@rubytech/create-maxy-code 0.1.477 → 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 (204) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1704-data-portal-standing-audit.md +673 -0
  3. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1789-reseat-channel-row-fork.md +1244 -0
  4. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1818-loop-gate-app-routes.md +462 -0
  5. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1819-top-level-label-allowlist.md +321 -0
  6. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1831-data-portal-class-derived-allowlist.md +2177 -0
  7. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1704-data-portal-standing-audit-design.md +177 -0
  8. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1789-reseat-channel-row-fork-design.md +201 -0
  9. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1818-loop-gate-app-routes-design.md +217 -0
  10. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1819-top-level-label-allowlist-design.md +80 -0
  11. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1820-cpu-triage-admin-tools-design.md +97 -0
  12. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1831-data-portal-class-derived-allowlist-design.md +163 -0
  13. package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
  14. package/payload/platform/lib/graph-style/dist/index.js +22 -0
  15. package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
  16. package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +6 -0
  17. package/payload/platform/lib/graph-style/src/index.ts +20 -0
  18. package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js +44 -0
  19. package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js.map +1 -1
  20. package/payload/platform/lib/storage-broker/dist/audit.d.ts +56 -0
  21. package/payload/platform/lib/storage-broker/dist/audit.d.ts.map +1 -1
  22. package/payload/platform/lib/storage-broker/dist/audit.js +34 -0
  23. package/payload/platform/lib/storage-broker/dist/audit.js.map +1 -1
  24. package/payload/platform/lib/storage-broker/src/__tests__/audit.test.ts +70 -1
  25. package/payload/platform/lib/storage-broker/src/audit.ts +111 -0
  26. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  27. package/payload/platform/plugins/admin/PLUGIN.md +7 -1
  28. package/payload/platform/plugins/admin/mcp/dist/index.js +72 -0
  29. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  30. package/payload/platform/plugins/admin/mcp/dist/tools/cpu-triage.d.ts +94 -0
  31. package/payload/platform/plugins/admin/mcp/dist/tools/cpu-triage.d.ts.map +1 -0
  32. package/payload/platform/plugins/admin/mcp/dist/tools/cpu-triage.js +229 -0
  33. package/payload/platform/plugins/admin/mcp/dist/tools/cpu-triage.js.map +1 -0
  34. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +14 -2
  35. package/payload/platform/plugins/cloudflare/PLUGIN.md +26 -1
  36. package/payload/platform/plugins/cloudflare/bin/d1-http.mjs +80 -0
  37. package/payload/platform/plugins/cloudflare/bin/portal-enrol.mjs +17 -2
  38. package/payload/platform/plugins/cloudflare/bin/portal-index-push.mjs +357 -0
  39. package/payload/platform/plugins/cloudflare/bin/schema-exposed-dirs.mjs +103 -0
  40. package/payload/platform/plugins/cloudflare/mcp/__tests__/authorize.test.ts +2 -2
  41. package/payload/platform/plugins/cloudflare/mcp/__tests__/d1-http.test.ts +96 -0
  42. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-directory-listing.test.ts +120 -0
  43. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-enrol.test.ts +59 -4
  44. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-push.test.ts +176 -0
  45. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-state-contract.test.ts +75 -0
  46. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-indexed-download.test.ts +131 -0
  47. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-sign.test.ts +68 -0
  48. package/payload/platform/plugins/cloudflare/mcp/__tests__/schema-exposed-dirs.test.ts +88 -0
  49. package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +39 -0
  50. package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +68 -1
  51. package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +30 -1
  52. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/portal-sign.mjs +87 -0
  53. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/session.ts +8 -4
  54. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +10 -0
  55. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +107 -1
  56. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +82 -2
  57. package/payload/platform/plugins/cloudflare/skills/data-portal/template/index.html +9 -0
  58. package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.css +56 -0
  59. package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.js +98 -2
  60. package/payload/platform/plugins/cloudflare/skills/data-portal/template/wrangler.toml +12 -0
  61. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  62. package/payload/platform/plugins/docs/references/cloudflare.md +12 -0
  63. package/payload/platform/plugins/email/PLUGIN.md +2 -1
  64. package/payload/platform/plugins/email/mcp/dist/__tests__/confirm-sent-copy.test.d.ts +2 -0
  65. package/payload/platform/plugins/email/mcp/dist/__tests__/confirm-sent-copy.test.d.ts.map +1 -0
  66. package/payload/platform/plugins/email/mcp/dist/__tests__/confirm-sent-copy.test.js +92 -0
  67. package/payload/platform/plugins/email/mcp/dist/__tests__/confirm-sent-copy.test.js.map +1 -0
  68. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js +112 -12
  69. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js.map +1 -1
  70. package/payload/platform/plugins/email/mcp/dist/__tests__/email-send-sent-copy.test.d.ts +2 -0
  71. package/payload/platform/plugins/email/mcp/dist/__tests__/email-send-sent-copy.test.d.ts.map +1 -0
  72. package/payload/platform/plugins/email/mcp/dist/__tests__/email-send-sent-copy.test.js +54 -0
  73. package/payload/platform/plugins/email/mcp/dist/__tests__/email-send-sent-copy.test.js.map +1 -0
  74. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js +170 -1
  75. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js.map +1 -1
  76. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-note.test.d.ts +2 -0
  77. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-note.test.d.ts.map +1 -0
  78. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-note.test.js +86 -0
  79. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-note.test.js.map +1 -0
  80. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-sweep.test.d.ts +2 -0
  81. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-sweep.test.d.ts.map +1 -0
  82. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-sweep.test.js +164 -0
  83. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-sweep.test.js.map +1 -0
  84. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-ledger.test.d.ts +2 -0
  85. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-ledger.test.d.ts.map +1 -0
  86. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-ledger.test.js +82 -0
  87. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-ledger.test.js.map +1 -0
  88. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +95 -0
  89. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
  90. package/payload/platform/plugins/email/mcp/dist/lib/imap.js +223 -0
  91. package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
  92. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-note.d.ts +18 -0
  93. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-note.d.ts.map +1 -0
  94. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-note.js +59 -0
  95. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-note.js.map +1 -0
  96. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-sweep.d.ts +48 -0
  97. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-sweep.d.ts.map +1 -0
  98. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-sweep.js +79 -0
  99. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-sweep.js.map +1 -0
  100. package/payload/platform/plugins/email/mcp/dist/lib/sent-ledger.d.ts +44 -0
  101. package/payload/platform/plugins/email/mcp/dist/lib/sent-ledger.d.ts.map +1 -0
  102. package/payload/platform/plugins/email/mcp/dist/lib/sent-ledger.js +63 -0
  103. package/payload/platform/plugins/email/mcp/dist/lib/sent-ledger.js.map +1 -0
  104. package/payload/platform/plugins/email/mcp/dist/scripts/sent-copy-sweep.d.ts +2 -0
  105. package/payload/platform/plugins/email/mcp/dist/scripts/sent-copy-sweep.d.ts.map +1 -0
  106. package/payload/platform/plugins/email/mcp/dist/scripts/sent-copy-sweep.js +55 -0
  107. package/payload/platform/plugins/email/mcp/dist/scripts/sent-copy-sweep.js.map +1 -0
  108. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.d.ts.map +1 -1
  109. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js +64 -13
  110. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js.map +1 -1
  111. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts.map +1 -1
  112. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js +11 -1
  113. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js.map +1 -1
  114. package/payload/platform/plugins/email/mcp/dist/tools/email-send.d.ts.map +1 -1
  115. package/payload/platform/plugins/email/mcp/dist/tools/email-send.js +16 -3
  116. package/payload/platform/plugins/email/mcp/dist/tools/email-send.js.map +1 -1
  117. package/payload/platform/plugins/email/references/email-reference.md +36 -0
  118. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.d.ts +2 -0
  119. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.d.ts.map +1 -0
  120. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.js +66 -0
  121. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.js.map +1 -0
  122. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/sent-sweep-throttle.test.d.ts +2 -0
  123. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/sent-sweep-throttle.test.d.ts.map +1 -0
  124. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/sent-sweep-throttle.test.js +18 -0
  125. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/sent-sweep-throttle.test.js.map +1 -0
  126. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.d.ts +41 -0
  127. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.d.ts.map +1 -0
  128. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.js +75 -0
  129. package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.js.map +1 -0
  130. package/payload/platform/plugins/scheduling/mcp/dist/lib/sent-sweep-throttle.d.ts +13 -0
  131. package/payload/platform/plugins/scheduling/mcp/dist/lib/sent-sweep-throttle.d.ts.map +1 -0
  132. package/payload/platform/plugins/scheduling/mcp/dist/lib/sent-sweep-throttle.js +10 -0
  133. package/payload/platform/plugins/scheduling/mcp/dist/lib/sent-sweep-throttle.js.map +1 -0
  134. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +175 -0
  135. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  136. package/payload/platform/scripts/cpu-triage-run.sh +117 -0
  137. package/payload/platform/scripts/cpu-triage.sh +23 -4
  138. package/payload/platform/services/claude-session-manager/dist/activity-range.d.ts +40 -0
  139. package/payload/platform/services/claude-session-manager/dist/activity-range.d.ts.map +1 -0
  140. package/payload/platform/services/claude-session-manager/dist/activity-range.js +68 -0
  141. package/payload/platform/services/claude-session-manager/dist/activity-range.js.map +1 -0
  142. package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts +40 -8
  143. package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts.map +1 -1
  144. package/payload/platform/services/claude-session-manager/dist/activity-rows.js +74 -7
  145. package/payload/platform/services/claude-session-manager/dist/activity-rows.js.map +1 -1
  146. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  147. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +2 -0
  148. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  149. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  150. package/payload/platform/services/claude-session-manager/dist/http-server.js +9 -0
  151. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  152. package/payload/server/{chunk-56WJMBQQ.js → chunk-3XLLTG6R.js} +14 -0
  153. package/payload/server/{chunk-JECAP3Z2.js → chunk-XGNRSM57.js} +3 -0
  154. package/payload/server/maxy-edge.js +1 -1
  155. package/payload/server/public/activity.html +8 -6
  156. package/payload/server/public/assets/{AdminLoginScreens-mEKP4pDi.js → AdminLoginScreens-DkxQZnvB.js} +1 -1
  157. package/payload/server/public/assets/AdminShell-Cv_nIFGe.js +2 -0
  158. package/payload/server/public/assets/Checkbox-CsTWK8mm.js +1 -0
  159. package/payload/server/public/assets/activity-BbnTiVBT.js +1 -0
  160. package/payload/server/public/assets/{admin-BVhgfgs1.js → admin-BZY3i_Ff.js} +1 -1
  161. package/payload/server/public/assets/{browser-D-HMmHcM.js → browser-BbIiSFKr.js} +1 -1
  162. package/payload/server/public/assets/{calendar-tGWvFyFn.js → calendar-BvxeTHvQ.js} +1 -1
  163. package/payload/server/public/assets/chat-DUKxufRk.js +1 -0
  164. package/payload/server/public/assets/chevron-left-BtfJD6LL.js +1 -0
  165. package/payload/server/public/assets/data-hl1wa-Ax.js +1 -0
  166. package/payload/server/public/assets/{graph-zEw610xK.js → graph-CDSGRoeb.js} +1 -1
  167. package/payload/server/public/assets/graph-labels-C5qLwnWA.js +1 -0
  168. package/payload/server/public/assets/{maximize-2-BlTjXT_Y.js → maximize-2-CAzH8dED.js} +1 -1
  169. package/payload/server/public/assets/{operator-C7oIw2PG.js → operator-BK7r8DAB.js} +1 -1
  170. package/payload/server/public/assets/{page-CTP7OFZa.js → page-B5YngOrK.js} +1 -1
  171. package/payload/server/public/assets/{page-DL6Zsdvk.js → page-BsCS0Dmc.js} +1 -1
  172. package/payload/server/public/assets/{public-r1A9dqR_.js → public-C1Pz-IBs.js} +1 -1
  173. package/payload/server/public/assets/{rotate-ccw-CzkkKx4-.js → rotate-ccw-UDln4Tou.js} +1 -1
  174. package/payload/server/public/assets/tasks-B-cLsn3p.js +1 -0
  175. package/payload/server/public/assets/{time-entry-format-j669DgXj.js → time-entry-format-DPOBIE18.js} +1 -1
  176. package/payload/server/public/assets/{triangle-alert-BPgCgIef.js → triangle-alert-Dz-CZKo5.js} +1 -1
  177. package/payload/server/public/assets/{useCopyFeedback-BmLa2aes.js → useCopyFeedback-wtUHGohk.js} +1 -1
  178. package/payload/server/public/assets/{useSelectionMode-DnnM7A8-.js → useSelectionMode-sph7AQT5.js} +1 -1
  179. package/payload/server/public/assets/useSubAccountSwitcher-BRIluIKf.css +1 -0
  180. package/payload/server/public/assets/{useVoiceRecorder-u2dwZfen.js → useVoiceRecorder-DN4jYQ0A.js} +1 -1
  181. package/payload/server/public/brand-defaults.css +1 -0
  182. package/payload/server/public/browser.html +5 -5
  183. package/payload/server/public/calendar.html +6 -6
  184. package/payload/server/public/chat.html +12 -12
  185. package/payload/server/public/data.html +11 -11
  186. package/payload/server/public/graph.html +11 -11
  187. package/payload/server/public/index.html +14 -14
  188. package/payload/server/public/operator.html +14 -14
  189. package/payload/server/public/privacy.html +1 -0
  190. package/payload/server/public/public.html +12 -12
  191. package/payload/server/public/tasks.html +6 -5
  192. package/payload/server/public/vnc-popout.html +1 -0
  193. package/payload/server/server.js +894 -384
  194. package/payload/server/{src-JYPKMWJR.js → src-XQ63FPRE.js} +3 -1
  195. package/payload/server/public/assets/AdminShell-BwSaZ88d.js +0 -2
  196. package/payload/server/public/assets/Checkbox-CN4uQ80w.js +0 -1
  197. package/payload/server/public/assets/activity-CIQ8ozNZ.js +0 -1
  198. package/payload/server/public/assets/chat-DkttVxAz.js +0 -1
  199. package/payload/server/public/assets/chevron-left-CZ4ez9G5.js +0 -1
  200. package/payload/server/public/assets/data-P-mcmnNi.js +0 -1
  201. package/payload/server/public/assets/graph-labels-qnBleOE6.js +0 -1
  202. package/payload/server/public/assets/tasks-fIJwYFWG.js +0 -1
  203. package/payload/server/public/assets/useSubAccountSwitcher-C_E8h07P.css +0 -1
  204. /package/payload/server/public/assets/{useSubAccountSwitcher-CFa6ZAT1.js → useSubAccountSwitcher-v8LyBzZN.js} +0 -0
@@ -117,7 +117,7 @@ import {
117
117
  vncLog,
118
118
  walkPremiumBundles,
119
119
  writeAdminUserAndPerson
120
- } from "./chunk-JECAP3Z2.js";
120
+ } from "./chunk-XGNRSM57.js";
121
121
  import {
122
122
  D1_MAX_SQL_STATEMENT_BYTES,
123
123
  D1_QUERY_MAX_BODY_BYTES,
@@ -129,10 +129,11 @@ import {
129
129
  listResources,
130
130
  makeHouseCfExec,
131
131
  makeHousePagesExec,
132
+ reconcileDataPortal,
132
133
  registerResource,
133
134
  resolveOwner,
134
135
  tooLargeMessage
135
- } from "./chunk-56WJMBQQ.js";
136
+ } from "./chunk-3XLLTG6R.js";
136
137
  import {
137
138
  __commonJS,
138
139
  __toESM
@@ -651,7 +652,17 @@ var require_dist3 = __commonJS({
651
652
  Risk: "#C15B57",
652
653
  Source: "#2F8FA6",
653
654
  Finding: "#4FA389",
654
- 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"
655
666
  };
656
667
  exports.FALLBACK_LABEL_COLOUR = "#94A3B8";
657
668
  exports.ALL_GRAPH_LABELS = Object.freeze(Object.keys(exports.GRAPH_LABEL_COLOURS));
@@ -748,7 +759,8 @@ var require_dist3 = __commonJS({
748
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>',
749
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>',
750
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>',
751
- 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>'
752
764
  };
753
765
  function iconForLabel2(label) {
754
766
  return Object.prototype.hasOwnProperty.call(LABEL_ICONS, label) ? LABEL_ICONS[label] : null;
@@ -844,6 +856,11 @@ var require_dist3 = __commonJS({
844
856
  if (clientName.length > 0) {
845
857
  return clientName.length > 24 ? clientName.slice(0, 24) + "\u2026" : clientName;
846
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
+ }
847
864
  } else if (primaryLabel2 === "Message") {
848
865
  const role = typeof props.role === "string" ? props.role : "";
849
866
  const roleShort = role === "user" ? "user" : role === "assistant" ? "asst" : role === "system" ? "sys" : role === "tool" ? "tool" : null;
@@ -917,6 +934,10 @@ var require_dist3 = __commonJS({
917
934
  const clientName = typeof props.clientName === "string" ? props.clientName : "";
918
935
  if (clientName.length > 0)
919
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;
920
941
  }
921
942
  for (const k of ["name", "title", "summary", "givenName", "subject", "text"]) {
922
943
  const v = props[k];
@@ -1290,8 +1311,8 @@ var require_dist4 = __commonJS({
1290
1311
  let edgesCreated = 0;
1291
1312
  for (const rel of relationships) {
1292
1313
  const type = rel.type.replace(/`/g, "");
1293
- 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)`;
1294
- 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 });
1295
1316
  const created = r.summary.counters.updates().relationshipsCreated;
1296
1317
  if (created === 0) {
1297
1318
  process.stderr.write(`[graph-write] reject reason=unresolved-target-on-create labels=${labelCsv} agent=${agentLabel} relType=${rel.type} targetId=${rel.targetNodeId}
@@ -1539,10 +1560,10 @@ var serveStatic = (options = { root: "" }) => {
1539
1560
  };
1540
1561
 
1541
1562
  // server/index.ts
1542
- 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";
1543
1564
  import { spawn as spawn3 } from "child_process";
1544
1565
  import { createHash as createHash7 } from "crypto";
1545
- 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";
1546
1567
  import { homedir as homedir4 } from "os";
1547
1568
  import { monitorEventLoopDelay } from "perf_hooks";
1548
1569
 
@@ -1957,14 +1978,14 @@ function readAllConversationsCached(accountId) {
1957
1978
  for (const file of dir) {
1958
1979
  if (!file.endsWith(".jsonl")) continue;
1959
1980
  const path3 = join4(STORE_ROOT(), accountId, file);
1960
- let stat8;
1981
+ let stat9;
1961
1982
  try {
1962
- stat8 = statSync3(path3);
1983
+ stat9 = statSync3(path3);
1963
1984
  } catch {
1964
1985
  continue;
1965
1986
  }
1966
1987
  const hit = idCache.get(path3);
1967
- if (hit && hit.size === stat8.size && hit.mtimeMs === stat8.mtimeMs) {
1988
+ if (hit && hit.size === stat9.size && hit.mtimeMs === stat9.mtimeMs) {
1968
1989
  out.set(file, hit.ids);
1969
1990
  continue;
1970
1991
  }
@@ -1981,7 +2002,7 @@ function readAllConversationsCached(accountId) {
1981
2002
  } catch {
1982
2003
  continue;
1983
2004
  }
1984
- idCache.set(path3, { size: stat8.size, mtimeMs: stat8.mtimeMs, ids: parsed });
2005
+ idCache.set(path3, { size: stat9.size, mtimeMs: stat9.mtimeMs, ids: parsed });
1985
2006
  out.set(file, parsed);
1986
2007
  }
1987
2008
  return out;
@@ -2815,7 +2836,7 @@ var credsSaveQueue = Promise.resolve();
2815
2836
  async function drainCredsSaveQueue(timeoutMs = 5e3) {
2816
2837
  console.error(`${TAG6} draining credential save queue\u2026`);
2817
2838
  const timer = new Promise(
2818
- (resolve37) => setTimeout(() => resolve37("timeout"), timeoutMs)
2839
+ (resolve38) => setTimeout(() => resolve38("timeout"), timeoutMs)
2819
2840
  );
2820
2841
  const result = await Promise.race([
2821
2842
  credsSaveQueue.then(() => "drained"),
@@ -2992,11 +3013,11 @@ async function createWaSocket(opts) {
2992
3013
  return sock;
2993
3014
  }
2994
3015
  async function waitForConnection(sock) {
2995
- return new Promise((resolve37, reject) => {
3016
+ return new Promise((resolve38, reject) => {
2996
3017
  const handler = (update) => {
2997
3018
  if (update.connection === "open") {
2998
3019
  sock.ev.off("connection.update", handler);
2999
- resolve37();
3020
+ resolve38();
3000
3021
  }
3001
3022
  if (update.connection === "close") {
3002
3023
  sock.ev.off("connection.update", handler);
@@ -3104,7 +3125,7 @@ function isTimeoutError(err) {
3104
3125
  return Boolean(err?.__maxyTimeout);
3105
3126
  }
3106
3127
  function withTimeout(label, promise, timeoutMs) {
3107
- return new Promise((resolve37, reject) => {
3128
+ return new Promise((resolve38, reject) => {
3108
3129
  const timer = setTimeout(() => {
3109
3130
  const err = new Error(`${label} timed out after ${timeoutMs}ms`);
3110
3131
  err.__maxyTimeout = true;
@@ -3114,7 +3135,7 @@ function withTimeout(label, promise, timeoutMs) {
3114
3135
  promise.then(
3115
3136
  (value) => {
3116
3137
  clearTimeout(timer);
3117
- resolve37(value);
3138
+ resolve38(value);
3118
3139
  },
3119
3140
  (err) => {
3120
3141
  clearTimeout(timer);
@@ -4028,15 +4049,15 @@ function getChannelActivity(accountId) {
4028
4049
 
4029
4050
  // app/lib/whatsapp/outbound/send.ts
4030
4051
  var TAG10 = "[whatsapp:outbound]";
4031
- function buildQuoted(q) {
4052
+ function buildQuoted(q2) {
4032
4053
  return {
4033
4054
  key: {
4034
- remoteJid: q.remoteJid,
4035
- id: q.id,
4036
- fromMe: q.fromMe,
4037
- ...q.participant ? { participant: q.participant } : {}
4055
+ remoteJid: q2.remoteJid,
4056
+ id: q2.id,
4057
+ fromMe: q2.fromMe,
4058
+ ...q2.participant ? { participant: q2.participant } : {}
4038
4059
  },
4039
- message: { conversation: q.body ?? "" }
4060
+ message: { conversation: q2.body ?? "" }
4040
4061
  };
4041
4062
  }
4042
4063
  async function sendTextMessage(sock, to, text, opts) {
@@ -4154,8 +4175,8 @@ async function persistWhatsAppMessage(input) {
4154
4175
  const { givenName, familyName } = splitName(input.pushName);
4155
4176
  const prev = sessionWriteLocks.get(input.cacheKey);
4156
4177
  let release;
4157
- const mine = new Promise((resolve37) => {
4158
- release = resolve37;
4178
+ const mine = new Promise((resolve38) => {
4179
+ release = resolve38;
4159
4180
  });
4160
4181
  const chained = (prev ?? Promise.resolve()).then(() => mine);
4161
4182
  sessionWriteLocks.set(input.cacheKey, chained);
@@ -5859,11 +5880,11 @@ async function connectWithReconnect(conn) {
5859
5880
  console.error(
5860
5881
  `${TAG17} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
5861
5882
  );
5862
- await new Promise((resolve37) => {
5863
- const timer = setTimeout(resolve37, delay);
5883
+ await new Promise((resolve38) => {
5884
+ const timer = setTimeout(resolve38, delay);
5864
5885
  conn.abortController.signal.addEventListener("abort", () => {
5865
5886
  clearTimeout(timer);
5866
- resolve37();
5887
+ resolve38();
5867
5888
  }, { once: true });
5868
5889
  });
5869
5890
  }
@@ -5871,16 +5892,16 @@ async function connectWithReconnect(conn) {
5871
5892
  }
5872
5893
  }
5873
5894
  function waitForDisconnectEvent(conn) {
5874
- return new Promise((resolve37) => {
5895
+ return new Promise((resolve38) => {
5875
5896
  if (!conn.sock) {
5876
- resolve37();
5897
+ resolve38();
5877
5898
  return;
5878
5899
  }
5879
5900
  const sock = conn.sock;
5880
5901
  const handler = (update) => {
5881
5902
  if (update.connection === "close") {
5882
5903
  sock.ev.off("connection.update", handler);
5883
- resolve37();
5904
+ resolve38();
5884
5905
  }
5885
5906
  };
5886
5907
  sock.ev.on("connection.update", handler);
@@ -6290,8 +6311,8 @@ async function handleInboundMessage(conn, msg) {
6290
6311
  const conversationKey = isGroup ? remoteJid : senderPhone;
6291
6312
  const debounceKey = `${conn.accountId}:${conversationKey}:${senderPhone}`;
6292
6313
  let resolvePending;
6293
- const sttPending = new Promise((resolve37) => {
6294
- resolvePending = resolve37;
6314
+ const sttPending = new Promise((resolve38) => {
6315
+ resolvePending = resolve38;
6295
6316
  });
6296
6317
  if (conn.debouncer) conn.debouncer.registerPending(debounceKey, sttPending);
6297
6318
  try {
@@ -6719,20 +6740,20 @@ function buildX11Env(chromiumWrapperPath, transport = "vnc") {
6719
6740
 
6720
6741
  // server/routes/health.ts
6721
6742
  function checkPort(port2, timeoutMs = 500) {
6722
- return new Promise((resolve37) => {
6743
+ return new Promise((resolve38) => {
6723
6744
  const socket = createConnection2(port2, "127.0.0.1");
6724
6745
  socket.setTimeout(timeoutMs);
6725
6746
  socket.once("connect", () => {
6726
6747
  socket.destroy();
6727
- resolve37(true);
6748
+ resolve38(true);
6728
6749
  });
6729
6750
  socket.once("error", () => {
6730
6751
  socket.destroy();
6731
- resolve37(false);
6752
+ resolve38(false);
6732
6753
  });
6733
6754
  socket.once("timeout", () => {
6734
6755
  socket.destroy();
6735
- resolve37(false);
6756
+ resolve38(false);
6736
6757
  });
6737
6758
  });
6738
6759
  }
@@ -6987,8 +7008,8 @@ function webchatTurnTimeoutMs() {
6987
7008
  return Number(process.env.WEBCHAT_TURN_TIMEOUT_MS ?? String(2 * 6e4));
6988
7009
  }
6989
7010
  function createChatRoutes(deps) {
6990
- const app64 = new Hono();
6991
- app64.post("/", async (c) => {
7011
+ const app65 = new Hono();
7012
+ app65.post("/", async (c) => {
6992
7013
  console.log(`[chat-route] entered route=public method=POST`);
6993
7014
  const contentType = c.req.header("content-type") ?? "";
6994
7015
  const account = resolveAccount();
@@ -7252,7 +7273,7 @@ ${result.result.text}` : result.result.text;
7252
7273
  }
7253
7274
  });
7254
7275
  });
7255
- return app64;
7276
+ return app65;
7256
7277
  }
7257
7278
 
7258
7279
  // app/lib/channel-pty-bridge/admin-session-id.ts
@@ -7286,8 +7307,8 @@ function isLoopbackAddr(addr) {
7286
7307
  return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
7287
7308
  }
7288
7309
  function createScheduleInjectRoutes(deps) {
7289
- const app64 = new Hono();
7290
- app64.post("/", async (c) => {
7310
+ const app65 = new Hono();
7311
+ app65.post("/", async (c) => {
7291
7312
  const env = c.env;
7292
7313
  const remoteAddr = env?.incoming?.socket?.remoteAddress ?? "";
7293
7314
  if (!isLoopbackAddr(remoteAddr)) {
@@ -7388,7 +7409,7 @@ function createScheduleInjectRoutes(deps) {
7388
7409
  console.error(`${TAG20} op=inject-spawn eventId=${eventId} sessionId=${sessionId} result=ok status=-`);
7389
7410
  return c.json({ ok: true }, 200);
7390
7411
  });
7391
- return app64;
7412
+ return app65;
7392
7413
  }
7393
7414
 
7394
7415
  // server/index.ts
@@ -8021,8 +8042,8 @@ async function startLogin(opts) {
8021
8042
  await clearAuth(authDir);
8022
8043
  let resolveCode = null;
8023
8044
  let rejectCode = null;
8024
- const codePromise = new Promise((resolve37, reject) => {
8025
- resolveCode = resolve37;
8045
+ const codePromise = new Promise((resolve38, reject) => {
8046
+ resolveCode = resolve38;
8026
8047
  rejectCode = reject;
8027
8048
  });
8028
8049
  const codeTimer = setTimeout(
@@ -9637,7 +9658,7 @@ app3.post("/r2/object/delete", envelopeBodyLimit("write"), async (c) => {
9637
9658
  });
9638
9659
  var storage_broker_default = app3;
9639
9660
  async function runStorageAudit(root) {
9640
- const { reconcileStorage } = await import("./src-JYPKMWJR.js");
9661
+ const { reconcileStorage } = await import("./src-XQ63FPRE.js");
9641
9662
  const cf = await makeHouseCfExec(root);
9642
9663
  const accountsDir = resolve11(root, "..", "data", "accounts");
9643
9664
  const strays = (await reconcileStorage({
@@ -9677,26 +9698,108 @@ async function runStorageAudit(root) {
9677
9698
  );
9678
9699
  }
9679
9700
  async function runPagesAudit(root) {
9680
- const { reconcilePages } = await import("./src-JYPKMWJR.js");
9681
- const session = getSession();
9682
- try {
9683
- const cf = await makeHousePagesExec(root);
9684
- const r = await reconcilePages({
9685
- cfProjects: async () => (await cf.pagesProjectList()).map((p) => p.name),
9686
- registeredProjects: async () => {
9701
+ const { reconcilePages } = await import("./src-XQ63FPRE.js");
9702
+ const cf = await makeHousePagesExec(root);
9703
+ const r = await reconcilePages({
9704
+ cfProjects: async () => (await cf.pagesProjectList()).map((p) => p.name),
9705
+ // Its own session per invocation, same rule as runStorageAudit above and
9706
+ // for the same reason. This previously held one session opened in the
9707
+ // enclosing scope, which was safe only because reconcilePages issues
9708
+ // exactly one registry query — a property of the library, not of this
9709
+ // caller. A second read added there would have reproduced 1813 here
9710
+ // verbatim. The old shape also held that session open across
9711
+ // makeHousePagesExec's network I/O, which is a pooled connection parked
9712
+ // for the length of a Cloudflare round trip.
9713
+ registeredProjects: async () => {
9714
+ const session = getSession();
9715
+ try {
9687
9716
  const res = await session.run(
9688
9717
  "MATCH (r:StorageResource {kind: $kind}) RETURN r.name AS name",
9689
9718
  { kind: "pages" }
9690
9719
  );
9691
9720
  return res.records.map((rec) => rec.get("name"));
9721
+ } finally {
9722
+ await session.close();
9692
9723
  }
9693
- });
9724
+ }
9725
+ });
9726
+ console.error(
9727
+ `[pages-audit] projects=${r.projects} registered=${r.registered} orphan=${r.orphans.length} phantom=${r.phantoms.length}` + r.orphans.map((n) => ` orphan:${n}`).join("") + r.phantoms.map((n) => ` phantom:${n}`).join("")
9728
+ );
9729
+ }
9730
+ function parseManifestRows(raw) {
9731
+ const envelope = Array.isArray(raw) ? raw[0] : null;
9732
+ const results = envelope?.results;
9733
+ if (!Array.isArray(results)) {
9734
+ throw new Error(`unexpected d1 payload: envelope carries no results array`);
9735
+ }
9736
+ return results.map((r, i) => {
9737
+ const row = r;
9738
+ const bad = ["objectKey", "ingested", "uploadedAt"].find(
9739
+ (f) => f === "ingested" ? typeof row[f] !== "number" : typeof row[f] !== "string"
9740
+ );
9741
+ if (bad) throw new Error(`unexpected d1 row: index=${i} field=${bad} type=${typeof row[bad]}`);
9742
+ return { objectKey: row.objectKey, ingested: row.ingested, uploadedAt: row.uploadedAt };
9743
+ });
9744
+ }
9745
+ function auditErrorText(message) {
9746
+ return message.split("\n")[0].replaceAll('"', "'").slice(0, 160);
9747
+ }
9748
+ function discoverPortalAccounts(accountsDir) {
9749
+ if (!existsSync9(accountsDir)) return [];
9750
+ const found = [];
9751
+ for (const accountId of readdirSync8(accountsDir)) {
9752
+ const configPath2 = join13(accountsDir, accountId, "data-portal.json");
9753
+ if (!existsSync9(configPath2)) continue;
9754
+ try {
9755
+ const cfg = JSON.parse(readFileSync15(configPath2, "utf-8"));
9756
+ if (typeof cfg.portalDbName === "string" && cfg.portalDbName.length > 0 && typeof cfg.bucketName === "string" && cfg.bucketName.length > 0) {
9757
+ found.push({ accountId, portalDbName: cfg.portalDbName, bucketName: cfg.bucketName });
9758
+ }
9759
+ } catch {
9760
+ }
9761
+ }
9762
+ return found;
9763
+ }
9764
+ async function runDataPortalAudit(root) {
9765
+ const portals = discoverPortalAccounts(resolve11(root, "..", "data", "accounts"));
9766
+ if (portals.length === 0) return "portals=0 degraded=0";
9767
+ const cf = await makeHouseCfExec(root);
9768
+ let degraded = 0;
9769
+ for (const portal of portals) {
9770
+ let rows = null;
9771
+ let d1Error = null;
9772
+ try {
9773
+ rows = parseManifestRows(
9774
+ await cf.d1Query(
9775
+ portal.portalDbName,
9776
+ "SELECT objectKey, ingested, uploadedAt FROM manifest"
9777
+ )
9778
+ );
9779
+ } catch (err) {
9780
+ d1Error = err.message;
9781
+ }
9782
+ let objects = null;
9783
+ let r2Error = null;
9784
+ try {
9785
+ objects = await cf.r2ObjectList(portal.bucketName);
9786
+ } catch (err) {
9787
+ r2Error = err.message;
9788
+ }
9789
+ const nowMs = Date.now();
9790
+ const reconciled = rows !== null && objects !== null ? reconcileDataPortal(objects, rows, nowMs) : null;
9791
+ const ageHrs = rows === null ? null : reconcileDataPortal([], rows, nowMs).oldestUningestedHrs;
9792
+ const degradation = [d1Error !== null ? "d1" : null, r2Error !== null ? "r2" : null].filter(Boolean).join("+");
9793
+ if (degradation) degraded += 1;
9794
+ const reasons = [
9795
+ d1Error !== null ? `d1:${auditErrorText(d1Error)}` : null,
9796
+ r2Error !== null ? `r2:${auditErrorText(r2Error)}` : null
9797
+ ].filter(Boolean).join(" | ");
9694
9798
  console.error(
9695
- `[pages-audit] projects=${r.projects} registered=${r.registered} orphan=${r.orphans.length} phantom=${r.phantoms.length}` + r.orphans.map((n) => ` orphan:${n}`).join("") + r.phantoms.map((n) => ` phantom:${n}`).join("")
9799
+ `[data-portal-audit] account=${portal.accountId} objects=${objects?.length ?? "na"} rows=${rows?.length ?? "na"} orphanObjects=${reconciled?.orphanObjects.length ?? "na"} phantomRows=${reconciled?.phantomRows.length ?? "na"} oldestUningestedHrs=${ageHrs?.toFixed(1) ?? "na"}` + (degradation ? ` degraded=${degradation} err="${reasons}"` : "")
9696
9800
  );
9697
- } finally {
9698
- await session.close();
9699
9801
  }
9802
+ return `portals=${portals.length} degraded=${degraded}`;
9700
9803
  }
9701
9804
 
9702
9805
  // server/google-pending-audit.ts
@@ -11254,12 +11357,12 @@ app5.get("/store-stream", requireAdminSession, (c) => {
11254
11357
  ...reactions && reactions.length > 0 ? { reactions } : {}
11255
11358
  };
11256
11359
  if (r.quotedId) {
11257
- const q = byMsgKeyId.get(r.quotedId);
11360
+ const q2 = byMsgKeyId.get(r.quotedId);
11258
11361
  turn = {
11259
11362
  ...turn,
11260
- 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 }
11261
11364
  };
11262
- 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}`);
11263
11366
  }
11264
11367
  if (r.attachmentId) {
11265
11368
  const meta = readAttachmentMeta(join19(uploadsBase, r.attachmentId));
@@ -12169,10 +12272,7 @@ function locateSidecar(sessionId) {
12169
12272
  }
12170
12273
  for (const slug of slugs) {
12171
12274
  const metaPath = join23(projectsRoot, slug, `${sessionId}.meta.json`);
12172
- if (existsSync15(metaPath)) {
12173
- const meta = readSidecarMeta(metaPath);
12174
- return { channel: meta.channel, adminUserId: meta.adminUserId, accountId: meta.accountId };
12175
- }
12275
+ if (existsSync15(metaPath)) return readSidecarMeta(metaPath);
12176
12276
  }
12177
12277
  return null;
12178
12278
  }
@@ -12185,6 +12285,13 @@ function readTargetOwner(sessionId) {
12185
12285
  }
12186
12286
  return { found: false, owner: null };
12187
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
+ }
12188
12295
  function ownedByRequester(owner, requesterUserId, primaryUserId) {
12189
12296
  if (requesterUserId === null) return false;
12190
12297
  return owner === requesterUserId || owner === null && requesterUserId === primaryUserId;
@@ -12393,8 +12500,8 @@ async function reapplyLeversViaManager() {
12393
12500
  var WEBCHAT_SEND_TURN_WINDOW_MS = Number(process.env.WEBCHAT_SEND_TURN_WINDOW_MS ?? String(15e3));
12394
12501
  var sendSeq = 0;
12395
12502
  function createWebchatRoutes(deps) {
12396
- const app64 = new Hono();
12397
- app64.post("/send", requireAdminSession, async (c) => {
12503
+ const app65 = new Hono();
12504
+ app65.post("/send", requireAdminSession, async (c) => {
12398
12505
  let accountId;
12399
12506
  try {
12400
12507
  accountId = resolvePlatformAccountId();
@@ -12606,7 +12713,7 @@ ${note}` : note;
12606
12713
  if (turnTimer.unref) turnTimer.unref();
12607
12714
  return c.json({ ok: true });
12608
12715
  });
12609
- app64.post("/interrupt", requireAdminSession, async (c) => {
12716
+ app65.post("/interrupt", requireAdminSession, async (c) => {
12610
12717
  const cacheKey = c.get("cacheKey");
12611
12718
  const requesterUserId = (cacheKey ? getUserIdForSession(cacheKey) : void 0) ?? null;
12612
12719
  const primaryUserId = resolvePrimaryAdminUserId();
@@ -12634,7 +12741,7 @@ ${note}` : note;
12634
12741
  }
12635
12742
  return c.json({ ok: true, stopped: outcome.stopped, intId: outcome.intId, deadChild: outcome.deadChild }, 200);
12636
12743
  });
12637
- app64.post("/settings", requireAdminSession, async (c) => {
12744
+ app65.post("/settings", requireAdminSession, async (c) => {
12638
12745
  const body = await c.req.json().catch(() => null);
12639
12746
  const op = body?.op;
12640
12747
  const value = body?.value;
@@ -12674,7 +12781,7 @@ ${note}` : note;
12674
12781
  console.log(`[webchat:settings] op=${op} outcome=accepted value=${value} settingsApplied=${settingsApplied}`);
12675
12782
  return c.json({ ok: true, settingsApplied });
12676
12783
  });
12677
- app64.get("/session", requireAdminSession, async (c) => {
12784
+ app65.get("/session", requireAdminSession, async (c) => {
12678
12785
  let accountId;
12679
12786
  try {
12680
12787
  accountId = resolvePlatformAccountId();
@@ -12752,7 +12859,7 @@ ${note}` : note;
12752
12859
  const indicators = await fetchComposerIndicators(sessionId);
12753
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) });
12754
12861
  });
12755
- app64.post("/permission-verdict", requireAdminSession, async (c) => {
12862
+ app65.post("/permission-verdict", requireAdminSession, async (c) => {
12756
12863
  const body = await c.req.json().catch(() => null);
12757
12864
  const sessionId = body?.sessionId;
12758
12865
  const requestId = body?.request_id;
@@ -12769,7 +12876,7 @@ ${note}` : note;
12769
12876
  const ok = deps.resolvePermissionVerdict?.(`session:${sessionId}`, requestId, behavior) ?? false;
12770
12877
  return c.json({ ok });
12771
12878
  });
12772
- return app64;
12879
+ return app65;
12773
12880
  }
12774
12881
 
12775
12882
  // server/routes/webchat-greeting.ts
@@ -14125,15 +14232,15 @@ function operatorRoleFor(config, userId) {
14125
14232
  async function buildAccountOptionList(accounts, userId) {
14126
14233
  const houseAccount = accounts.find((a) => a.config.role === "house");
14127
14234
  console.log(`[admin-accounts] op=list count=${accounts.length} house=${houseAccount?.accountId ?? "none"}`);
14128
- const resolve37 = async (a) => {
14235
+ const resolve38 = async (a) => {
14129
14236
  const businessName = await fetchAccountName(a.accountId) || void 0;
14130
14237
  return { accountId: a.accountId, businessName, role: operatorRoleFor(a.config, userId), isHouse: a.config.role === "house" };
14131
14238
  };
14132
14239
  const houseOptions = await Promise.all(
14133
- accounts.filter((a) => a.config.role === "house").map(resolve37)
14240
+ accounts.filter((a) => a.config.role === "house").map(resolve38)
14134
14241
  );
14135
14242
  const nonHouseOptions = await Promise.all(
14136
- accounts.filter((a) => a.config.role !== "house").map(resolve37)
14243
+ accounts.filter((a) => a.config.role !== "house").map(resolve38)
14137
14244
  );
14138
14245
  const sortKey = (o) => (o.businessName || o.accountId).toLowerCase();
14139
14246
  nonHouseOptions.sort((x, y) => {
@@ -15140,9 +15247,9 @@ app18.post("/:id/resume", async (c) => {
15140
15247
  if (healQueue.length > 0) {
15141
15248
  void (async () => {
15142
15249
  for (let i = 0; i < healQueue.length; i++) {
15143
- const q = healQueue[i];
15250
+ const q2 = healQueue[i];
15144
15251
  try {
15145
- const healComponents = await Promise.all(q.components.map(async (c2) => {
15252
+ const healComponents = await Promise.all(q2.components.map(async (c2) => {
15146
15253
  const base = {
15147
15254
  componentId: c2.componentId,
15148
15255
  name: c2.name,
@@ -15180,25 +15287,25 @@ app18.post("/:id/resume", async (c) => {
15180
15287
  }));
15181
15288
  const messageId = await persistMessage(
15182
15289
  sessionId,
15183
- q.role,
15184
- q.content,
15290
+ q2.role,
15291
+ q2.content,
15185
15292
  accountId,
15186
15293
  void 0,
15187
- q.createdAt,
15294
+ q2.createdAt,
15188
15295
  void 0,
15189
15296
  healComponents,
15190
15297
  void 0
15191
15298
  );
15192
15299
  jsonlHealOk += 1;
15193
15300
  try {
15194
- 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)}
15195
15302
  `);
15196
15303
  } catch {
15197
15304
  }
15198
15305
  } catch (err) {
15199
15306
  jsonlHealFail += 1;
15200
15307
  try {
15201
- 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))}
15202
15309
  `);
15203
15310
  } catch {
15204
15311
  }
@@ -15523,10 +15630,10 @@ function computeSpecialistDomains(accountId) {
15523
15630
  const resolveDesc = (qualified) => {
15524
15631
  if (!qualified.startsWith("mcp__")) return void 0;
15525
15632
  const rest = qualified.slice(5);
15526
- const sep9 = rest.indexOf("__");
15527
- if (sep9 < 0) return void 0;
15528
- const plugin = rest.slice(0, sep9);
15529
- 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);
15530
15637
  let map = descByPlugin.get(plugin);
15531
15638
  if (!map) {
15532
15639
  const dir = pluginDirs.get(plugin);
@@ -15829,18 +15936,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
15829
15936
  ]);
15830
15937
  var app21 = new Hono();
15831
15938
  app21.post("/", async (c) => {
15832
- const TAG53 = "[admin:events]";
15939
+ const TAG54 = "[admin:events]";
15833
15940
  let body;
15834
15941
  try {
15835
15942
  body = await c.req.json();
15836
15943
  } catch (err) {
15837
15944
  const detail = err instanceof Error ? err.message : String(err);
15838
- console.error(`${TAG53} reject reason=body-not-json detail=${detail}`);
15945
+ console.error(`${TAG54} reject reason=body-not-json detail=${detail}`);
15839
15946
  return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
15840
15947
  }
15841
15948
  const event = typeof body.event === "string" ? body.event : "";
15842
15949
  if (!ALLOWED_EVENTS.has(event)) {
15843
- 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)}`);
15844
15951
  return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
15845
15952
  }
15846
15953
  const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
@@ -18481,7 +18588,7 @@ var CONVERSATION_PARENT_LABELS = /* @__PURE__ */ new Set(["AdminConversation", "
18481
18588
  var app23 = new Hono();
18482
18589
  app23.get("/", requireAdminSession, async (c) => {
18483
18590
  const cacheKey = c.var.cacheKey;
18484
- const q = (c.req.query("q") ?? "").trim();
18591
+ const q2 = (c.req.query("q") ?? "").trim();
18485
18592
  const rawLimit = c.req.query("limit");
18486
18593
  const rawLabels = c.req.query("labels");
18487
18594
  const rawThreshold = c.req.query("threshold");
@@ -18490,13 +18597,13 @@ app23.get("/", requireAdminSession, async (c) => {
18490
18597
  console.error(`[graph-search] auth-rejected endpoint="/api/admin/graph-search" reason="no account for session"`);
18491
18598
  return c.json({ error: "Account not found for session" }, 401);
18492
18599
  }
18493
- if (!q) return c.json({ error: "q (search query) required" }, 400);
18600
+ if (!q2) return c.json({ error: "q (search query) required" }, 400);
18494
18601
  const labels = rawLabels ? rawLabels.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : [];
18495
18602
  const wildcard = labels.includes("*");
18496
18603
  const noChips = labels.length === 0;
18497
18604
  const allLabels = wildcard || noChips;
18498
18605
  if (noChips && !wildcard) {
18499
- console.error(`[graph-search] op=labels-default applied=all query="${q}"`);
18606
+ console.error(`[graph-search] op=labels-default applied=all query="${q2}"`);
18500
18607
  }
18501
18608
  const parsedLimit = rawLimit ? parseInt(rawLimit, 10) : DEFAULT_LIMIT;
18502
18609
  const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, MAX_LIMIT) : DEFAULT_LIMIT;
@@ -18516,7 +18623,7 @@ app23.get("/", requireAdminSession, async (c) => {
18516
18623
  const session = getSession();
18517
18624
  try {
18518
18625
  const res = await hybrid(session, embed, {
18519
- query: q,
18626
+ query: q2,
18520
18627
  accountId,
18521
18628
  labels: forwardedLabels,
18522
18629
  limit,
@@ -18526,7 +18633,7 @@ app23.get("/", requireAdminSession, async (c) => {
18526
18633
  const total = Date.now() - started;
18527
18634
  if (res.embedError) {
18528
18635
  console.error(`[graph-search] embed-unavailable err="${res.embedError}" \u2014 bm25-only`);
18529
- 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}`);
18530
18637
  }
18531
18638
  let labelMatchCount = 0;
18532
18639
  let bodyFulltextCount = 0;
@@ -18577,16 +18684,16 @@ app23.get("/", requireAdminSession, async (c) => {
18577
18684
  }
18578
18685
  resolvedResults = Array.from(seen.values()).sort((a, b) => b.score - a.score);
18579
18686
  console.error(
18580
- `[graph-search] label-match query="${q}" hits=${labelMatchCount}`
18687
+ `[graph-search] label-match query="${q2}" hits=${labelMatchCount}`
18581
18688
  );
18582
18689
  console.error(
18583
- `[graph-search] body-fulltext query="${q}" hits=${bodyFulltextCount} ms=${total}`
18690
+ `[graph-search] body-fulltext query="${q2}" hits=${bodyFulltextCount} ms=${total}`
18584
18691
  );
18585
18692
  }
18586
18693
  const labelsToken = wildcard ? "*" : noChips ? "all" : labels.join(",");
18587
18694
  const expandedFlag = !wildcard && wantsBodyFulltext ? 1 : 0;
18588
18695
  console.error(
18589
- `[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}`
18590
18697
  );
18591
18698
  console.error(
18592
18699
  `[graph-search] threshold=${res.threshold ?? "off"} raw-merged=${res.rawMerged} suppressed=${res.suppressed} rendered=${res.results.length} bm25-bypass=${res.bm25Bypass}`
@@ -18609,7 +18716,7 @@ app23.get("/", requireAdminSession, async (c) => {
18609
18716
  } catch (err) {
18610
18717
  const elapsed = Date.now() - started;
18611
18718
  const message = err instanceof Error ? err.message : String(err);
18612
- 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}"`);
18613
18720
  return c.json({ error: `Graph search unavailable: ${message}` }, 503);
18614
18721
  } finally {
18615
18722
  await session.close();
@@ -18676,7 +18783,11 @@ var FILTER_TOP_LEVEL_LABELS = Object.freeze(
18676
18783
  "Risk",
18677
18784
  "Source",
18678
18785
  "Finding",
18679
- "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"
18680
18791
  ])
18681
18792
  );
18682
18793
  var AGENT_ACTION_LABELS = Object.freeze(
@@ -18727,6 +18838,17 @@ var STATIC_TOP_LEVEL_LABELS = Object.freeze(
18727
18838
  "Review",
18728
18839
  "ImageObject",
18729
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",
18730
18852
  "Task",
18731
18853
  "Project",
18732
18854
  "Event",
@@ -18777,8 +18899,8 @@ function parseTopLevelLabels(content) {
18777
18899
  const line = lines[i];
18778
18900
  if (line.startsWith("## ")) return [];
18779
18901
  if (!line.includes("|")) continue;
18780
- const sep9 = lines[i + 1];
18781
- 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;
18782
18904
  const headers = splitPipeRow(line).map((h) => stripBackticks(h).toLowerCase());
18783
18905
  const labelCol = headers.findIndex((h) => h === "neo4j label");
18784
18906
  if (labelCol === -1) return [];
@@ -19029,7 +19151,7 @@ async function handleNeighbourhood(c, accountId) {
19029
19151
  }
19030
19152
  const includeAgentActions = c.req.query("includeAgentActions") === "1";
19031
19153
  const agentActionLabels = includeAgentActions ? [] : [...AGENT_ACTION_LABELS];
19032
- const q = (c.req.query("q") ?? "").trim();
19154
+ const q2 = (c.req.query("q") ?? "").trim();
19033
19155
  const rawLimit = c.req.query("limit");
19034
19156
  const parsedLimit = rawLimit ? parseInt(rawLimit, 10) : NEIGHBOURHOOD_SEARCH_DEFAULT_LIMIT;
19035
19157
  const searchLimit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, NEIGHBOURHOOD_SEARCH_MAX_LIMIT) : NEIGHBOURHOOD_SEARCH_DEFAULT_LIMIT;
@@ -19037,9 +19159,9 @@ async function handleNeighbourhood(c, accountId) {
19037
19159
  const session = getSession();
19038
19160
  try {
19039
19161
  let allowedIds = null;
19040
- if (q) {
19162
+ if (q2) {
19041
19163
  const searchRes = await hybrid(session, embed, {
19042
- query: q,
19164
+ query: q2,
19043
19165
  accountId,
19044
19166
  labels: [],
19045
19167
  limit: searchLimit,
@@ -19122,7 +19244,7 @@ async function handleNeighbourhood(c, accountId) {
19122
19244
  }
19123
19245
  if (allowedIds !== null) {
19124
19246
  console.error(
19125
- `[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}`
19126
19248
  );
19127
19249
  }
19128
19250
  return c.json({ nodes, edges });
@@ -20463,18 +20585,68 @@ var session_rc_spawn_default = app35;
20463
20585
 
20464
20586
  // server/routes/admin/session-reseat.ts
20465
20587
  import { randomUUID as randomUUID12 } from "crypto";
20466
- 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) {
20467
20633
  const sessionId = mintId();
20468
20634
  const key = `session:${sessionId}`;
20469
20635
  const payload = {
20470
20636
  sessionId,
20471
20637
  forkFromSessionId: body.fromSessionId,
20472
20638
  model: body.model,
20473
- role: "admin",
20474
- channel: "webchat",
20475
- senderId: key,
20476
- webchatChannel: webchatChannelDescriptor(key)
20639
+ role: "admin"
20477
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
+ }
20478
20650
  if (body.name) payload.name = body.name;
20479
20651
  if (body.permissionMode) payload.permissionMode = body.permissionMode;
20480
20652
  if (body.effort) payload.effort = body.effort;
@@ -20515,14 +20687,18 @@ app36.post("/", requireAdminSession, async (c) => {
20515
20687
  }
20516
20688
  const accountId = cacheKey ? getAccountIdForSession(cacheKey) : void 0;
20517
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;
20518
20692
  const plan = resolveReseatPlan(
20519
20693
  { fromSessionId, model, ...permissionMode ? { permissionMode } : {}, ...effort ? { effort } : {} },
20520
20694
  randomUUID12,
20521
20695
  adminUserId,
20522
- authenticatedName
20696
+ authenticatedName,
20697
+ source
20523
20698
  );
20699
+ const isChannelFork = isAdminChannelSource(source);
20524
20700
  const reseatReqId = randomUUID12();
20525
- 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}`);
20526
20702
  let res;
20527
20703
  try {
20528
20704
  res = await fetch(`${managerBase("session-reseat:wrapper")}/rc-spawn`, {
@@ -20562,6 +20738,23 @@ app36.post("/", requireAdminSession, async (c) => {
20562
20738
  } catch (err) {
20563
20739
  console.error(`[session-reseat] canonical-forward-failed err=${err instanceof Error ? err.message : String(err)}`);
20564
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
+ }
20565
20758
  const target = `/chat?session=${plan.sessionId}`;
20566
20759
  console.log(`[chat-reseat] op=acquired reseatReqId=${reseatReqId} pid=${parsed?.spawnedPid ?? "none"} new=${plan.sessionId}`);
20567
20760
  console.log(`[chat-reseat] op=navigate reseatReqId=${reseatReqId} target=${target}`);
@@ -20696,14 +20889,14 @@ app37.get("/", async (c) => {
20696
20889
  var system_stats_default = app37;
20697
20890
 
20698
20891
  // server/routes/admin/health.ts
20699
- import { existsSync as existsSync31, readFileSync as readFileSync34 } from "fs";
20700
- 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";
20701
20894
  var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve26(process.cwd(), "..");
20702
20895
  var brandHostname = "maxy";
20703
- var brandJsonPath = join35(PLATFORM_ROOT6, "config", "brand.json");
20896
+ var brandJsonPath = join36(PLATFORM_ROOT6, "config", "brand.json");
20704
20897
  if (existsSync31(brandJsonPath)) {
20705
20898
  try {
20706
- const brand = JSON.parse(readFileSync34(brandJsonPath, "utf-8"));
20899
+ const brand = JSON.parse(readFileSync35(brandJsonPath, "utf-8"));
20707
20900
  if (brand.hostname) brandHostname = brand.hostname;
20708
20901
  } catch {
20709
20902
  }
@@ -20713,7 +20906,7 @@ var PROCESS_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
20713
20906
  var PROBE_TIMEOUT_MS = 1e3;
20714
20907
  function readVersion() {
20715
20908
  if (!existsSync31(VERSION_FILE)) return "unknown";
20716
- return readFileSync34(VERSION_FILE, "utf-8").trim() || "unknown";
20909
+ return readFileSync35(VERSION_FILE, "utf-8").trim() || "unknown";
20717
20910
  }
20718
20911
  async function probeConversationDb() {
20719
20912
  let session;
@@ -20893,7 +21086,7 @@ function managerLogFollowUrl(sessionId, opts) {
20893
21086
 
20894
21087
  // server/routes/admin/linkedin-ingest.ts
20895
21088
  var TAG31 = "[linkedin-ingest-route]";
20896
- 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}$/;
20897
21090
  var ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
20898
21091
  var LINKEDIN_URL = /^https:\/\/www\.linkedin\.com\//;
20899
21092
  function rejectSchema(reason, missing = []) {
@@ -20907,7 +21100,7 @@ function validate(body) {
20907
21100
  if (b.kind !== "profile" && b.kind !== "thread") missing.push("kind");
20908
21101
  if (typeof b.capturedAt !== "string" || !ISO.test(b.capturedAt)) missing.push("capturedAt");
20909
21102
  if (typeof b.pageUrl !== "string" || !LINKEDIN_URL.test(b.pageUrl)) missing.push("pageUrl");
20910
- 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");
20911
21104
  if (missing.length) return rejectSchema("envelope", missing);
20912
21105
  if (b.kind === "profile") {
20913
21106
  const p = b.profile;
@@ -21490,7 +21683,7 @@ var browser_default = app45;
21490
21683
 
21491
21684
  // server/routes/admin/calendar.ts
21492
21685
  import { existsSync as existsSync32 } from "fs";
21493
- import { join as join37 } from "path";
21686
+ import { join as join38 } from "path";
21494
21687
 
21495
21688
  // server/lib/calendar-ics.ts
21496
21689
  var CRLF = "\r\n";
@@ -21589,8 +21782,8 @@ function countVEvents(ics) {
21589
21782
  }
21590
21783
 
21591
21784
  // server/lib/calendar-availability.ts
21592
- import { readFileSync as readFileSync35 } from "fs";
21593
- import { join as join36 } from "path";
21785
+ import { readFileSync as readFileSync36 } from "fs";
21786
+ import { join as join37 } from "path";
21594
21787
  var AVAILABILITY_FILENAME = "calendar-availability.json";
21595
21788
  var REQUIRED_KEYS = [
21596
21789
  "timezone",
@@ -21599,10 +21792,10 @@ var REQUIRED_KEYS = [
21599
21792
  "weekly"
21600
21793
  ];
21601
21794
  function readAvailabilityConfig(accountDir) {
21602
- const path3 = join36(accountDir, AVAILABILITY_FILENAME);
21795
+ const path3 = join37(accountDir, AVAILABILITY_FILENAME);
21603
21796
  let raw;
21604
21797
  try {
21605
- raw = readFileSync35(path3, "utf-8");
21798
+ raw = readFileSync36(path3, "utf-8");
21606
21799
  } catch {
21607
21800
  throw new Error(`availability not configured: ${path3} not found`);
21608
21801
  }
@@ -21961,7 +22154,7 @@ app46.get("/booking-link", requireAdminSession, (c) => {
21961
22154
  console.error('[calendar] op=booking-link-fail err="no account configured"');
21962
22155
  return c.json({ bookingDomain: null });
21963
22156
  }
21964
- if (!existsSync32(join37(account.accountDir, AVAILABILITY_FILENAME))) {
22157
+ if (!existsSync32(join38(account.accountDir, AVAILABILITY_FILENAME))) {
21965
22158
  console.log("[calendar] op=booking-link bookingDomain=none source=availability-config");
21966
22159
  return c.json({ bookingDomain: null });
21967
22160
  }
@@ -22016,16 +22209,32 @@ app47.get("/", requireAdminSession, async (c) => {
22016
22209
  WHERE te.endedAt IS NULL
22017
22210
  WITH t, te ORDER BY te.startedAt
22018
22211
  WITH t, head(collect(te)) AS openTe
22212
+ // OPTIONAL is load-bearing. Unassigned is the common state, so a plain
22213
+ // MATCH would silently drop most of the list with nothing looking wrong.
22214
+ OPTIONAL MATCH (t)-[:ASSIGNED_TO]->(a:AdminUser)
22215
+ // head(collect(...)) for the same reason the TimeEntry match above uses
22216
+ // it: a second ASSIGNED_TO edge would fan the row out, duplicating
22217
+ // rowKey and double-counting the assignee.
22218
+ WITH t, openTe, head(collect(a)) AS assignee
22019
22219
  RETURN t.taskId AS taskId, elementId(t) AS rowKey, t.name AS name, t.clientName AS clientName,
22020
22220
  t.propertyAddress AS propertyAddress, t.fee AS fee, t.status AS status,
22021
22221
  coalesce(t.secondsLogged, 0) AS secondsLogged,
22022
- openTe.startedAt AS runningStartedAt
22222
+ openTe.startedAt AS runningStartedAt,
22223
+ t.createdAt AS createdAt,
22224
+ assignee.userId AS assigneeUserId, assignee.name AS assigneeName
22023
22225
  ORDER BY t.createdAt DESC`,
22024
22226
  { accountId }
22025
22227
  );
22228
+ const totalResult = await session.run(
22229
+ `MATCH (t:Task {accountId: $accountId}) RETURN count(t) AS total`,
22230
+ { accountId }
22231
+ );
22232
+ const graphTotal = totalResult.records.length > 0 ? toNum(totalResult.records[0].get("total")) : 0;
22026
22233
  const open = [];
22027
22234
  const completed = [];
22028
22235
  let missingTaskId = 0;
22236
+ let missingCreatedAt = 0;
22237
+ let assigned = 0;
22029
22238
  for (const rec of result.records) {
22030
22239
  const status = rec.get("status") ?? "pending";
22031
22240
  const runningStartedAt = rec.get("runningStartedAt") ?? null;
@@ -22033,6 +22242,10 @@ app47.get("/", requireAdminSession, async (c) => {
22033
22242
  const rawTaskId = rec.get("taskId");
22034
22243
  const taskId = typeof rawTaskId === "string" && rawTaskId !== "" ? rawTaskId : null;
22035
22244
  if (taskId === null) missingTaskId++;
22245
+ const createdAt = rec.get("createdAt") ?? null;
22246
+ if (createdAt === null) missingCreatedAt++;
22247
+ const assigneeUserId = rec.get("assigneeUserId") ?? null;
22248
+ if (assigneeUserId !== null) assigned++;
22036
22249
  const row = {
22037
22250
  taskId,
22038
22251
  rowKey: rec.get("rowKey"),
@@ -22043,12 +22256,23 @@ app47.get("/", requireAdminSession, async (c) => {
22043
22256
  status,
22044
22257
  secondsLogged: toNum(rec.get("secondsLogged")),
22045
22258
  running: runningStartedAt !== null,
22046
- runningStartedAt
22259
+ runningStartedAt,
22260
+ createdAt,
22261
+ assigneeUserId,
22262
+ assigneeName: rec.get("assigneeName") ?? null
22047
22263
  };
22048
22264
  if (COMPLETED.has(status)) completed.push(row);
22049
22265
  else if (!HIDDEN_FROM_OPEN.has(status)) open.push(row);
22050
22266
  }
22051
- console.log(`${TAG37} op=list account=${accountId} open=${open.length} completed=${completed.length} missingTaskId=${missingTaskId}`);
22267
+ const returned = result.records.length;
22268
+ if (returned < graphTotal) {
22269
+ console.error(
22270
+ `${TAG37} op=list row-count-shortfall account=${accountId} returned=${returned} graphTotal=${graphTotal} missing=${graphTotal - returned}`
22271
+ );
22272
+ }
22273
+ console.log(
22274
+ `${TAG37} op=list account=${accountId} returned=${returned} graphTotal=${graphTotal} open=${open.length} completed=${completed.length} missingTaskId=${missingTaskId} missingCreatedAt=${missingCreatedAt} assigned=${assigned}`
22275
+ );
22052
22276
  return c.json({ open, completed });
22053
22277
  } catch (err) {
22054
22278
  console.error(`${TAG37} op=list error="${err instanceof Error ? err.message : String(err)}"`);
@@ -22366,7 +22590,13 @@ app52.get("/", requireAdminSession, async (c) => {
22366
22590
  const start = Date.now();
22367
22591
  let res;
22368
22592
  try {
22369
- res = await fetch(`${managerBase("activity:wrapper")}/activity`, {
22593
+ const qs = new URLSearchParams();
22594
+ const from = c.req.query("from");
22595
+ const to = c.req.query("to");
22596
+ if (from) qs.set("from", from);
22597
+ if (to) qs.set("to", to);
22598
+ const suffix = qs.size > 0 ? `?${qs.toString()}` : "";
22599
+ res = await fetch(`${managerBase("activity:wrapper")}/activity${suffix}`, {
22370
22600
  signal: AbortSignal.timeout(3e4)
22371
22601
  });
22372
22602
  } catch (err) {
@@ -22420,6 +22650,11 @@ app52.get("/", requireAdminSession, async (c) => {
22420
22650
  rows,
22421
22651
  census,
22422
22652
  censusAgeMs,
22653
+ // `satisfies` and not a bare literal (Task 1825). LoopsPayload is what the
22654
+ // activity page destructures; without the check, a key renamed or dropped
22655
+ // here still compiles and the page reads undefined, which its own
22656
+ // vocabulary renders as a dash meaning "not measured". The check runs both
22657
+ // ways: a key the page does not know about fails as an excess property.
22423
22658
  loops: {
22424
22659
  armed: armed2,
22425
22660
  rows: loopRows,
@@ -22722,7 +22957,7 @@ app56.route("/request-magic-link", request_magic_link_default);
22722
22957
  var access_default = app56;
22723
22958
 
22724
22959
  // server/routes/sites.ts
22725
- 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";
22726
22961
  import { resolve as resolve28 } from "path";
22727
22962
  var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
22728
22963
  var MIME = {
@@ -22786,13 +23021,13 @@ app57.get("/:rel{.*}", (c) => {
22786
23021
  console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
22787
23022
  return c.text("Forbidden", 403);
22788
23023
  }
22789
- let stat8;
23024
+ let stat9;
22790
23025
  try {
22791
- stat8 = existsSync33(filePath) ? statSync14(filePath) : null;
23026
+ stat9 = existsSync33(filePath) ? statSync14(filePath) : null;
22792
23027
  } catch {
22793
- stat8 = null;
23028
+ stat9 = null;
22794
23029
  }
22795
- if (stat8?.isDirectory() && !reqPath.endsWith("/")) {
23030
+ if (stat9?.isDirectory() && !reqPath.endsWith("/")) {
22796
23031
  const search = new URL(c.req.url).search;
22797
23032
  const target = `${reqPath}/${search}`;
22798
23033
  console.log(
@@ -22800,7 +23035,7 @@ app57.get("/:rel{.*}", (c) => {
22800
23035
  );
22801
23036
  return c.redirect(target, 301);
22802
23037
  }
22803
- if (stat8?.isDirectory()) {
23038
+ if (stat9?.isDirectory()) {
22804
23039
  filePath = resolve28(filePath, "index.html");
22805
23040
  }
22806
23041
  if (!filePath.startsWith(rootDir + "/")) {
@@ -22826,7 +23061,7 @@ app57.get("/:rel{.*}", (c) => {
22826
23061
  }
22827
23062
  let body;
22828
23063
  try {
22829
- body = readFileSync36(realPath);
23064
+ body = readFileSync37(realPath);
22830
23065
  } catch (err) {
22831
23066
  const code = err?.code;
22832
23067
  if (code === "EISDIR") {
@@ -22862,7 +23097,7 @@ var sites_default = app57;
22862
23097
 
22863
23098
  // app/lib/visitor-token.ts
22864
23099
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
22865
- 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";
22866
23101
  import { dirname as dirname12 } from "path";
22867
23102
  var TOKEN_PREFIX = "v1.";
22868
23103
  var SECRET_BYTES = 32;
@@ -22871,7 +23106,7 @@ var cachedSecret = null;
22871
23106
  function getSecret() {
22872
23107
  if (cachedSecret) return cachedSecret;
22873
23108
  try {
22874
- const hex2 = readFileSync37(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
23109
+ const hex2 = readFileSync38(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
22875
23110
  if (hex2.length === SECRET_BYTES * 2) {
22876
23111
  cachedSecret = Buffer.from(hex2, "hex");
22877
23112
  return cachedSecret;
@@ -22881,11 +23116,11 @@ function getSecret() {
22881
23116
  const fresh = randomBytes(SECRET_BYTES).toString("hex");
22882
23117
  try {
22883
23118
  mkdirSync8(dirname12(VISITOR_TOKEN_SECRET_FILE), { recursive: true, mode: 448 });
22884
- writeFileSync12(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
23119
+ writeFileSync13(VISITOR_TOKEN_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
22885
23120
  console.log(`[visitor-token] secret minted path=${VISITOR_TOKEN_SECRET_FILE}`);
22886
23121
  } catch {
22887
23122
  }
22888
- const hex = readFileSync37(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
23123
+ const hex = readFileSync38(VISITOR_TOKEN_SECRET_FILE, "utf-8").trim();
22889
23124
  cachedSecret = Buffer.from(hex, "hex");
22890
23125
  return cachedSecret;
22891
23126
  }
@@ -22938,8 +23173,8 @@ var VISITOR_COOKIE_NAME = "mxy_v";
22938
23173
  var VISITOR_COOKIE_MAX_AGE_SECONDS = Math.floor(DEFAULT_TTL_MS / 1e3);
22939
23174
 
22940
23175
  // app/lib/brand-config.ts
22941
- import { existsSync as existsSync34, readFileSync as readFileSync38 } from "fs";
22942
- import { join as join38 } from "path";
23176
+ import { existsSync as existsSync34, readFileSync as readFileSync39 } from "fs";
23177
+ import { join as join39 } from "path";
22943
23178
  var cached2 = null;
22944
23179
  var cachedAttempted = false;
22945
23180
  function readBrandConfig() {
@@ -22947,10 +23182,10 @@ function readBrandConfig() {
22947
23182
  cachedAttempted = true;
22948
23183
  const platformRoot5 = process.env.MAXY_PLATFORM_ROOT;
22949
23184
  if (!platformRoot5) return null;
22950
- const brandPath = join38(platformRoot5, "config", "brand.json");
23185
+ const brandPath = join39(platformRoot5, "config", "brand.json");
22951
23186
  if (!existsSync34(brandPath)) return null;
22952
23187
  try {
22953
- cached2 = JSON.parse(readFileSync38(brandPath, "utf-8"));
23188
+ cached2 = JSON.parse(readFileSync39(brandPath, "utf-8"));
22954
23189
  return cached2;
22955
23190
  } catch {
22956
23191
  return null;
@@ -23084,8 +23319,8 @@ function appendConsentParams(pageUrl, token) {
23084
23319
  const hashIdx = pageUrl.indexOf("#");
23085
23320
  const hash = hashIdx >= 0 ? pageUrl.slice(hashIdx) : "";
23086
23321
  const base = hashIdx >= 0 ? pageUrl.slice(0, hashIdx) : pageUrl;
23087
- const sep9 = base.indexOf("?") >= 0 ? "&" : "?";
23088
- 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;
23089
23324
  }
23090
23325
  }
23091
23326
  var SAFE_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$/;
@@ -23448,13 +23683,13 @@ var visitor_event_default = app60;
23448
23683
 
23449
23684
  // server/routes/session.ts
23450
23685
  import { resolve as resolve29 } from "path";
23451
- 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";
23452
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;
23453
23688
  function writeBrandingCache(accountId, agentSlug, branding) {
23454
23689
  try {
23455
23690
  const cacheDir = resolve29(MAXY_DIR, "branding-cache", accountId);
23456
23691
  mkdirSync9(cacheDir, { recursive: true });
23457
- writeFileSync13(resolve29(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
23692
+ writeFileSync14(resolve29(cacheDir, `${agentSlug}.json`), JSON.stringify(branding), "utf-8");
23458
23693
  } catch (err) {
23459
23694
  console.error(`[branding] cache write failed: ${err instanceof Error ? err.message : String(err)}`);
23460
23695
  }
@@ -23779,8 +24014,205 @@ app62.get("/free-busy", async (c) => {
23779
24014
  });
23780
24015
  var calendar_public_default = app62;
23781
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
+
23782
24214
  // app/lib/timeentry-census.ts
23783
- var TAG45 = "[timeentry-census]";
24215
+ var TAG46 = "[timeentry-census]";
23784
24216
  function reconcileTimeEntries(rows, now, horizonMs) {
23785
24217
  const openEntries = rows.openEntries.length;
23786
24218
  let oldestOpenAgeMs = 0;
@@ -23820,12 +24252,12 @@ async function runTimeEntryCensusOnce(session, now, horizonMs) {
23820
24252
  sumAdjustments: toNum(r.get("sumAdjustments"))
23821
24253
  }));
23822
24254
  const finding = reconcileTimeEntries({ openEntries, taskSums }, now, horizonMs);
23823
- 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}`;
23824
24256
  if (finding.alarm) console.error(`${line} alarm=true`);
23825
24257
  else console.log(line);
23826
24258
  return finding;
23827
24259
  } catch (err) {
23828
- console.error(`${TAG45} error="${err instanceof Error ? err.message : String(err)}"`);
24260
+ console.error(`${TAG46} error="${err instanceof Error ? err.message : String(err)}"`);
23829
24261
  return null;
23830
24262
  }
23831
24263
  }
@@ -23839,13 +24271,13 @@ function startTimeEntryCensus(openSession, opts = {}) {
23839
24271
  try {
23840
24272
  session = openSession();
23841
24273
  } catch (err) {
23842
- 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)}"`);
23843
24275
  return;
23844
24276
  }
23845
24277
  try {
23846
24278
  await runTimeEntryCensusOnce(session, Date.now(), horizonMs);
23847
24279
  } catch (err) {
23848
- 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)}"`);
23849
24281
  } finally {
23850
24282
  try {
23851
24283
  await session.close();
@@ -23862,8 +24294,8 @@ function startTimeEntryCensus(openSession, opts = {}) {
23862
24294
  }
23863
24295
 
23864
24296
  // app/lib/ledger-census.ts
23865
- import { readdirSync as readdirSync25, readFileSync as readFileSync39, statSync as statSync15 } from "fs";
23866
- 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";
23867
24299
 
23868
24300
  // ../lib/ledger-core/dist/reconcile.js
23869
24301
  var LEDGER_INDEX_NAMES = [
@@ -23906,7 +24338,7 @@ function formatCensusLine(f) {
23906
24338
  }
23907
24339
 
23908
24340
  // app/lib/ledger-census.ts
23909
- var TAG46 = "[ledger-census]";
24341
+ var TAG47 = "[ledger-census]";
23910
24342
  var DAY_MS = 864e5;
23911
24343
  function countWriteRejects24h(logDir, now) {
23912
24344
  let entries;
@@ -23917,10 +24349,10 @@ function countWriteRejects24h(logDir, now) {
23917
24349
  }
23918
24350
  let total = 0;
23919
24351
  for (const name of entries) {
23920
- const path3 = join39(logDir, name);
24352
+ const path3 = join41(logDir, name);
23921
24353
  try {
23922
24354
  if (now - statSync15(path3).mtimeMs > DAY_MS) continue;
23923
- for (const line of readFileSync39(path3, "utf8").split("\n")) {
24355
+ for (const line of readFileSync40(path3, "utf8").split("\n")) {
23924
24356
  if (line.includes("[graph-write] reject")) total += 1;
23925
24357
  }
23926
24358
  } catch {
@@ -23982,7 +24414,7 @@ async function runLedgerCensusOnce(session, now) {
23982
24414
  else console.log(line);
23983
24415
  return finding;
23984
24416
  } catch (err) {
23985
- console.error(`${TAG46} error="${err instanceof Error ? err.message : String(err)}"`);
24417
+ console.error(`${TAG47} error="${err instanceof Error ? err.message : String(err)}"`);
23986
24418
  return null;
23987
24419
  }
23988
24420
  }
@@ -23995,7 +24427,7 @@ function startLedgerCensus(openSession, opts = {}) {
23995
24427
  session = openSession();
23996
24428
  } catch (err) {
23997
24429
  console.error(
23998
- `${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)}"`
23999
24431
  );
24000
24432
  return;
24001
24433
  }
@@ -24003,7 +24435,7 @@ function startLedgerCensus(openSession, opts = {}) {
24003
24435
  await runLedgerCensusOnce(session, Date.now());
24004
24436
  } catch (err) {
24005
24437
  console.error(
24006
- `${TAG46} tick-failed error="${err instanceof Error ? err.message : String(err)}"`
24438
+ `${TAG47} tick-failed error="${err instanceof Error ? err.message : String(err)}"`
24007
24439
  );
24008
24440
  } finally {
24009
24441
  try {
@@ -24021,7 +24453,7 @@ function startLedgerCensus(openSession, opts = {}) {
24021
24453
  }
24022
24454
 
24023
24455
  // app/lib/conversation-audit.ts
24024
- var TAG47 = "[conversation-audit]";
24456
+ var TAG48 = "[conversation-audit]";
24025
24457
  async function runConversationAudit() {
24026
24458
  const session = getSession();
24027
24459
  try {
@@ -24047,12 +24479,12 @@ async function runConversationAudit() {
24047
24479
  unpairable: Number(rec?.get("unpairable") ?? 0)
24048
24480
  };
24049
24481
  console.error(
24050
- `${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}`
24051
24483
  );
24052
24484
  return result;
24053
24485
  } catch (err) {
24054
24486
  const reason = err instanceof Error ? `${err.name}:${err.message.slice(0, 200)}` : String(err).slice(0, 200);
24055
- console.error(`${TAG47} op=failed reason=${reason}`);
24487
+ console.error(`${TAG48} op=failed reason=${reason}`);
24056
24488
  return null;
24057
24489
  } finally {
24058
24490
  await session.close();
@@ -24178,13 +24610,13 @@ function startGraphHealthTimer() {
24178
24610
 
24179
24611
  // app/lib/file-watcher.ts
24180
24612
  import * as fsp2 from "fs/promises";
24181
- import { resolve as resolve30, sep as sep8 } from "path";
24613
+ import { resolve as resolve31, sep as sep9 } from "path";
24182
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;
24183
24615
  var DEFAULT_COALESCE_MS = 500;
24184
24616
  var ROOTS = ["accounts"];
24185
24617
  function _routeEvent(rootName, filename) {
24186
24618
  if (!filename) return null;
24187
- const segs = filename.split(sep8).filter(Boolean);
24619
+ const segs = filename.split(sep9).filter(Boolean);
24188
24620
  if (segs.length < 2) return null;
24189
24621
  const accountId = segs[0];
24190
24622
  if (!ACCOUNT_UUID_RE4.test(accountId)) return null;
@@ -24197,7 +24629,7 @@ async function startFileWatcher(opts = {}) {
24197
24629
  const dropFn = opts.drop ?? dropFileIndex;
24198
24630
  for (const r of ROOTS) {
24199
24631
  try {
24200
- await fsp2.mkdir(resolve30(dataRoot, r), { recursive: true });
24632
+ await fsp2.mkdir(resolve31(dataRoot, r), { recursive: true });
24201
24633
  } catch (err) {
24202
24634
  console.error(
24203
24635
  `[file-watcher] start-failed root="${r}" err="${err.message}" \u2014 index will be maintained by the 5-min reconcile backstop only`
@@ -24218,7 +24650,7 @@ async function startFileWatcher(opts = {}) {
24218
24650
  timers.set(relativePath, t);
24219
24651
  }
24220
24652
  async function runHook(relativePath, accountId) {
24221
- const absolute = resolve30(dataRoot, relativePath);
24653
+ const absolute = resolve31(dataRoot, relativePath);
24222
24654
  let exists = false;
24223
24655
  try {
24224
24656
  const st = await fsp2.stat(absolute);
@@ -24242,7 +24674,7 @@ async function startFileWatcher(opts = {}) {
24242
24674
  }
24243
24675
  }
24244
24676
  async function watchRoot(rootName) {
24245
- const absRoot = resolve30(dataRoot, rootName);
24677
+ const absRoot = resolve31(dataRoot, rootName);
24246
24678
  try {
24247
24679
  const iter = fsp2.watch(absRoot, { recursive: true, signal: controller.signal });
24248
24680
  for await (const event of iter) {
@@ -24281,7 +24713,7 @@ async function startFileWatcher(opts = {}) {
24281
24713
 
24282
24714
  // app/lib/migrate-uploads.ts
24283
24715
  import { mkdir as mkdir5, readdir as readdir5, rename as rename2, rm as rm3 } from "fs/promises";
24284
- 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";
24285
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;
24286
24718
  async function walkFiles(dir) {
24287
24719
  const out = [];
@@ -24292,7 +24724,7 @@ async function walkFiles(dir) {
24292
24724
  return out;
24293
24725
  }
24294
24726
  for (const e of entries) {
24295
- const abs = resolve31(dir, e.name);
24727
+ const abs = resolve32(dir, e.name);
24296
24728
  if (e.isDirectory()) {
24297
24729
  out.push(...await walkFiles(abs));
24298
24730
  } else if (e.isFile()) {
@@ -24312,7 +24744,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
24312
24744
  let moved = 0;
24313
24745
  for (const oldAbs of files) {
24314
24746
  const suffix = relative6(srcDir, oldAbs);
24315
- const newAbs = resolve31(destDir, suffix);
24747
+ const newAbs = resolve32(destDir, suffix);
24316
24748
  await mkdir5(dirname13(newAbs), { recursive: true });
24317
24749
  await rename2(oldAbs, newAbs);
24318
24750
  moved++;
@@ -24339,7 +24771,7 @@ async function relocateTree(srcDir, destDir, dataRoot, accountId, session) {
24339
24771
  }
24340
24772
  async function migrateUploads(opts = {}) {
24341
24773
  const dataRoot = opts.dataRoot ?? DATA_ROOT;
24342
- const oldRoot = resolve31(dataRoot, "uploads");
24774
+ const oldRoot = resolve32(dataRoot, "uploads");
24343
24775
  let topEntries;
24344
24776
  try {
24345
24777
  topEntries = await readdir5(oldRoot, { withFileTypes: true });
@@ -24360,8 +24792,8 @@ async function migrateUploads(opts = {}) {
24360
24792
  const name = entry.name;
24361
24793
  if (ACCOUNT_UUID_RE5.test(name)) {
24362
24794
  moved += await relocateTree(
24363
- resolve31(oldRoot, name),
24364
- resolve31(dataRoot, "accounts", name, "uploads"),
24795
+ resolve32(oldRoot, name),
24796
+ resolve32(dataRoot, "accounts", name, "uploads"),
24365
24797
  dataRoot,
24366
24798
  name,
24367
24799
  session
@@ -24373,8 +24805,8 @@ async function migrateUploads(opts = {}) {
24373
24805
  continue;
24374
24806
  }
24375
24807
  moved += await relocateTree(
24376
- resolve31(oldRoot, "public"),
24377
- resolve31(dataRoot, "accounts", installAccountId, "uploads", "public"),
24808
+ resolve32(oldRoot, "public"),
24809
+ resolve32(dataRoot, "accounts", installAccountId, "uploads", "public"),
24378
24810
  dataRoot,
24379
24811
  null,
24380
24812
  // public uploads carry no graph nodes
@@ -24403,9 +24835,9 @@ async function migrateUploads(opts = {}) {
24403
24835
  }
24404
24836
 
24405
24837
  // app/lib/migrate-admin-webchat-sidecars.ts
24406
- 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";
24407
24839
  import { existsSync as existsSync36 } from "fs";
24408
- import { join as join40 } from "path";
24840
+ import { join as join42 } from "path";
24409
24841
  var ADMIN_ROLE2 = "admin";
24410
24842
  var WEBCHAT_CHANNEL2 = "webchat";
24411
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;
@@ -24413,10 +24845,10 @@ var defaultResolveName = async (_accountId, userId) => {
24413
24845
  const res = await loadAdminUserName(userId);
24414
24846
  return res.source === "neo4j" && res.joined.length > 0 ? res.joined : null;
24415
24847
  };
24416
- async function readRaw2(path3) {
24848
+ async function readRaw3(path3) {
24417
24849
  let raw;
24418
24850
  try {
24419
- raw = await readFile6(path3, "utf8");
24851
+ raw = await readFile7(path3, "utf8");
24420
24852
  } catch {
24421
24853
  return null;
24422
24854
  }
@@ -24450,7 +24882,7 @@ async function collectLiveSidecars(projectsRoot) {
24450
24882
  }
24451
24883
  for (const slug of slugs) {
24452
24884
  if (!slug.isDirectory()) continue;
24453
- const slugDir = join40(projectsRoot, slug.name);
24885
+ const slugDir = join42(projectsRoot, slug.name);
24454
24886
  let entries;
24455
24887
  try {
24456
24888
  entries = await readdir6(slugDir, { withFileTypes: true });
@@ -24459,9 +24891,9 @@ async function collectLiveSidecars(projectsRoot) {
24459
24891
  }
24460
24892
  for (const entry of entries) {
24461
24893
  if (entry.isFile() && SESSION_META_RE.test(entry.name)) {
24462
- out.push(join40(slugDir, entry.name));
24894
+ out.push(join42(slugDir, entry.name));
24463
24895
  } else if (entry.isDirectory() && entry.name === "subagents") {
24464
- const subDir = join40(slugDir, entry.name);
24896
+ const subDir = join42(slugDir, entry.name);
24465
24897
  let subs;
24466
24898
  try {
24467
24899
  subs = await readdir6(subDir, { withFileTypes: true });
@@ -24469,7 +24901,7 @@ async function collectLiveSidecars(projectsRoot) {
24469
24901
  continue;
24470
24902
  }
24471
24903
  for (const s of subs) {
24472
- 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));
24473
24905
  }
24474
24906
  }
24475
24907
  }
@@ -24481,7 +24913,7 @@ function shortId(path3) {
24481
24913
  return base.slice(0, 8);
24482
24914
  }
24483
24915
  async function migrateAdminWebchatSidecars(opts = {}) {
24484
- 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) ?? "";
24485
24917
  const usersFile = opts.usersFile ?? USERS_FILE;
24486
24918
  const accountId = opts.accountId ?? resolveAccount()?.accountId ?? null;
24487
24919
  const resolveName = opts.resolveName ?? defaultResolveName;
@@ -24521,7 +24953,7 @@ async function migrateAdminWebchatSidecars(opts = {}) {
24521
24953
  };
24522
24954
  const sidecars = await collectLiveSidecars(projectsRoot);
24523
24955
  for (const path3 of sidecars) {
24524
- const raw = await readRaw2(path3);
24956
+ const raw = await readRaw3(path3);
24525
24957
  if (!raw) continue;
24526
24958
  if (raw.role !== ADMIN_ROLE2 || raw.channel !== WEBCHAT_CHANNEL2) continue;
24527
24959
  result.scanned++;
@@ -24539,7 +24971,7 @@ async function migrateAdminWebchatSidecars(opts = {}) {
24539
24971
  await writeRaw(path3, raw);
24540
24972
  result.backfilled++;
24541
24973
  await resolveAdminName();
24542
- const back = await readRaw2(path3);
24974
+ const back = await readRaw3(path3);
24543
24975
  const wrote = back && back.adminUserId === soleAdminUserId;
24544
24976
  if (wrote && resolvedNameForAdmin) {
24545
24977
  result.resolvedName++;
@@ -24826,8 +25258,8 @@ var streamSSE = (c, cb, onError) => {
24826
25258
 
24827
25259
  // app/lib/whatsapp/gateway/routes.ts
24828
25260
  function createWaChannelRoutes(deps) {
24829
- const app64 = new Hono();
24830
- app64.get("/wa-channel/inbound", (c) => {
25261
+ const app65 = new Hono();
25262
+ app65.get("/wa-channel/inbound", (c) => {
24831
25263
  const senderId = c.req.query("senderId");
24832
25264
  if (!senderId) return c.json({ error: "senderId required" }, 400);
24833
25265
  return streamSSE(c, async (stream2) => {
@@ -24835,9 +25267,11 @@ function createWaChannelRoutes(deps) {
24835
25267
  void stream2.writeSSE({ data: JSON.stringify(payload) });
24836
25268
  });
24837
25269
  console.error(`[whatsapp-native] op=channel-attached senderId=${senderId}`);
25270
+ deps.onAttached?.(senderId);
24838
25271
  stream2.onAbort(() => {
24839
25272
  deps.hub.detach(senderId);
24840
25273
  console.error(`[whatsapp-native] op=channel-detached senderId=${senderId}`);
25274
+ deps.onDetached?.(senderId);
24841
25275
  });
24842
25276
  while (!stream2.aborted) {
24843
25277
  await stream2.sleep(15e3);
@@ -24845,7 +25279,7 @@ function createWaChannelRoutes(deps) {
24845
25279
  }
24846
25280
  });
24847
25281
  });
24848
- app64.post("/wa-channel/reply", async (c) => {
25282
+ app65.post("/wa-channel/reply", async (c) => {
24849
25283
  const body = await c.req.json().catch(() => null);
24850
25284
  const senderId = body?.senderId;
24851
25285
  const text = body?.text;
@@ -24866,7 +25300,7 @@ function createWaChannelRoutes(deps) {
24866
25300
  console.error(`[whatsapp-native] op=reply-dispatch senderId=${senderId} bytes=${bytes}`);
24867
25301
  return c.json({ ok: true });
24868
25302
  });
24869
- app64.post("/wa-channel/reply-document", async (c) => {
25303
+ app65.post("/wa-channel/reply-document", async (c) => {
24870
25304
  const body = await c.req.json().catch(() => null);
24871
25305
  const senderId = body?.senderId;
24872
25306
  const files = body?.files;
@@ -24905,7 +25339,7 @@ function createWaChannelRoutes(deps) {
24905
25339
  }
24906
25340
  return c.json({ ok: true, results });
24907
25341
  });
24908
- app64.post("/wa-channel/ready", async (c) => {
25342
+ app65.post("/wa-channel/ready", async (c) => {
24909
25343
  const body = await c.req.json().catch(() => null);
24910
25344
  const senderId = body?.senderId;
24911
25345
  if (typeof senderId !== "string") {
@@ -24914,7 +25348,7 @@ function createWaChannelRoutes(deps) {
24914
25348
  deps.onReady?.(senderId);
24915
25349
  return c.json({ ok: true });
24916
25350
  });
24917
- app64.post("/wa-channel/received", async (c) => {
25351
+ app65.post("/wa-channel/received", async (c) => {
24918
25352
  const body = await c.req.json().catch(() => null);
24919
25353
  const senderId = body?.senderId;
24920
25354
  const waMessageId = body?.waMessageId;
@@ -24924,7 +25358,7 @@ function createWaChannelRoutes(deps) {
24924
25358
  deps.onReceived?.(senderId, waMessageId);
24925
25359
  return c.json({ ok: true });
24926
25360
  });
24927
- app64.post("/wa-channel/turn-end", async (c) => {
25361
+ app65.post("/wa-channel/turn-end", async (c) => {
24928
25362
  const body = await c.req.json().catch(() => null);
24929
25363
  const senderId = body?.senderId;
24930
25364
  const sessionId = body?.sessionId;
@@ -24957,7 +25391,7 @@ function createWaChannelRoutes(deps) {
24957
25391
  console.error(`[whatsapp-native] op=turn-end sessionId=${sid} replied=no delivered=none`);
24958
25392
  return c.json({ ok: true, delivered: "none" });
24959
25393
  });
24960
- return app64;
25394
+ return app65;
24961
25395
  }
24962
25396
 
24963
25397
  // app/lib/whatsapp/gateway/wa-gateway.ts
@@ -24979,9 +25413,30 @@ var WaGateway = class {
24979
25413
  spawning = /* @__PURE__ */ new Set();
24980
25414
  seq = 0;
24981
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
+ }
24982
25435
  routes() {
24983
25436
  return createWaChannelRoutes({
24984
25437
  hub: this.hub,
25438
+ onAttached: (senderId) => this.recordAttached(senderId),
25439
+ onDetached: (senderId) => this.recordDetached(senderId, Date.now()),
24985
25440
  sendOutbound: (senderId, text) => this.sendOutbound(senderId, text),
24986
25441
  sendDocument: (senderId, filePath, caption) => this.sendDocument(senderId, filePath, caption),
24987
25442
  onReady: (senderId) => {
@@ -25092,7 +25547,7 @@ var WaGateway = class {
25092
25547
  },
25093
25548
  Date.now()
25094
25549
  );
25095
- const resolvedSessionId = adminSessionIdFor(
25550
+ const resolvedSessionId = (input.role === "admin" ? this.deps.resolveSessionOverride?.(input.effectiveAccountId, senderId) : null) || adminSessionIdFor(
25096
25551
  input.effectiveAccountId,
25097
25552
  senderId,
25098
25553
  input.role === "public" && input.personId ? input.personId : void 0
@@ -25160,17 +25615,28 @@ function logDuplicateSenderGroups(groups) {
25160
25615
  );
25161
25616
  }
25162
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
+ }
25163
25624
 
25164
25625
  // app/lib/whatsapp/gateway/spawn-request.ts
25165
25626
  function buildWaSpawnRequest(input) {
25166
25627
  const personId = input.role === "public" && input.personId ? input.personId : void 0;
25167
25628
  return {
25168
- // Task 1746 — the deterministic per-sender id is the ONLY id a sender's
25169
- // session ever has. Task 1526's per-firing seat override used to replace it
25170
- // on a scheduled cold start, which spawned a second session for the sender;
25171
- // that session then attached under the sender's own hub key and swallowed
25172
- // their real messages until it detached, splitting the conversation in two.
25173
- 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),
25174
25640
  role: input.role,
25175
25641
  channel: "whatsapp",
25176
25642
  personId,
@@ -25354,8 +25820,8 @@ var import_dist7 = __toESM(require_dist(), 1);
25354
25820
 
25355
25821
  // app/lib/webchat/gateway/routes.ts
25356
25822
  function createWebchatChannelRoutes(deps) {
25357
- const app64 = new Hono();
25358
- app64.get("/webchat-channel/inbound", (c) => {
25823
+ const app65 = new Hono();
25824
+ app65.get("/webchat-channel/inbound", (c) => {
25359
25825
  const key = c.req.query("key");
25360
25826
  if (!key) return c.json({ error: "key required" }, 400);
25361
25827
  return streamSSE(c, async (stream2) => {
@@ -25373,7 +25839,7 @@ function createWebchatChannelRoutes(deps) {
25373
25839
  }
25374
25840
  });
25375
25841
  });
25376
- app64.post("/webchat-channel/reply", async (c) => {
25842
+ app65.post("/webchat-channel/reply", async (c) => {
25377
25843
  const body = await c.req.json().catch(() => null);
25378
25844
  const key = body?.key;
25379
25845
  const text = body?.text;
@@ -25383,7 +25849,7 @@ function createWebchatChannelRoutes(deps) {
25383
25849
  deps.onReply?.(key, text);
25384
25850
  return c.json({ ok: true });
25385
25851
  });
25386
- app64.post("/webchat-channel/connector-auth", async (c) => {
25852
+ app65.post("/webchat-channel/connector-auth", async (c) => {
25387
25853
  const body = await c.req.json().catch(() => null);
25388
25854
  const key = body?.key;
25389
25855
  const sessionId = body?.sessionId;
@@ -25396,7 +25862,7 @@ function createWebchatChannelRoutes(deps) {
25396
25862
  const result = await deps.onConnectorAuth({ key, sessionId, name, completed });
25397
25863
  return c.json(result);
25398
25864
  });
25399
- app64.post("/webchat-channel/ready", async (c) => {
25865
+ app65.post("/webchat-channel/ready", async (c) => {
25400
25866
  const body = await c.req.json().catch(() => null);
25401
25867
  const key = body?.key;
25402
25868
  if (typeof key !== "string") {
@@ -25405,7 +25871,7 @@ function createWebchatChannelRoutes(deps) {
25405
25871
  deps.onReady?.(key);
25406
25872
  return c.json({ ok: true });
25407
25873
  });
25408
- app64.post("/webchat-channel/permission-request", async (c) => {
25874
+ app65.post("/webchat-channel/permission-request", async (c) => {
25409
25875
  const body = await c.req.json().catch(() => null);
25410
25876
  const key = body?.key;
25411
25877
  const requestId = body?.request_id;
@@ -25419,7 +25885,7 @@ function createWebchatChannelRoutes(deps) {
25419
25885
  const verdict = await deps.awaitPermissionVerdict({ key, requestId, toolName, description, inputPreview });
25420
25886
  return c.json(verdict);
25421
25887
  });
25422
- app64.post("/webchat-channel/received", async (c) => {
25888
+ app65.post("/webchat-channel/received", async (c) => {
25423
25889
  const body = await c.req.json().catch(() => null);
25424
25890
  const key = body?.key;
25425
25891
  const messageId = body?.messageId;
@@ -25429,7 +25895,7 @@ function createWebchatChannelRoutes(deps) {
25429
25895
  deps.onReceived?.(key, messageId);
25430
25896
  return c.json({ ok: true });
25431
25897
  });
25432
- app64.post("/webchat-channel/turn-end", async (c) => {
25898
+ app65.post("/webchat-channel/turn-end", async (c) => {
25433
25899
  const body = await c.req.json().catch(() => null);
25434
25900
  const key = body?.key;
25435
25901
  const sessionId = body?.sessionId;
@@ -25452,7 +25918,7 @@ function createWebchatChannelRoutes(deps) {
25452
25918
  console.error(`[webchat-native] op=turn-end channel=webchat key=${k} sessionId=${sid} replied=no delivered=none`);
25453
25919
  return c.json({ ok: true, delivered: "none" });
25454
25920
  });
25455
- return app64;
25921
+ return app65;
25456
25922
  }
25457
25923
 
25458
25924
  // app/lib/webchat/gateway/webchat-gateway.ts
@@ -25510,16 +25976,16 @@ var WebchatGateway = class _WebchatGateway {
25510
25976
  * The public /api/chat route awaits this to return the reply on the same SSE
25511
25977
  * response, preserving the pre-756 POST→single-blob browser contract. */
25512
25978
  awaitReply(key, timeoutMs) {
25513
- return new Promise((resolve37) => {
25979
+ return new Promise((resolve38) => {
25514
25980
  const timer = setTimeout(() => {
25515
25981
  this.replyAwaiters.delete(key);
25516
- resolve37({ timeout: true });
25982
+ resolve38({ timeout: true });
25517
25983
  }, timeoutMs);
25518
25984
  if (timer.unref) timer.unref();
25519
25985
  this.replyAwaiters.set(key, (r) => {
25520
25986
  clearTimeout(timer);
25521
25987
  this.replyAwaiters.delete(key);
25522
- resolve37(r);
25988
+ resolve38(r);
25523
25989
  });
25524
25990
  });
25525
25991
  }
@@ -25530,11 +25996,11 @@ var WebchatGateway = class _WebchatGateway {
25530
25996
  * terminal dialog stays answerable). */
25531
25997
  awaitPermissionVerdict(p, timeoutMs) {
25532
25998
  const k = _WebchatGateway.promptKey(p.key, p.requestId);
25533
- return new Promise((resolve37) => {
25999
+ return new Promise((resolve38) => {
25534
26000
  this.pendingPrompts.get(k)?.resolve({ timeout: true });
25535
26001
  const timer = setTimeout(() => {
25536
26002
  this.pendingPrompts.delete(k);
25537
- resolve37({ timeout: true });
26003
+ resolve38({ timeout: true });
25538
26004
  }, timeoutMs);
25539
26005
  if (timer.unref) timer.unref();
25540
26006
  this.pendingPrompts.set(k, {
@@ -25543,7 +26009,7 @@ var WebchatGateway = class _WebchatGateway {
25543
26009
  resolve: (r) => {
25544
26010
  clearTimeout(timer);
25545
26011
  this.pendingPrompts.delete(k);
25546
- resolve37(r);
26012
+ resolve38(r);
25547
26013
  }
25548
26014
  });
25549
26015
  console.error(`[webchat:perm] op=open key=${keyDisplay(p.key)} id=${p.requestId} tool=${p.toolName}`);
@@ -26108,7 +26574,7 @@ async function fanOut(subscribers, text, onError, tag) {
26108
26574
 
26109
26575
  // app/lib/webchat/file-delivery.ts
26110
26576
  import { realpathSync as realpathSync8 } from "fs";
26111
- import { resolve as resolve32 } from "path";
26577
+ import { resolve as resolve33 } from "path";
26112
26578
 
26113
26579
  // app/lib/channel-pty-bridge/file-delivery.ts
26114
26580
  var SEND_USER_FILE = "SendUserFile";
@@ -26199,29 +26665,29 @@ function makeFileDelivery(opts) {
26199
26665
  }
26200
26666
 
26201
26667
  // app/lib/webchat/file-delivery.ts
26202
- var TAG48 = "[webchat-adaptor]";
26668
+ var TAG49 = "[webchat-adaptor]";
26203
26669
  function platformRoot2() {
26204
26670
  return process.env.MAXY_PLATFORM_ROOT || "";
26205
26671
  }
26206
26672
  function makeWebchatSendFile(entry) {
26207
26673
  return async (filePath) => {
26208
26674
  if (!entry.accountId) {
26209
- 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}`);
26210
26676
  return { ok: false, error: "no-account" };
26211
26677
  }
26212
- const accountDir = resolve32(platformRoot2(), "..", "data/accounts", entry.accountId);
26678
+ const accountDir = resolve33(platformRoot2(), "..", "data/accounts", entry.accountId);
26213
26679
  try {
26214
26680
  const resolved = realpathSync8(filePath);
26215
26681
  const accountResolved = realpathSync8(accountDir);
26216
26682
  if (!resolved.startsWith(accountResolved + "/")) {
26217
- 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}`);
26218
26684
  return { ok: false, error: "outside-account" };
26219
26685
  }
26220
26686
  return { ok: true };
26221
26687
  } catch (err) {
26222
26688
  const code = err.code;
26223
26689
  console.error(
26224
- `${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}`
26225
26691
  );
26226
26692
  return { ok: false, error: code === "ENOENT" ? "not-found" : "path-error" };
26227
26693
  }
@@ -26230,7 +26696,7 @@ function makeWebchatSendFile(entry) {
26230
26696
  function makeWebchatFileDelivery(entry) {
26231
26697
  return makeFileDelivery({
26232
26698
  entry,
26233
- tag: TAG48,
26699
+ tag: TAG49,
26234
26700
  channel: "webchat",
26235
26701
  sendFile: makeWebchatSendFile(entry),
26236
26702
  deferUntilVerdict: true
@@ -26292,7 +26758,7 @@ function buildPublicWebchatSpawnRequest(input) {
26292
26758
  }
26293
26759
 
26294
26760
  // app/lib/whatsapp/inbound/file-delivery-bridge.ts
26295
- var TAG49 = "[whatsapp-adaptor]";
26761
+ var TAG50 = "[whatsapp-adaptor]";
26296
26762
  var SEND_USER_FILE2 = "SendUserFile";
26297
26763
  var WHATSAPP_SEND_DOCUMENT = "whatsapp-send-document";
26298
26764
  function platformRoot3() {
@@ -26310,7 +26776,7 @@ function makeWhatsAppSendFile(entry, maxyAccountId) {
26310
26776
  });
26311
26777
  if (result.ok) return { ok: true };
26312
26778
  console.error(
26313
- `${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}`
26314
26780
  );
26315
26781
  return { ok: false, error: result.error };
26316
26782
  };
@@ -26318,7 +26784,7 @@ function makeWhatsAppSendFile(entry, maxyAccountId) {
26318
26784
  function makeWhatsAppFileDelivery(entry, maxyAccountId) {
26319
26785
  const shared = makeFileDelivery({
26320
26786
  entry,
26321
- tag: TAG49,
26787
+ tag: TAG50,
26322
26788
  channel: "whatsapp",
26323
26789
  sendFile: makeWhatsAppSendFile(entry, maxyAccountId)
26324
26790
  });
@@ -26353,7 +26819,7 @@ function makeWhatsAppFileDelivery(entry, maxyAccountId) {
26353
26819
  if (!delivered) {
26354
26820
  const fileField = call.filePath !== void 0 ? ` file=${call.filePath}` : "";
26355
26821
  console.error(
26356
- `${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}`
26357
26823
  );
26358
26824
  }
26359
26825
  }
@@ -26539,8 +27005,8 @@ var import_dist8 = __toESM(require_dist(), 1);
26539
27005
 
26540
27006
  // app/lib/telegram/gateway/routes.ts
26541
27007
  function createTelegramChannelRoutes(deps) {
26542
- const app64 = new Hono();
26543
- app64.get("/tg-channel/inbound", (c) => {
27008
+ const app65 = new Hono();
27009
+ app65.get("/tg-channel/inbound", (c) => {
26544
27010
  const key = c.req.query("key");
26545
27011
  if (!key) return c.json({ error: "key required" }, 400);
26546
27012
  return streamSSE(c, async (stream2) => {
@@ -26558,7 +27024,7 @@ function createTelegramChannelRoutes(deps) {
26558
27024
  }
26559
27025
  });
26560
27026
  });
26561
- app64.post("/tg-channel/reply", async (c) => {
27027
+ app65.post("/tg-channel/reply", async (c) => {
26562
27028
  const body = await c.req.json().catch(() => null);
26563
27029
  const key = body?.key;
26564
27030
  const text = body?.text;
@@ -26574,7 +27040,7 @@ function createTelegramChannelRoutes(deps) {
26574
27040
  console.error(`[telegram-native] op=reply key=${key} bytes=${Buffer.byteLength(text, "utf8")}`);
26575
27041
  return c.json({ ok: true });
26576
27042
  });
26577
- app64.post("/tg-channel/ready", async (c) => {
27043
+ app65.post("/tg-channel/ready", async (c) => {
26578
27044
  const body = await c.req.json().catch(() => null);
26579
27045
  const key = body?.key;
26580
27046
  if (typeof key !== "string") {
@@ -26583,7 +27049,7 @@ function createTelegramChannelRoutes(deps) {
26583
27049
  deps.onReady?.(key);
26584
27050
  return c.json({ ok: true });
26585
27051
  });
26586
- app64.post("/tg-channel/received", async (c) => {
27052
+ app65.post("/tg-channel/received", async (c) => {
26587
27053
  const body = await c.req.json().catch(() => null);
26588
27054
  const key = body?.key;
26589
27055
  const messageId = body?.messageId;
@@ -26593,7 +27059,7 @@ function createTelegramChannelRoutes(deps) {
26593
27059
  deps.onReceived?.(key, messageId);
26594
27060
  return c.json({ ok: true });
26595
27061
  });
26596
- app64.post("/tg-channel/turn-end", async (c) => {
27062
+ app65.post("/tg-channel/turn-end", async (c) => {
26597
27063
  const body = await c.req.json().catch(() => null);
26598
27064
  const key = body?.key;
26599
27065
  const sessionId = body?.sessionId;
@@ -26626,7 +27092,7 @@ function createTelegramChannelRoutes(deps) {
26626
27092
  console.error(`[telegram-native] op=turn-end key=${key} sessionId=${sid} replied=no delivered=none`);
26627
27093
  return c.json({ ok: true, delivered: "none" });
26628
27094
  });
26629
- return app64;
27095
+ return app65;
26630
27096
  }
26631
27097
 
26632
27098
  // app/lib/telegram/gateway/telegram-gateway.ts
@@ -26717,7 +27183,7 @@ var TelegramGateway = class {
26717
27183
  { key: input.senderId, text: input.text, messageId: `tg-${++this.seq}`, source, standingRules, scheduleProvenance: input.scheduleProvenance },
26718
27184
  Date.now()
26719
27185
  );
26720
- const resolvedSessionId = adminSessionIdFor(
27186
+ const resolvedSessionId = (input.role === "admin" ? this.deps.resolveSessionOverride?.(input.accountId, input.senderId) : null) || adminSessionIdFor(
26721
27187
  input.accountId,
26722
27188
  input.senderId,
26723
27189
  input.role === "public" && input.personId ? input.personId : void 0
@@ -26754,10 +27220,11 @@ var TelegramGateway = class {
26754
27220
  function buildTelegramSpawnRequest(input) {
26755
27221
  const personId = input.role === "public" && input.personId ? input.personId : void 0;
26756
27222
  return {
26757
- // Task 1746 — the deterministic per-sender id is the ONLY id a sender's
26758
- // session ever has. Mirror of the WhatsApp builder; see its comment for why
26759
- // Task 1526's per-firing override was removed.
26760
- 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),
26761
27228
  role: input.role,
26762
27229
  channel: "telegram",
26763
27230
  personId,
@@ -26774,9 +27241,9 @@ function buildTelegramSpawnRequest(input) {
26774
27241
 
26775
27242
  // app/lib/telegram/outbound/send-document.ts
26776
27243
  import { realpathSync as realpathSync9 } from "fs";
26777
- import { readFile as readFile7, stat as stat7 } from "fs/promises";
26778
- import { resolve as resolve33, basename as basename11 } from "path";
26779
- 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]";
26780
27247
  var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
26781
27248
  async function sendTelegramDocument(input) {
26782
27249
  const { botToken, chatId, filePath, caption, maxyAccountId, platformRoot: platformRoot5 } = input;
@@ -26786,25 +27253,25 @@ async function sendTelegramDocument(input) {
26786
27253
  if (!maxyAccountId || !platformRoot5) {
26787
27254
  return { ok: false, status: 400, error: "Cannot validate file path: missing account or platform context" };
26788
27255
  }
26789
- const accountDir = resolve33(platformRoot5, "..", "data/accounts", maxyAccountId);
27256
+ const accountDir = resolve34(platformRoot5, "..", "data/accounts", maxyAccountId);
26790
27257
  let resolvedPath;
26791
27258
  try {
26792
27259
  resolvedPath = realpathSync9(filePath);
26793
27260
  const accountResolved = realpathSync9(accountDir);
26794
27261
  if (!resolvedPath.startsWith(accountResolved + "/")) {
26795
- console.error(`${TAG50} document REJECTED reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
27262
+ console.error(`${TAG51} document REJECTED reason=outside_account_directory maxyAccountId=${maxyAccountId}`);
26796
27263
  return { ok: false, status: 403, error: "Access denied: file is outside the account directory" };
26797
27264
  }
26798
27265
  } catch (err) {
26799
27266
  const code = err.code;
26800
27267
  if (code === "ENOENT") {
26801
- console.error(`${TAG50} document ENOENT path=${filePath}`);
27268
+ console.error(`${TAG51} document ENOENT path=${filePath}`);
26802
27269
  return { ok: false, status: 404, error: `File not found: ${filePath}` };
26803
27270
  }
26804
- console.error(`${TAG50} document path error: ${String(err)}`);
27271
+ console.error(`${TAG51} document path error: ${String(err)}`);
26805
27272
  return { ok: false, status: 500, error: String(err) };
26806
27273
  }
26807
- const fileStat = await stat7(resolvedPath);
27274
+ const fileStat = await stat8(resolvedPath);
26808
27275
  if (fileStat.size > TELEGRAM_DOCUMENT_MAX_BYTES) {
26809
27276
  return {
26810
27277
  ok: false,
@@ -26813,7 +27280,7 @@ async function sendTelegramDocument(input) {
26813
27280
  };
26814
27281
  }
26815
27282
  const filename = basename11(resolvedPath);
26816
- const buffer = Buffer.from(await readFile7(resolvedPath));
27283
+ const buffer = Buffer.from(await readFile8(resolvedPath));
26817
27284
  const form = new FormData();
26818
27285
  form.append("chat_id", String(chatId));
26819
27286
  if (caption) form.append("caption", caption);
@@ -26834,13 +27301,13 @@ async function sendTelegramDocument(input) {
26834
27301
  error = e instanceof Error ? e.message : String(e);
26835
27302
  }
26836
27303
  console.error(
26837
- `${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}` : "")
26838
27305
  );
26839
27306
  return ok ? { ok: true, messageId } : { ok: false, status: 500, error };
26840
27307
  }
26841
27308
 
26842
27309
  // app/lib/telegram/outbound/file-delivery.ts
26843
- var TAG51 = "[telegram:outbound]";
27310
+ var TAG52 = "[telegram:outbound]";
26844
27311
  function platformRoot4() {
26845
27312
  return process.env.MAXY_PLATFORM_ROOT || "";
26846
27313
  }
@@ -26852,11 +27319,11 @@ function makeTelegramSendFile(entry) {
26852
27319
  botToken = (entry.role === "admin" ? tg?.adminBotToken : tg?.publicBotToken) ?? null;
26853
27320
  }
26854
27321
  if (!botToken) {
26855
- 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}`);
26856
27323
  return { ok: false, error: "no-bot-token" };
26857
27324
  }
26858
27325
  if (entry.replyTarget == null) {
26859
- 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}`);
26860
27327
  return { ok: false, error: "no-reply-target" };
26861
27328
  }
26862
27329
  const result = await sendTelegramDocument({
@@ -26873,7 +27340,7 @@ function makeTelegramSendFile(entry) {
26873
27340
  function makeTelegramFileDelivery(entry) {
26874
27341
  return makeFileDelivery({
26875
27342
  entry,
26876
- tag: TAG51,
27343
+ tag: TAG52,
26877
27344
  channel: "telegram",
26878
27345
  sendFile: makeTelegramSendFile(entry)
26879
27346
  });
@@ -26913,17 +27380,17 @@ function startTelegramNativeFileFollower(input) {
26913
27380
  }
26914
27381
 
26915
27382
  // server/telegram-descriptor.ts
26916
- import { resolve as resolve34, join as join41 } from "path";
27383
+ import { resolve as resolve35, join as join43 } from "path";
26917
27384
  function telegramGatewayUrl() {
26918
27385
  return `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`;
26919
27386
  }
26920
27387
  function telegramServerPath() {
26921
- 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");
26922
27389
  }
26923
27390
 
26924
27391
  // app/lib/channel-pty-bridge/public-session-end-review.ts
26925
27392
  import { randomUUID as randomUUID16 } from "crypto";
26926
- var TAG52 = "[public-session-review]";
27393
+ var TAG53 = "[public-session-review]";
26927
27394
  var CONSUMED_CAP = 500;
26928
27395
  var consumed = /* @__PURE__ */ new Set();
26929
27396
  function consumeOnce(sessionId) {
@@ -26950,7 +27417,7 @@ async function fetchJsonl(sessionId) {
26950
27417
  return await res.text();
26951
27418
  } catch (err) {
26952
27419
  console.error(
26953
- `${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)}"`
26954
27421
  );
26955
27422
  return null;
26956
27423
  }
@@ -26974,7 +27441,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
26974
27441
  const res = await fetch(url);
26975
27442
  if (!res.ok) {
26976
27443
  console.error(
26977
- `${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}`
26978
27445
  );
26979
27446
  return [];
26980
27447
  }
@@ -26982,7 +27449,7 @@ async function fetchPriorWrites(sliceToken, accountId) {
26982
27449
  return Array.isArray(body.writes) ? body.writes : [];
26983
27450
  } catch (err) {
26984
27451
  console.error(
26985
- `${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)}"`
26986
27453
  );
26987
27454
  return [];
26988
27455
  }
@@ -27011,7 +27478,7 @@ function composeInitialMessage(input) {
27011
27478
  }
27012
27479
  async function dispatchReviewer(input, initialMessage) {
27013
27480
  const sessionId = randomUUID16();
27014
- 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)}`);
27015
27482
  const spawned = await managerRcSpawn({
27016
27483
  sessionId,
27017
27484
  initialMessage,
@@ -27031,21 +27498,21 @@ async function firePublicSessionEndReview(input) {
27031
27498
  const dispatchedAt = Date.now();
27032
27499
  if (consumed.has(input.sessionId)) {
27033
27500
  console.log(
27034
- `${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`
27035
27502
  );
27036
27503
  return;
27037
27504
  }
27038
27505
  const jsonl = await fetchJsonl(input.sessionId);
27039
27506
  if (!jsonl) {
27040
27507
  console.log(
27041
- `${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`
27042
27509
  );
27043
27510
  return;
27044
27511
  }
27045
27512
  const operatorTurns = countOperatorTurns(jsonl);
27046
27513
  if (operatorTurns === 0) {
27047
27514
  console.log(
27048
- `${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`
27049
27516
  );
27050
27517
  return;
27051
27518
  }
@@ -27059,19 +27526,19 @@ async function firePublicSessionEndReview(input) {
27059
27526
  });
27060
27527
  if (!consumeOnce(input.sessionId)) {
27061
27528
  console.log(
27062
- `${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`
27063
27530
  );
27064
27531
  return;
27065
27532
  }
27066
27533
  const dispatched = await dispatchReviewer(input, initialMessage);
27067
27534
  if (!dispatched.ok) {
27068
27535
  console.error(
27069
- `${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}`
27070
27537
  );
27071
27538
  return;
27072
27539
  }
27073
27540
  console.log(
27074
- `${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}`
27075
27542
  );
27076
27543
  }
27077
27544
 
@@ -27205,8 +27672,8 @@ function broadcastAdminShutdown(reason) {
27205
27672
 
27206
27673
  // ../lib/entitlement/src/index.ts
27207
27674
  import { createPublicKey, createHash as createHash6, verify as cryptoVerify } from "crypto";
27208
- import { existsSync as existsSync37, readFileSync as readFileSync40, statSync as statSync16 } from "fs";
27209
- 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";
27210
27677
 
27211
27678
  // ../lib/entitlement/src/canonicalize.ts
27212
27679
  function canonicalize(value) {
@@ -27241,7 +27708,7 @@ var PUBKEY_SHA256 = "8eee6bcb33545fd13b16d3199a5735ca5db5062834c7b49dfe4f23801d9
27241
27708
  var GRACE_DAYS = 7;
27242
27709
  var GRACE_MS = GRACE_DAYS * 24 * 60 * 60 * 1e3;
27243
27710
  function pubkeyPath(brand) {
27244
- return resolve35(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
27711
+ return resolve36(brand.platformRoot, "lib", "entitlement", "rubytech-pubkey.pem");
27245
27712
  }
27246
27713
  var memo = null;
27247
27714
  function memoKey(mtimeMs, account) {
@@ -27253,12 +27720,12 @@ function resolveEntitlement(brand, account) {
27253
27720
  if (brand.commercialMode !== true) {
27254
27721
  return logResolved(implicitTrust(account), null);
27255
27722
  }
27256
- const entitlementPath = resolve35(brand.configDir, "entitlement.json");
27723
+ const entitlementPath = resolve36(brand.configDir, "entitlement.json");
27257
27724
  if (!existsSync37(entitlementPath)) {
27258
27725
  return logResolved(anonymousFallback("missing"), { reason: "missing" });
27259
27726
  }
27260
- const stat8 = statSync16(entitlementPath);
27261
- const key = memoKey(stat8.mtimeMs, account);
27727
+ const stat9 = statSync16(entitlementPath);
27728
+ const key = memoKey(stat9.mtimeMs, account);
27262
27729
  if (memo && memo.key === key) {
27263
27730
  return memo.result;
27264
27731
  }
@@ -27269,7 +27736,7 @@ function resolveEntitlement(brand, account) {
27269
27736
  function verifyAndResolve(brand, entitlementPath, account) {
27270
27737
  let pubkeyPem;
27271
27738
  try {
27272
- pubkeyPem = readFileSync40(pubkeyPath(brand), "utf-8");
27739
+ pubkeyPem = readFileSync41(pubkeyPath(brand), "utf-8");
27273
27740
  } catch (err) {
27274
27741
  return logResolved(anonymousFallback("pubkey-missing"), {
27275
27742
  reason: "pubkey-missing"
@@ -27283,7 +27750,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
27283
27750
  }
27284
27751
  let envelope;
27285
27752
  try {
27286
- envelope = JSON.parse(readFileSync40(entitlementPath, "utf-8"));
27753
+ envelope = JSON.parse(readFileSync41(entitlementPath, "utf-8"));
27287
27754
  } catch {
27288
27755
  return logResolved(anonymousFallback("malformed"), { reason: "malformed" });
27289
27756
  }
@@ -27445,14 +27912,14 @@ function clientFrom(c) {
27445
27912
  }
27446
27913
  installMediaDownloadGuard();
27447
27914
  var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT || "";
27448
- 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") : "";
27449
27916
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", marketingUrl: "https://getmaxy.com" };
27450
27917
  if (BRAND_JSON_PATH && !existsSync38(BRAND_JSON_PATH)) {
27451
27918
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
27452
27919
  }
27453
27920
  if (BRAND_JSON_PATH && existsSync38(BRAND_JSON_PATH)) {
27454
27921
  try {
27455
- const parsed = JSON.parse(readFileSync41(BRAND_JSON_PATH, "utf-8"));
27922
+ const parsed = JSON.parse(readFileSync42(BRAND_JSON_PATH, "utf-8"));
27456
27923
  BRAND = { ...BRAND, ...parsed };
27457
27924
  } catch (err) {
27458
27925
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -27484,11 +27951,11 @@ var brandLoginOpts = {
27484
27951
  appleTouchIconPath: brandAppIcon512Path,
27485
27952
  themeColor: BRAND.defaultColors?.primary
27486
27953
  };
27487
- var ALIAS_DOMAINS_PATH = join42(homedir4(), BRAND.configDir, "alias-domains.json");
27954
+ var ALIAS_DOMAINS_PATH = join44(homedir4(), BRAND.configDir, "alias-domains.json");
27488
27955
  function loadAliasDomains() {
27489
27956
  try {
27490
27957
  if (!existsSync38(ALIAS_DOMAINS_PATH)) return null;
27491
- const parsed = JSON.parse(readFileSync41(ALIAS_DOMAINS_PATH, "utf-8"));
27958
+ const parsed = JSON.parse(readFileSync42(ALIAS_DOMAINS_PATH, "utf-8"));
27492
27959
  if (!Array.isArray(parsed)) {
27493
27960
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
27494
27961
  return null;
@@ -27512,7 +27979,15 @@ watchFile(ALIAS_DOMAINS_PATH, { interval: 2e3 }, () => {
27512
27979
  function isPublicHost(host) {
27513
27980
  return host.startsWith("public.") || aliasDomains.has(host);
27514
27981
  }
27515
- 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
+ }
27516
27991
  var nativeFileFollowers = /* @__PURE__ */ new Map();
27517
27992
  var webchatFileFollowers = /* @__PURE__ */ new Map();
27518
27993
  async function fetchAccountStandingRules(accountId) {
@@ -27551,8 +28026,9 @@ async function recordPassiveDispatch(input) {
27551
28026
  }
27552
28027
  var waGateway = new WaGateway({
27553
28028
  fetchStandingRules: fetchAccountStandingRules,
28029
+ resolveSessionOverride: (accountId, senderId) => resolveChannelOverride(accountId, "whatsapp", senderId),
27554
28030
  gatewayUrl: `http://127.0.0.1:${process.env.MAXY_UI_INTERNAL_PORT ?? ""}`,
27555
- 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"),
27556
28032
  // Task 751 / 1390 — file delivery on the native channel. `maxyAccountId` (the
27557
28033
  // path-validation scope) is the sender's effective SESSION account, resolved
27558
28034
  // once at the inbound gate and threaded here via the gateway's per-sender doc
@@ -27567,7 +28043,7 @@ var waGateway = new WaGateway({
27567
28043
  caption,
27568
28044
  accountId,
27569
28045
  maxyAccountId,
27570
- platformRoot: resolve36(process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, ".."))
28046
+ platformRoot: resolve37(process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, ".."))
27571
28047
  });
27572
28048
  return result.ok ? { ok: true, messageId: result.messageId } : { ok: false, error: result.error };
27573
28049
  },
@@ -27619,7 +28095,17 @@ var waGateway = new WaGateway({
27619
28095
  const managesAccount = effectiveAccountId !== accountId ? effectiveAccountId : "none";
27620
28096
  console.error(`[whatsapp-native] op=account-manager-route senderId=${senderId} managesAccount=${managesAccount} effectiveAccount=${effectiveAccountId} source=gate`);
27621
28097
  }
27622
- 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
+ });
27623
28109
  await ensureSessionConversation({
27624
28110
  accountId: effectiveAccountId,
27625
28111
  sessionId: req.sessionId,
@@ -27665,13 +28151,13 @@ var waGateway = new WaGateway({
27665
28151
  nativeFileFollowers.set(senderId, ac);
27666
28152
  }
27667
28153
  });
27668
- app63.route("/", waGateway.routes());
28154
+ app64.route("/", waGateway.routes());
27669
28155
  waGateway.startSweeper();
27670
28156
  function runDuplicateSenderAudit() {
27671
28157
  try {
27672
28158
  const configDir2 = process.env.CLAUDE_CONFIG_DIR;
27673
28159
  if (!configDir2) return;
27674
- const projectsRoot = join42(configDir2, "projects");
28160
+ const projectsRoot = join44(configDir2, "projects");
27675
28161
  const records = [];
27676
28162
  for (const { path: jsonlPath, isSubagent, archived } of enumerateJsonls(projectsRoot)) {
27677
28163
  if (isSubagent || archived) continue;
@@ -27681,6 +28167,8 @@ function runDuplicateSenderAudit() {
27681
28167
  records.push({ sessionId, channel: meta.channel, senderId: meta.senderId, accountId: meta.accountId });
27682
28168
  }
27683
28169
  logDuplicateSenderGroups(findDuplicateSenderGroups(records));
28170
+ const now = Date.now();
28171
+ logUnseatedSenders(waGateway.unseatedSenders(now, 15 * 6e4), now);
27684
28172
  } catch (err) {
27685
28173
  console.error(`[whatsapp-native] op=duplicate-sender-audit-error error=${err instanceof Error ? err.message : String(err)}`);
27686
28174
  }
@@ -27787,16 +28275,26 @@ var webchatGateway = new WebchatGateway({
27787
28275
  firePublicSessionEndReview: (input) => firePublicSessionEndReview(input)
27788
28276
  });
27789
28277
  setWebchatGateway(webchatGateway);
27790
- app63.route("/", webchatGateway.routes());
28278
+ app64.route("/", webchatGateway.routes());
27791
28279
  webchatGateway.startSweeper();
27792
28280
  webchatGateway.startPublicReaper();
27793
28281
  var telegramFileFollowers = /* @__PURE__ */ new Map();
27794
28282
  var telegramGateway = new TelegramGateway({
27795
28283
  fetchStandingRules: fetchAccountStandingRules,
28284
+ resolveSessionOverride: (accountId, senderId) => resolveChannelOverride(accountId, "telegram", senderId),
27796
28285
  gatewayUrl: telegramGatewayUrl(),
27797
28286
  serverPath: telegramServerPath(),
27798
28287
  ensureChannelSession: async ({ accountId, senderId, role, personId, chatId, gatewayUrl, serverPath }) => {
27799
- 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
+ });
27800
28298
  const result = await managerRcSpawn(req);
27801
28299
  if ("error" in result) {
27802
28300
  console.error(`[telegram-native] spawn-failed senderId=${senderId} role=${role} error=${result.error} status=${result.status}`);
@@ -27821,7 +28319,7 @@ var telegramGateway = new TelegramGateway({
27821
28319
  }
27822
28320
  });
27823
28321
  setTelegramGateway(telegramGateway);
27824
- app63.route("/", telegramGateway.routes());
28322
+ app64.route("/", telegramGateway.routes());
27825
28323
  telegramGateway.startSweeper();
27826
28324
  var chatRoutes = createChatRoutes({
27827
28325
  handleInbound: (input) => webchatGateway.handleInbound(input),
@@ -27855,19 +28353,19 @@ var scheduleInjectRoutes = createScheduleInjectRoutes({
27855
28353
  return managed.account;
27856
28354
  }
27857
28355
  });
27858
- app63.route("/api/channel/schedule-inject", scheduleInjectRoutes);
27859
- app63.use("*", clientIpMiddleware);
28356
+ app64.route("/api/channel/schedule-inject", scheduleInjectRoutes);
28357
+ app64.use("*", clientIpMiddleware);
27860
28358
  function allowsSameOriginFraming(path3) {
27861
28359
  return path3 === "/vnc-viewer.html" || path3.startsWith("/api/admin/attachment/") || path3.startsWith("/api/public-reader/attachment/") || path3 === "/api/admin/files/download";
27862
28360
  }
27863
- app63.use("*", async (c, next) => {
28361
+ app64.use("*", async (c, next) => {
27864
28362
  await next();
27865
28363
  c.header("X-Content-Type-Options", "nosniff");
27866
28364
  c.header("Referrer-Policy", "strict-origin-when-cross-origin");
27867
28365
  c.header("X-Frame-Options", allowsSameOriginFraming(c.req.path) ? "SAMEORIGIN" : "DENY");
27868
28366
  });
27869
28367
  var HTTP_LOG_PATHS = /* @__PURE__ */ new Set(["/vnc-viewer.html", "/vnc-popout.html"]);
27870
- app63.use("*", async (c, next) => {
28368
+ app64.use("*", async (c, next) => {
27871
28369
  if (!HTTP_LOG_PATHS.has(c.req.path)) {
27872
28370
  await next();
27873
28371
  return;
@@ -27885,7 +28383,7 @@ app63.use("*", async (c, next) => {
27885
28383
  });
27886
28384
  }
27887
28385
  });
27888
- app63.use("*", async (c, next) => {
28386
+ app64.use("*", async (c, next) => {
27889
28387
  const host = (c.req.header("host") ?? "").split(":")[0];
27890
28388
  if (isOperatorHost(host, getOperatorDomains()) || !isPublicHost(host)) {
27891
28389
  await next();
@@ -27923,7 +28421,7 @@ function resolveRemoteAuthOpts(c) {
27923
28421
  return { ...brandLoginOpts, origin };
27924
28422
  }
27925
28423
  var MAX_LOGIN_BODY = 8 * 1024;
27926
- app63.post("/__remote-auth/login", async (c) => {
28424
+ app64.post("/__remote-auth/login", async (c) => {
27927
28425
  const client = clientFrom(c);
27928
28426
  const clientIp = client.ip || "unknown";
27929
28427
  if (!requestIsTlsTerminated(c)) {
@@ -27968,7 +28466,7 @@ app63.post("/__remote-auth/login", async (c) => {
27968
28466
  }
27969
28467
  });
27970
28468
  });
27971
- app63.get("/__remote-auth/logout", (c) => {
28469
+ app64.get("/__remote-auth/logout", (c) => {
27972
28470
  const client = clientFrom(c);
27973
28471
  const clientIp = client.ip || "unknown";
27974
28472
  console.error(`[remote-auth] logout ip=${clientIp}`);
@@ -27981,7 +28479,7 @@ app63.get("/__remote-auth/logout", (c) => {
27981
28479
  }
27982
28480
  });
27983
28481
  });
27984
- app63.post("/__remote-auth/change-password", async (c) => {
28482
+ app64.post("/__remote-auth/change-password", async (c) => {
27985
28483
  const client = clientFrom(c);
27986
28484
  const clientIp = client.ip || "unknown";
27987
28485
  const rateLimited = checkRateLimit(client);
@@ -28040,13 +28538,13 @@ app63.post("/__remote-auth/change-password", async (c) => {
28040
28538
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), mode: "change", changeError: "Failed to save password", redirect }), 200);
28041
28539
  }
28042
28540
  });
28043
- app63.get("/__remote-auth/setup", (c) => {
28541
+ app64.get("/__remote-auth/setup", (c) => {
28044
28542
  if (isRemoteAuthConfigured()) {
28045
28543
  return c.redirect("/");
28046
28544
  }
28047
28545
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), mode: "setup" }), 200);
28048
28546
  });
28049
- app63.post("/__remote-auth/set-initial-password", async (c) => {
28547
+ app64.post("/__remote-auth/set-initial-password", async (c) => {
28050
28548
  if (isRemoteAuthConfigured()) {
28051
28549
  return c.redirect("/");
28052
28550
  }
@@ -28084,10 +28582,10 @@ app63.post("/__remote-auth/set-initial-password", async (c) => {
28084
28582
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), mode: "setup", setupError: "Failed to save password. Please try again." }), 200);
28085
28583
  }
28086
28584
  });
28087
- app63.get("/api/remote-auth/status", (c) => {
28585
+ app64.get("/api/remote-auth/status", (c) => {
28088
28586
  return c.json({ configured: isRemoteAuthConfigured() });
28089
28587
  });
28090
- app63.post("/api/remote-auth/set-password", async (c) => {
28588
+ app64.post("/api/remote-auth/set-password", async (c) => {
28091
28589
  let body;
28092
28590
  try {
28093
28591
  body = await c.req.json();
@@ -28126,10 +28624,10 @@ app63.post("/api/remote-auth/set-password", async (c) => {
28126
28624
  return c.json({ error: "Failed to save password" }, 500);
28127
28625
  }
28128
28626
  });
28129
- app63.route("/api/_client-error", client_error_default);
28627
+ app64.route("/api/_client-error", client_error_default);
28130
28628
  console.log("[client-error-route] mounted");
28131
28629
  var PWA_PUBLIC_PATHS = /* @__PURE__ */ new Set(["/sw.js", ...PWA_SURFACES.map((s) => s.manifestPath)]);
28132
- app63.use("*", async (c, next) => {
28630
+ app64.use("*", async (c, next) => {
28133
28631
  const host = (c.req.header("host") ?? "").split(":")[0];
28134
28632
  const path3 = c.req.path;
28135
28633
  if (path3 === "/favicon.ico" || path3.startsWith("/assets/") || path3.startsWith("/brand/") || // Public free/busy is read by the booking page (a separate Cloudflare Pages
@@ -28173,27 +28671,28 @@ app63.use("*", async (c, next) => {
28173
28671
  }
28174
28672
  return c.html(renderLoginPage({ ...resolveRemoteAuthOpts(c), redirect: path3 }), 200);
28175
28673
  });
28176
- app63.route("/api/health", health_default);
28177
- app63.route("/api/chat", chatRoutes);
28178
- app63.route("/api/whatsapp", whatsapp_default);
28179
- app63.route("/api/storage", storage_broker_default);
28180
- app63.route("/api/whatsapp-reader", whatsapp_reader_default);
28181
- app63.route("/api/public-reader", public_reader_default);
28182
- 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({
28183
28681
  handleInbound: (input) => webchatGateway.handleInbound(input),
28184
28682
  // Task 940 — the permission relay's pointer read + verdict write.
28185
28683
  pendingPromptFor: (key) => webchatGateway.pendingPromptFor(key),
28186
28684
  deliveryFailureFor: (key) => webchatGateway.deliveryFailureFor(key),
28187
28685
  resolvePermissionVerdict: (key, requestId, behavior) => webchatGateway.resolvePermissionVerdict(key, requestId, behavior)
28188
28686
  }));
28189
- app63.route("/api/webchat/greeting", webchat_greeting_default);
28190
- app63.route("/api/telegram", telegram_default);
28191
- app63.route("/api/quickbooks", quickbooks_default);
28192
- app63.route("/api/onboarding", onboarding_default);
28193
- app63.route("/api/admin", admin_default);
28194
- app63.route("/api/access", access_default);
28195
- app63.route("/api/session", session_default2);
28196
- 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);
28197
28696
  var SAFE_SLUG_RE2 = /^[a-z][a-z0-9-]{2,49}$/;
28198
28697
  var SAFE_FILENAME_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
28199
28698
  var IMAGE_MIME = {
@@ -28205,7 +28704,7 @@ var IMAGE_MIME = {
28205
28704
  ".svg": "image/svg+xml",
28206
28705
  ".ico": "image/x-icon"
28207
28706
  };
28208
- app63.get("/agent-assets/:slug/:filename", (c) => {
28707
+ app64.get("/agent-assets/:slug/:filename", (c) => {
28209
28708
  const slug = c.req.param("slug");
28210
28709
  const filename = c.req.param("filename");
28211
28710
  if (!SAFE_SLUG_RE2.test(slug)) {
@@ -28221,8 +28720,8 @@ app63.get("/agent-assets/:slug/:filename", (c) => {
28221
28720
  console.error(`[agent-assets] no-account slug=${slug} file=${filename}`);
28222
28721
  return c.text("Not found", 404);
28223
28722
  }
28224
- const filePath = resolve36(account.accountDir, "agents", slug, "assets", filename);
28225
- 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");
28226
28725
  if (!filePath.startsWith(expectedDir + "/")) {
28227
28726
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
28228
28727
  return c.text("Forbidden", 403);
@@ -28234,13 +28733,13 @@ app63.get("/agent-assets/:slug/:filename", (c) => {
28234
28733
  const ext = "." + filename.split(".").pop()?.toLowerCase();
28235
28734
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
28236
28735
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
28237
- const body = readFileSync41(filePath);
28736
+ const body = readFileSync42(filePath);
28238
28737
  return c.body(body, 200, {
28239
28738
  "Content-Type": contentType,
28240
28739
  "Cache-Control": "public, max-age=3600"
28241
28740
  });
28242
28741
  });
28243
- app63.get("/generated/:filename", (c) => {
28742
+ app64.get("/generated/:filename", (c) => {
28244
28743
  const filename = c.req.param("filename");
28245
28744
  if (!SAFE_FILENAME_RE.test(filename) || filename.includes("..")) {
28246
28745
  console.error(`[generated] serve file=${filename} status=403`);
@@ -28251,8 +28750,8 @@ app63.get("/generated/:filename", (c) => {
28251
28750
  console.error(`[generated] serve file=${filename} status=404`);
28252
28751
  return c.text("Not found", 404);
28253
28752
  }
28254
- const filePath = resolve36(account.accountDir, "generated", filename);
28255
- const expectedDir = resolve36(account.accountDir, "generated");
28753
+ const filePath = resolve37(account.accountDir, "generated", filename);
28754
+ const expectedDir = resolve37(account.accountDir, "generated");
28256
28755
  if (!filePath.startsWith(expectedDir + "/")) {
28257
28756
  console.error(`[generated] serve file=${filename} status=403`);
28258
28757
  return c.text("Forbidden", 403);
@@ -28264,22 +28763,22 @@ app63.get("/generated/:filename", (c) => {
28264
28763
  const ext = "." + filename.split(".").pop()?.toLowerCase();
28265
28764
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
28266
28765
  console.log(`[generated] serve file=${filename} status=200`);
28267
- const body = readFileSync41(filePath);
28766
+ const body = readFileSync42(filePath);
28268
28767
  return c.body(body, 200, {
28269
28768
  "Content-Type": contentType,
28270
28769
  "Cache-Control": "public, max-age=86400"
28271
28770
  });
28272
28771
  });
28273
- app63.route("/sites", sites_default);
28274
- app63.route("/listings", listings_default);
28275
- app63.route("/v", visitor_event_default);
28276
- 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);
28277
28776
  var htmlCache = /* @__PURE__ */ new Map();
28278
28777
  var brandLogoPath = "/brand/maxy-monochrome.png";
28279
28778
  var brandIconPath = "/brand/maxy-monochrome.png";
28280
28779
  if (BRAND_JSON_PATH && existsSync38(BRAND_JSON_PATH)) {
28281
28780
  try {
28282
- const fullBrand = JSON.parse(readFileSync41(BRAND_JSON_PATH, "utf-8"));
28781
+ const fullBrand = JSON.parse(readFileSync42(BRAND_JSON_PATH, "utf-8"));
28283
28782
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
28284
28783
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
28285
28784
  } catch {
@@ -28306,11 +28805,11 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
28306
28805
  var brandThemeColor = BRAND.defaultColors?.primary ?? "#000000";
28307
28806
  var brandBackgroundColor = BRAND.defaultColors?.background ?? "#ffffff";
28308
28807
  var brandAppIconsExist = [brandAppIcon192Path, brandAppIcon512Path, brandMaskableIconPath].every(
28309
- (p) => existsSync38(resolve36(process.cwd(), "public", p.replace(/^\//, "")))
28808
+ (p) => existsSync38(resolve37(process.cwd(), "public", p.replace(/^\//, "")))
28310
28809
  );
28311
28810
  var SW_SOURCE = (() => {
28312
28811
  try {
28313
- return readFileSync41(resolve36(process.cwd(), "public", "sw.js"), "utf-8");
28812
+ return readFileSync42(resolve37(process.cwd(), "public", "sw.js"), "utf-8");
28314
28813
  } catch {
28315
28814
  return null;
28316
28815
  }
@@ -28318,9 +28817,9 @@ var SW_SOURCE = (() => {
28318
28817
  function readInstalledVersion() {
28319
28818
  try {
28320
28819
  if (!PLATFORM_ROOT8) return "unknown";
28321
- const versionFile = join42(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
28820
+ const versionFile = join44(PLATFORM_ROOT8, "config", `.${BRAND.hostname}-version`);
28322
28821
  if (!existsSync38(versionFile)) return "unknown";
28323
- const content = readFileSync41(versionFile, "utf-8").trim();
28822
+ const content = readFileSync42(versionFile, "utf-8").trim();
28324
28823
  return content || "unknown";
28325
28824
  } catch {
28326
28825
  return "unknown";
@@ -28361,7 +28860,7 @@ var clientErrorReporterScript = `<script>
28361
28860
  function cachedHtml(file) {
28362
28861
  let html = htmlCache.get(file);
28363
28862
  if (!html) {
28364
- html = readFileSync41(resolve36(process.cwd(), "public", file), "utf-8");
28863
+ html = readFileSync42(resolve37(process.cwd(), "public", file), "utf-8");
28365
28864
  const productNameEsc = escapeHtml(BRAND.productName);
28366
28865
  html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
28367
28866
  html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
@@ -28378,13 +28877,13 @@ ${clientErrorReporterScript}
28378
28877
  return html;
28379
28878
  }
28380
28879
  function loadBrandingCache(agentSlug) {
28381
- const configDir2 = join42(homedir4(), BRAND.configDir);
28880
+ const configDir2 = join44(homedir4(), BRAND.configDir);
28382
28881
  try {
28383
28882
  const accountId = getDefaultAccountId();
28384
28883
  if (!accountId) return null;
28385
- const cachePath = join42(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
28884
+ const cachePath = join44(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
28386
28885
  if (!existsSync38(cachePath)) return null;
28387
- return JSON.parse(readFileSync41(cachePath, "utf-8"));
28886
+ return JSON.parse(readFileSync42(cachePath, "utf-8"));
28388
28887
  } catch {
28389
28888
  return null;
28390
28889
  }
@@ -28428,7 +28927,7 @@ function brandedPublicHtml(agentSlug) {
28428
28927
  function agentUnavailableHtml() {
28429
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>`;
28430
28929
  }
28431
- app63.get("/", (c) => {
28930
+ app64.get("/", (c) => {
28432
28931
  const host = (c.req.header("host") ?? "").split(":")[0];
28433
28932
  const klass = classifyHost(host, getOperatorDomains(), isPublicHost);
28434
28933
  if (klass === "operator") {
@@ -28450,12 +28949,12 @@ app63.get("/", (c) => {
28450
28949
  console.log(`[host-class] host=${host} class=admin served=index.html`);
28451
28950
  return c.html(cachedHtml("index.html"));
28452
28951
  });
28453
- app63.get("/public", (c) => {
28952
+ app64.get("/public", (c) => {
28454
28953
  const host = (c.req.header("host") ?? "").split(":")[0];
28455
28954
  if (isPublicHost(host)) return c.text("Not found", 404);
28456
28955
  return c.html(cachedHtml("public.html"));
28457
28956
  });
28458
- app63.get("/public-chat", (c) => {
28957
+ app64.get("/public-chat", (c) => {
28459
28958
  const host = (c.req.header("host") ?? "").split(":")[0];
28460
28959
  if (isPublicHost(host)) return c.text("Not found", 404);
28461
28960
  return c.html(cachedHtml("public.html"));
@@ -28474,12 +28973,12 @@ async function logViewerFetch(c, next) {
28474
28973
  duration_ms: Date.now() - start
28475
28974
  });
28476
28975
  }
28477
- app63.use("/vnc-viewer.html", logViewerFetch);
28478
- app63.use("/vnc-popout.html", logViewerFetch);
28479
- 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) => {
28480
28979
  let html = htmlCache.get("vnc-popout.html");
28481
28980
  if (!html) {
28482
- html = readFileSync41(resolve36(process.cwd(), "public", "vnc-popout.html"), "utf-8");
28981
+ html = readFileSync42(resolve37(process.cwd(), "public", "vnc-popout.html"), "utf-8");
28483
28982
  const name = escapeHtml(BRAND.productName);
28484
28983
  html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
28485
28984
  html = html.replace("</head>", ` ${brandScript}
@@ -28489,7 +28988,7 @@ app63.get("/vnc-popout.html", (c) => {
28489
28988
  }
28490
28989
  return c.html(html);
28491
28990
  });
28492
- app63.post("/api/vnc/client-event", async (c) => {
28991
+ app64.post("/api/vnc/client-event", async (c) => {
28493
28992
  let body;
28494
28993
  try {
28495
28994
  body = await c.req.json();
@@ -28510,11 +29009,11 @@ app63.post("/api/vnc/client-event", async (c) => {
28510
29009
  });
28511
29010
  return c.json({ ok: true });
28512
29011
  });
28513
- app63.get("/g/:slug", (c) => {
29012
+ app64.get("/g/:slug", (c) => {
28514
29013
  return c.html(brandedPublicHtml(resolveDefaultSlug() ?? void 0));
28515
29014
  });
28516
29015
  for (const pwa of PWA_SURFACES) {
28517
- app63.get(pwa.manifestPath, (c) => {
29016
+ app64.get(pwa.manifestPath, (c) => {
28518
29017
  const manifest = buildManifest(pwa, {
28519
29018
  productName: BRAND.productName,
28520
29019
  appIcon192: brandAppIcon192Path,
@@ -28530,7 +29029,7 @@ for (const pwa of PWA_SURFACES) {
28530
29029
  return c.body(JSON.stringify(manifest));
28531
29030
  });
28532
29031
  }
28533
- app63.get("/sw.js", (c) => {
29032
+ app64.get("/sw.js", (c) => {
28534
29033
  if (SW_SOURCE == null) {
28535
29034
  console.error("[pwa] op=sw status=500 reason=sw-source-missing");
28536
29035
  return c.text("Service worker unavailable", 500);
@@ -28539,12 +29038,12 @@ app63.get("/sw.js", (c) => {
28539
29038
  console.log(`[pwa] op=sw status=200 ct=${SW_CONTENT_TYPE}`);
28540
29039
  return c.body(SW_SOURCE);
28541
29040
  });
28542
- app63.get("/graph", (c) => {
29041
+ app64.get("/graph", (c) => {
28543
29042
  const host = (c.req.header("host") ?? "").split(":")[0];
28544
29043
  if (isPublicHost(host) || isOperatorHost(host, getOperatorDomains())) return c.text("Not found", 404);
28545
29044
  return c.html(cachedHtml("graph.html"));
28546
29045
  });
28547
- app63.get("/chat", (c) => {
29046
+ app64.get("/chat", (c) => {
28548
29047
  const host = (c.req.header("host") ?? "").split(":")[0];
28549
29048
  if (isOperatorHost(host, getOperatorDomains())) {
28550
29049
  console.log(`[host-class] host=${host} class=operator served=operator.html`);
@@ -28553,32 +29052,32 @@ app63.get("/chat", (c) => {
28553
29052
  if (isPublicHost(host)) return c.text("Not found", 404);
28554
29053
  return c.html(cachedHtml("chat.html"));
28555
29054
  });
28556
- app63.get("/data", (c) => {
29055
+ app64.get("/data", (c) => {
28557
29056
  const host = (c.req.header("host") ?? "").split(":")[0];
28558
29057
  if (isPublicHost(host)) return c.text("Not found", 404);
28559
29058
  return c.html(cachedHtml("data.html"));
28560
29059
  });
28561
- app63.get("/tasks", (c) => {
29060
+ app64.get("/tasks", (c) => {
28562
29061
  const host = (c.req.header("host") ?? "").split(":")[0];
28563
29062
  if (isPublicHost(host)) return c.text("Not found", 404);
28564
29063
  return c.html(cachedHtml("tasks.html"));
28565
29064
  });
28566
- app63.get("/activity", (c) => {
29065
+ app64.get("/activity", (c) => {
28567
29066
  const host = (c.req.header("host") ?? "").split(":")[0];
28568
29067
  if (isPublicHost(host)) return c.text("Not found", 404);
28569
29068
  return c.html(cachedHtml("activity.html"));
28570
29069
  });
28571
- app63.get("/calendar", (c) => {
29070
+ app64.get("/calendar", (c) => {
28572
29071
  const host = (c.req.header("host") ?? "").split(":")[0];
28573
29072
  if (isPublicHost(host)) return c.text("Not found", 404);
28574
29073
  return c.html(cachedHtml("calendar.html"));
28575
29074
  });
28576
- app63.get("/browser", (c) => {
29075
+ app64.get("/browser", (c) => {
28577
29076
  const host = (c.req.header("host") ?? "").split(":")[0];
28578
29077
  if (isPublicHost(host) || isOperatorHost(host, getOperatorDomains())) return c.text("Not found", 404);
28579
29078
  return c.html(cachedHtml("browser.html"));
28580
29079
  });
28581
- app63.get("/:slug", async (c, next) => {
29080
+ app64.get("/:slug", async (c, next) => {
28582
29081
  const slug = c.req.param("slug");
28583
29082
  if (AGENT_SLUG_PATTERN.test(`/${slug}`)) {
28584
29083
  const account = resolveAccount();
@@ -28593,13 +29092,13 @@ app63.get("/:slug", async (c, next) => {
28593
29092
  await next();
28594
29093
  });
28595
29094
  if (brandFaviconPath !== "/favicon.ico") {
28596
- app63.get("/favicon.ico", (c) => {
29095
+ app64.get("/favicon.ico", (c) => {
28597
29096
  c.header("Cache-Control", "public, max-age=300");
28598
29097
  return c.redirect(brandFaviconPath, 302);
28599
29098
  });
28600
29099
  }
28601
- app63.use("/*", serveStatic({ root: "./public" }));
28602
- app63.all("*", (c) => {
29100
+ app64.use("/*", serveStatic({ root: "./public" }));
29101
+ app64.all("*", (c) => {
28603
29102
  const host = (c.req.header("host") ?? "").split(":")[0];
28604
29103
  const path3 = c.req.path;
28605
29104
  if (isPublicHost(host)) {
@@ -28613,11 +29112,11 @@ app63.all("*", (c) => {
28613
29112
  });
28614
29113
  var port = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "ui-server" });
28615
29114
  var hostname = process.env.HOSTNAME ?? "127.0.0.1";
28616
- var httpServer = serve({ fetch: app63.fetch, port, hostname });
29115
+ var httpServer = serve({ fetch: app64.fetch, port, hostname });
28617
29116
  console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28618
29117
  {
28619
- const reconcilePlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28620
- 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");
28621
29120
  const RECONCILE_INTERVAL_MS = 12e4;
28622
29121
  const runReconcile = (ctx) => {
28623
29122
  if (!existsSync38(reconcileScript)) return;
@@ -28642,8 +29141,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28642
29141
  });
28643
29142
  }
28644
29143
  {
28645
- const outlookPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28646
- 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");
28647
29146
  const OUTLOOK_COMPLETE_INTERVAL_MS = 3e4;
28648
29147
  const runOutlookComplete = (ctx) => {
28649
29148
  if (!existsSync38(outlookScript)) return;
@@ -28668,8 +29167,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28668
29167
  });
28669
29168
  }
28670
29169
  {
28671
- const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28672
- const strandedAccountsDir = resolve36(auditRoot, "..", "data/accounts");
29170
+ const auditRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29171
+ const strandedAccountsDir = resolve37(auditRoot, "..", "data/accounts");
28673
29172
  const STRANDED_AUDIT_INTERVAL_MS = 3e5;
28674
29173
  const STRANDED_AGE_MS = 16 * 6e4;
28675
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;
@@ -28679,7 +29178,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28679
29178
  const now = Date.now();
28680
29179
  for (const name of readdirSync26(strandedAccountsDir)) {
28681
29180
  if (!STRANDED_UUID_RE.test(name)) continue;
28682
- const pendingPath2 = resolve36(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
29181
+ const pendingPath2 = resolve37(strandedAccountsDir, name, "secrets/outlook/pending-devicecode.enc");
28683
29182
  if (!existsSync38(pendingPath2)) continue;
28684
29183
  const ageMs = now - statSync17(pendingPath2).mtimeMs;
28685
29184
  if (ageMs > STRANDED_AGE_MS) {
@@ -28698,8 +29197,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28698
29197
  });
28699
29198
  }
28700
29199
  {
28701
- const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28702
- const googleAccountsDir = resolve36(googleRoot, "..", "data/accounts");
29200
+ const googleRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
29201
+ const googleAccountsDir = resolve37(googleRoot, "..", "data/accounts");
28703
29202
  const GOOGLE_PENDING_AUDIT_INTERVAL_MS = 3e5;
28704
29203
  const runGooglePendingAuditSafe = () => {
28705
29204
  try {
@@ -28714,7 +29213,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28714
29213
  firstRunDelayMs: 27e3,
28715
29214
  run: runGooglePendingAuditSafe
28716
29215
  });
28717
- 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");
28718
29217
  const GOOGLE_ACCOUNT_AUDIT_INTERVAL_MS = 36e5;
28719
29218
  const runGoogleAccountAudit = (ctx) => {
28720
29219
  if (!existsSync38(googleAuditScript)) return;
@@ -28742,7 +29241,7 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28742
29241
  });
28743
29242
  }
28744
29243
  {
28745
- const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
29244
+ const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..");
28746
29245
  const STORAGE_AUDIT_INTERVAL_MS = 3e5;
28747
29246
  const runStorageAuditSafe = () => runStorageAudit(auditPlatformRoot).catch((err) => {
28748
29247
  console.error(`[storage-audit] error="${err.message}"`);
@@ -28764,10 +29263,20 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28764
29263
  firstRunDelayMs: 35e3,
28765
29264
  run: runPagesAuditSafe
28766
29265
  });
29266
+ const runDataPortalAuditSafe = () => runDataPortalAudit(auditPlatformRoot).catch((err) => {
29267
+ console.error(`[data-portal-audit] error="${err.message}"`);
29268
+ throw err;
29269
+ });
29270
+ registerLoop({
29271
+ name: "data-portal-audit",
29272
+ intervalMs: STORAGE_AUDIT_INTERVAL_MS,
29273
+ firstRunDelayMs: 5e4,
29274
+ run: runDataPortalAuditSafe
29275
+ });
28767
29276
  }
28768
29277
  {
28769
- const publishPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28770
- 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");
28771
29280
  const PUBLISH_INTERVAL_MS = 3e5;
28772
29281
  const currentAccount = () => {
28773
29282
  try {
@@ -28797,21 +29306,21 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28797
29306
  }
28798
29307
  };
28799
29308
  const auditSnapshotAge = (account) => {
28800
- const availPath = resolve36(account.accountDir, "calendar-availability.json");
29309
+ const availPath = resolve37(account.accountDir, "calendar-availability.json");
28801
29310
  let hasBookingSite = false;
28802
29311
  try {
28803
29312
  if (existsSync38(availPath)) {
28804
- const cfg = JSON.parse(readFileSync41(availPath, "utf-8"));
29313
+ const cfg = JSON.parse(readFileSync42(availPath, "utf-8"));
28805
29314
  hasBookingSite = typeof cfg.bookingDbName === "string" && cfg.bookingDbName.length > 0;
28806
29315
  }
28807
29316
  } catch {
28808
29317
  }
28809
29318
  if (!hasBookingSite) return;
28810
- const statePath = resolve36(account.accountDir, "state", "booking-availability", "last-publish.json");
29319
+ const statePath = resolve37(account.accountDir, "state", "booking-availability", "last-publish.json");
28811
29320
  let lastSuccessAt = null;
28812
29321
  if (existsSync38(statePath)) {
28813
29322
  try {
28814
- const rec = JSON.parse(readFileSync41(statePath, "utf-8"));
29323
+ const rec = JSON.parse(readFileSync42(statePath, "utf-8"));
28815
29324
  lastSuccessAt = rec.lastSuccessAt ?? null;
28816
29325
  } catch {
28817
29326
  console.error(`[calendar-availability] op=audit accountId=${account.accountId} snapshotAgeMs=na reason=bad-state-file`);
@@ -28839,8 +29348,8 @@ console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
28839
29348
  startTimeEntryCensus(() => getSession());
28840
29349
  startLedgerCensus(() => getSession());
28841
29350
  {
28842
- const auditPlatformRoot = process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..");
28843
- 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");
28844
29353
  const auditLogDir = process.env.LOG_DIR ?? LOG_DIR;
28845
29354
  const CONNECTOR_AUDIT_INTERVAL_MS = 3e5;
28846
29355
  const runConnectorAudit = (ctx) => {
@@ -28912,6 +29421,7 @@ var SUBAPP_MANIFEST = [
28912
29421
  { prefix: "/api/access", file: "server/routes/access/index.ts", subapp: access_default },
28913
29422
  { prefix: "/api/session", file: "server/routes/session.ts", subapp: session_default2 },
28914
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 },
28915
29425
  { prefix: "/api/_client-error", file: "server/routes/client-error.ts", subapp: client_error_default },
28916
29426
  { prefix: "/listings", file: "server/routes/listings.ts", subapp: listings_default },
28917
29427
  { prefix: "/v", file: "server/routes/visitor-event.ts", subapp: visitor_event_default },
@@ -28923,7 +29433,7 @@ for (const m of SUBAPP_MANIFEST) {
28923
29433
  }
28924
29434
  try {
28925
29435
  const registered = [];
28926
- for (const r of app63.routes ?? []) {
29436
+ for (const r of app64.routes ?? []) {
28927
29437
  if (typeof r.path !== "string" || r.path.includes(":") || r.path.includes("*")) continue;
28928
29438
  if (AGENT_SLUG_PATTERN.test(r.path)) {
28929
29439
  registered.push({ method: (r.method ?? "ALL").toUpperCase(), path: r.path });
@@ -28967,7 +29477,7 @@ async function runAdminUserReconcileTick() {
28967
29477
  console.error("[adminuser-self-heal] skip reason=no-users-file");
28968
29478
  return;
28969
29479
  }
28970
- const usersRaw = readFileSync41(USERS_FILE, "utf-8").trim();
29480
+ const usersRaw = readFileSync42(USERS_FILE, "utf-8").trim();
28971
29481
  if (!usersRaw) {
28972
29482
  console.error("[adminuser-self-heal] skip reason=empty-users-file");
28973
29483
  return;
@@ -29015,7 +29525,7 @@ registerLoop({
29015
29525
  var USERS_RECONCILE_INTERVAL_MS = 60 * 60 * 1e3;
29016
29526
  function countUsersRows() {
29017
29527
  if (!existsSync38(USERS_FILE)) return 0;
29018
- const raw = readFileSync41(USERS_FILE, "utf-8").trim();
29528
+ const raw = readFileSync42(USERS_FILE, "utf-8").trim();
29019
29529
  if (!raw) return 0;
29020
29530
  const users = JSON.parse(raw);
29021
29531
  return users.filter((u) => typeof u.userId === "string").length;
@@ -29148,7 +29658,7 @@ if (bootAccountConfig?.whatsapp) {
29148
29658
  }
29149
29659
  init({
29150
29660
  configDir: configDirForWhatsApp,
29151
- platformRoot: resolve36(process.env.MAXY_PLATFORM_ROOT ?? join42(__dirname, "..")),
29661
+ platformRoot: resolve37(process.env.MAXY_PLATFORM_ROOT ?? join44(__dirname, "..")),
29152
29662
  accountConfig: bootAccountConfig,
29153
29663
  onMessage: async (msg) => {
29154
29664
  if (msg.isOwnerMirror) {