@askexenow/exe-os 0.9.302 → 0.9.303

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 (299) hide show
  1. package/deploy/compose/README.md +7 -3
  2. package/deploy/compose/add-routes.sh +107 -0
  3. package/deploy/compose/docker-compose.yml +80 -14
  4. package/deploy/compose/gateway-watchdog.sh +223 -0
  5. package/deploy/compose/setup.sh +7 -8
  6. package/deploy/stack-manifests/v0.9.json +44 -1
  7. package/dist/{active-agent-GGQAP4QE.js → active-agent-F3TFM4GE.js} +4 -3
  8. package/dist/{active-agent-ODI6GGVD.js → active-agent-SJ2EH6WL.js} +4 -3
  9. package/dist/{agentic-ontology-M7T72M6V.js → agentic-ontology-B36CUQLH.js} +1 -1
  10. package/dist/{backfill-metadata-GSZP2BEW.js → backfill-metadata-HPLVG42J.js} +5 -4
  11. package/dist/{behaviors-JM6KFSXL.js → behaviors-KRP2Q5XL.js} +6 -5
  12. package/dist/bin/agentic-ontology-backfill.js +6 -5
  13. package/dist/bin/agentic-reflection-backfill.js +7 -6
  14. package/dist/bin/agentic-semantic-label.js +6 -5
  15. package/dist/bin/backfill-conversations.js +7 -6
  16. package/dist/bin/backfill-responses.js +7 -6
  17. package/dist/bin/backfill-vectors.js +9 -8
  18. package/dist/bin/bulk-sync-postgres.js +13 -12
  19. package/dist/bin/cc-doctor.js +5 -4
  20. package/dist/bin/cleanup-stale-review-tasks.js +13 -12
  21. package/dist/bin/cli.js +16 -16
  22. package/dist/bin/exe-agent-config.js +3 -2
  23. package/dist/bin/exe-agent.js +14 -13
  24. package/dist/bin/exe-assign.js +9 -8
  25. package/dist/bin/exe-boot.js +21 -20
  26. package/dist/bin/exe-call.js +5 -4
  27. package/dist/bin/exe-cloud.js +13 -12
  28. package/dist/bin/exe-config-dump.js +525 -0
  29. package/dist/bin/exe-dispatch.js +13 -12
  30. package/dist/bin/exe-doctor.js +2 -2
  31. package/dist/bin/exe-export-behaviors.js +8 -7
  32. package/dist/bin/exe-forget.js +7 -6
  33. package/dist/bin/exe-gateway.js +8 -7
  34. package/dist/bin/exe-healthcheck.js +7 -4
  35. package/dist/bin/exe-heartbeat.js +13 -12
  36. package/dist/bin/exe-kill.js +16 -15
  37. package/dist/bin/exe-launch-agent.js +40 -24
  38. package/dist/bin/exe-new-employee.js +9 -8
  39. package/dist/bin/exe-pending-messages.js +14 -13
  40. package/dist/bin/exe-pending-notifications.js +13 -12
  41. package/dist/bin/exe-pending-reviews.js +13 -12
  42. package/dist/bin/exe-rename.js +5 -4
  43. package/dist/bin/exe-review.js +15 -14
  44. package/dist/bin/exe-search-quality.js +282 -0
  45. package/dist/bin/exe-search.js +6 -5
  46. package/dist/bin/exe-session-cleanup.js +18 -17
  47. package/dist/bin/exe-settings.js +14 -13
  48. package/dist/bin/exe-start-codex.js +13 -12
  49. package/dist/bin/exe-start-opencode.js +9 -8
  50. package/dist/bin/exe-status.js +14 -13
  51. package/dist/bin/exe-support.js +3 -3
  52. package/dist/bin/exe-team.js +4 -3
  53. package/dist/bin/exe-timers.js +237 -0
  54. package/dist/bin/git-sweep.js +14 -13
  55. package/dist/bin/graph-backfill.js +7 -6
  56. package/dist/bin/graph-export.js +6 -5
  57. package/dist/bin/import-history.js +10 -9
  58. package/dist/bin/install-launchd.js +2 -2
  59. package/dist/bin/install.js +10 -9
  60. package/dist/bin/intercom-check.js +4 -4
  61. package/dist/bin/mcp-sessions.js +2 -2
  62. package/dist/bin/orchestration-metrics.js +5 -4
  63. package/dist/bin/postgres-agentic-reflection-backfill.js +2 -2
  64. package/dist/bin/postgres-agentic-semantic-backfill.js +1 -1
  65. package/dist/bin/scan-tasks.js +13 -12
  66. package/dist/bin/setup.js +1 -1
  67. package/dist/bin/shard-migrate.js +5 -4
  68. package/dist/bin/stack-update.js +4 -4
  69. package/dist/bin/vps-health-gate.js +1 -1
  70. package/dist/{capability-cards-BCTFKT4G.js → capability-cards-7FFJF4HF.js} +3 -2
  71. package/dist/{capacity-monitor-B4S3XEDO.js → capacity-monitor-G4XO7GNU.js} +14 -13
  72. package/dist/{catchup-brief-I35KHK62.js → catchup-brief-NBAN42X7.js} +16 -15
  73. package/dist/{chunk-HZEAAKCR.js → chunk-2DDRLHWF.js} +3 -3
  74. package/dist/{chunk-AAPPSMPH.js → chunk-2JESQBYU.js} +1 -1
  75. package/dist/{chunk-V6P7TPPA.js → chunk-3BE52GPB.js} +148 -2
  76. package/dist/{chunk-7FNELLMX.js → chunk-4GBTBZFC.js} +8 -8
  77. package/dist/{chunk-BEIYF4NS.js → chunk-4NWEVZKT.js} +3 -3
  78. package/dist/{chunk-MM7SWUQ4.js → chunk-4OTIVBY3.js} +1 -1
  79. package/dist/{chunk-MY36XDUK.js → chunk-4VHQ6QQ3.js} +1 -1
  80. package/dist/{chunk-4AN6KMVG.js → chunk-53LLDHLD.js} +1 -1
  81. package/dist/{chunk-YNALY3SX.js → chunk-5SYMJCBM.js} +1 -1
  82. package/dist/{chunk-7I43GVER.js → chunk-5Y2DO57X.js} +3 -3
  83. package/dist/{chunk-W65Z4SJS.js → chunk-624YJGMR.js} +1 -1
  84. package/dist/{chunk-3IJCP6ZH.js → chunk-6U6HNE2Y.js} +1 -1
  85. package/dist/{chunk-PBCRUNIK.js → chunk-75SBA2D4.js} +463 -137
  86. package/dist/{chunk-LD2JKEJT.js → chunk-7CMIKNAB.js} +4 -4
  87. package/dist/{chunk-RO7EXVSJ.js → chunk-7DAUFPDV.js} +1 -1
  88. package/dist/{chunk-KA5RK5HZ.js → chunk-7RGIYC7W.js} +2 -2
  89. package/dist/{chunk-OZRO37JW.js → chunk-7RMIXFMD.js} +4 -4
  90. package/dist/{chunk-EI7PBUIA.js → chunk-7WQ36FAD.js} +1 -1
  91. package/dist/{chunk-LTI2DLHY.js → chunk-ALVJ3CT3.js} +2 -2
  92. package/dist/{chunk-BUGMW7YF.js → chunk-B7BDXCKK.js} +11 -11
  93. package/dist/{chunk-P33JVGSP.js → chunk-BM3YRAE5.js} +1 -1
  94. package/dist/{chunk-OMCNFTSI.js → chunk-C5CR4XDV.js} +1 -1
  95. package/dist/{chunk-N76VTMMF.js → chunk-CCYRCRLW.js} +2 -2
  96. package/dist/{chunk-6X2YNVLB.js → chunk-CTIEJGO4.js} +3 -3
  97. package/dist/{chunk-WIL6LXNG.js → chunk-CTUQZ7LF.js} +1 -1
  98. package/dist/{chunk-35U72CD2.js → chunk-D3IYBLWN.js} +3 -3
  99. package/dist/{chunk-QNJPDIWY.js → chunk-D6INBP4F.js} +1 -1
  100. package/dist/{chunk-KHZQVLNZ.js → chunk-EW2P62CV.js} +46 -0
  101. package/dist/{chunk-OR7J7TIA.js → chunk-EWIW3R6V.js} +1 -1
  102. package/dist/{chunk-OVKNFTCL.js → chunk-FMJ4D5G6.js} +133 -22
  103. package/dist/{chunk-W3E4VEEJ.js → chunk-FTOUQEYH.js} +1 -1
  104. package/dist/{chunk-2LJDLQNO.js → chunk-G4SVYLPT.js} +1 -1
  105. package/dist/{chunk-AYMSIV7X.js → chunk-HDCP6CGI.js} +1 -1
  106. package/dist/{chunk-RHMWSUJB.js → chunk-HHKI2FBV.js} +2 -2
  107. package/dist/{chunk-LE7CDIOA.js → chunk-HU5SQUZX.js} +1 -1
  108. package/dist/{chunk-H2ISTRWU.js → chunk-HUO7WZFC.js} +1 -1
  109. package/dist/{chunk-HNLBHP6P.js → chunk-JAPPHNDZ.js} +3 -3
  110. package/dist/{chunk-TCJEFW5V.js → chunk-JBWYZAQE.js} +1 -1
  111. package/dist/{chunk-4LZ54YGA.js → chunk-JLOB72OV.js} +5 -5
  112. package/dist/{chunk-T62GAN7I.js → chunk-JPGU7XUA.js} +7 -7
  113. package/dist/{chunk-ZQ2QHGWE.js → chunk-KCJZJJJG.js} +20 -24
  114. package/dist/{chunk-KVHPBQV4.js → chunk-KLAVORDC.js} +1 -1
  115. package/dist/{chunk-ORNKGGFX.js → chunk-MDO5LEP7.js} +1 -1
  116. package/dist/{chunk-6ERZFDBS.js → chunk-MGKZPSXW.js} +1 -1
  117. package/dist/{chunk-PJTRAFL4.js → chunk-MO3G76UK.js} +3 -3
  118. package/dist/{chunk-P3SIFDES.js → chunk-N5G4T47B.js} +16 -4
  119. package/dist/{chunk-D6UICP3C.js → chunk-NME5E325.js} +81 -3
  120. package/dist/{chunk-ST3X3YMZ.js → chunk-NWUPOAFV.js} +4 -4
  121. package/dist/{chunk-US64UR5O.js → chunk-O2KITXII.js} +3 -3
  122. package/dist/{chunk-3YJMI422.js → chunk-OVQDDRGZ.js} +4 -4
  123. package/dist/{chunk-WHOT5NZ7.js → chunk-P4WXINQT.js} +14 -2
  124. package/dist/{chunk-RQUHAXMM.js → chunk-PHFPIUWU.js} +2 -0
  125. package/dist/{chunk-U724Z3S3.js → chunk-PNYUSBLA.js} +100 -37
  126. package/dist/{chunk-GSWGOGP5.js → chunk-PRU6NRJY.js} +5 -5
  127. package/dist/{chunk-SXJACGQ5.js → chunk-PTR4DP7H.js} +4 -4
  128. package/dist/{chunk-XKGNKH6V.js → chunk-PY6RG2DV.js} +1 -1
  129. package/dist/{chunk-QWLPAEG5.js → chunk-Q32XYR7C.js} +3 -3
  130. package/dist/{chunk-H7ZNSQ2E.js → chunk-QPA5UAXG.js} +2 -2
  131. package/dist/{chunk-YMSWCERY.js → chunk-QTDS7LFB.js} +3 -3
  132. package/dist/chunk-RQ7OVXUZ.js +81 -0
  133. package/dist/{chunk-IMJNYREY.js → chunk-S4OOC2EM.js} +1 -1
  134. package/dist/{chunk-Z3EXECOX.js → chunk-S76SQ6R5.js} +1 -1
  135. package/dist/{chunk-74TUYU5A.js → chunk-SCUUQ3VB.js} +1 -1
  136. package/dist/{chunk-7ZWC5HVG.js → chunk-UFYO4DLS.js} +1 -1
  137. package/dist/{chunk-ALH6QLYF.js → chunk-UI5WA22X.js} +2 -2
  138. package/dist/{chunk-BR74NYEF.js → chunk-UJJIFFIJ.js} +11 -11
  139. package/dist/{chunk-VME3UH4D.js → chunk-UN6KSHLI.js} +1 -1
  140. package/dist/{chunk-CXKR5Z5F.js → chunk-V7E4I7J5.js} +4 -0
  141. package/dist/chunk-VQRTO7IS.js +290 -0
  142. package/dist/{chunk-VDHUHXFF.js → chunk-VTMPER4B.js} +2 -2
  143. package/dist/{chunk-Y5ASPRP7.js → chunk-X37ENEN5.js} +17 -13
  144. package/dist/{chunk-TLOILHSL.js → chunk-XHJESIVW.js} +32 -14
  145. package/dist/chunk-XXJRZ75E.js +43 -0
  146. package/dist/{chunk-4FG6IX7U.js → chunk-Y4TYFJ37.js} +1 -1
  147. package/dist/{chunk-VLSYKMCJ.js → chunk-YHYWYSWK.js} +1 -1
  148. package/dist/{chunk-5YL4Y27D.js → chunk-YLAYKXX3.js} +1 -1
  149. package/dist/{chunk-RMRZXPUU.js → chunk-YLLUDO54.js} +1 -1
  150. package/dist/{token-budget-UMHRRDBT.js → chunk-ZXZU6NCE.js} +2 -9
  151. package/dist/{co-activation-OKAHEMPE.js → co-activation-W3DTVIXG.js} +3 -2
  152. package/dist/{co-occurrence-IHSPB53O.js → co-occurrence-JP6QO7DC.js} +5 -4
  153. package/dist/{code-context-index-KGY4LKCA.js → code-context-index-6SWHYPLZ.js} +3 -3
  154. package/dist/{conversation-entity-extractor-KXJSHRGJ.js → conversation-entity-extractor-KYOBXNID.js} +2 -2
  155. package/dist/{crdt-sync-K2G3C6XY.js → crdt-sync-V4MYKLOX.js} +1 -1
  156. package/dist/{crm-webhook-ERZSQ3CW.js → crm-webhook-PJZ37A2Q.js} +2 -2
  157. package/dist/{cto-delegation-gate-ZCJ53LBQ.js → cto-delegation-gate-72TPHIVT.js} +12 -11
  158. package/dist/daemon-auth-N6P2MZAV.js +17 -0
  159. package/dist/{daemon-orchestration-V6HALVDS.js → daemon-orchestration-7ESTK6HS.js} +16 -15
  160. package/dist/daemon-ready-signal-3L2MJT7C.js +13 -0
  161. package/dist/{db-backup-VZHSFUVA.js → db-backup-3CT2GJK2.js} +1 -1
  162. package/dist/{doc-graph-extractor-LQ2IFND2.js → doc-graph-extractor-ECZF4H6G.js} +5 -4
  163. package/dist/{dreaming-QAKUASYV.js → dreaming-BZZAZHL5.js} +13 -12
  164. package/dist/{exe-drift-H3UWNFFY.js → exe-drift-24KYGBPH.js} +4 -3
  165. package/dist/{exe-export-MN5D4JMT.js → exe-export-SONG5KUF.js} +7 -6
  166. package/dist/{exe-import-IFFMEVD7.js → exe-import-JJJL7E5D.js} +7 -6
  167. package/dist/{exe-key-RSZC72QK.js → exe-key-BRKHDMIX.js} +3 -2
  168. package/dist/{exe-snapshot-I622MPLS.js → exe-snapshot-436XVPEC.js} +16 -15
  169. package/dist/{fast-db-init-QEFVT7Q3.js → fast-db-init-4VL3ZQIH.js} +1 -1
  170. package/dist/{founder-context-3SOKEW76.js → founder-context-6KTVHGVC.js} +2 -2
  171. package/dist/gateway/index.js +18 -17
  172. package/dist/{git-staleness-E7RPQA5U.js → git-staleness-VFDGCAMO.js} +3 -2
  173. package/dist/{git-task-sweep-D3A2C5BW.js → git-task-sweep-KZ27TCNB.js} +13 -12
  174. package/dist/{global-procedures-POG4V6IK.js → global-procedures-LDKZ2IPH.js} +4 -3
  175. package/dist/{graph-auto-extract-E7KALNWA.js → graph-auto-extract-VA3SFHPF.js} +5 -4
  176. package/dist/{graph-rag-3RZN7JR3.js → graph-rag-HITZZ63L.js} +2 -2
  177. package/dist/hooks/bug-report-worker.js +15 -14
  178. package/dist/hooks/codex-stop-task-finalizer.js +15 -14
  179. package/dist/hooks/commit-complete.js +15 -14
  180. package/dist/hooks/error-recall.js +8 -7
  181. package/dist/hooks/exe-heartbeat-hook.js +4 -3
  182. package/dist/hooks/ingest-worker.js +3 -3
  183. package/dist/hooks/ingest.js +8 -7
  184. package/dist/hooks/instructions-loaded.js +5 -4
  185. package/dist/hooks/manifest.json +20 -20
  186. package/dist/hooks/notification.js +5 -4
  187. package/dist/hooks/post-compact.js +14 -13
  188. package/dist/hooks/post-tool-combined.js +7 -7
  189. package/dist/hooks/pre-compact.js +18 -17
  190. package/dist/hooks/pre-tool-use.js +18 -17
  191. package/dist/hooks/prompt-submit.js +28 -27
  192. package/dist/hooks/session-end.js +23 -22
  193. package/dist/hooks/session-start.js +46 -22
  194. package/dist/hooks/stop.js +22 -21
  195. package/dist/hooks/subagent-stop.js +14 -13
  196. package/dist/hooks/summary-worker.js +21 -20
  197. package/dist/index.js +25 -23
  198. package/dist/{installer-IXUX73JN.js → installer-7LYVKNUK.js} +10 -9
  199. package/dist/{installer-RCUPPZ7Y.js → installer-B6PUJTSR.js} +10 -9
  200. package/dist/{installer-HDD6OH5Z.js → installer-MSAJOOHK.js} +7 -6
  201. package/dist/lib/cloud-sync.js +13 -12
  202. package/dist/lib/consolidation.js +8 -7
  203. package/dist/lib/database.js +3 -2
  204. package/dist/lib/db-daemon-client.js +3 -3
  205. package/dist/lib/db.js +3 -2
  206. package/dist/lib/device-registry.js +3 -1
  207. package/dist/lib/embedder.js +3 -3
  208. package/dist/lib/employee-templates.js +5 -4
  209. package/dist/lib/employees.js +3 -2
  210. package/dist/lib/exe-daemon-client.js +2 -2
  211. package/dist/lib/exe-daemon.js +252 -402
  212. package/dist/lib/hybrid-search.js +6 -5
  213. package/dist/lib/identity.js +3 -2
  214. package/dist/lib/license.js +2 -2
  215. package/dist/lib/messaging.js +13 -12
  216. package/dist/lib/reminders.js +4 -3
  217. package/dist/lib/schedules.js +6 -5
  218. package/dist/lib/session-registry.js +5 -4
  219. package/dist/lib/skill-learning.js +7 -6
  220. package/dist/lib/store.js +7 -4
  221. package/dist/lib/task-router.js +4 -3
  222. package/dist/lib/tasks.js +14 -13
  223. package/dist/lib/tmux-routing.js +16 -11
  224. package/dist/lib/token-spend.js +4 -3
  225. package/dist/{license-gate-V2UCK4KJ.js → license-gate-U7TC44UX.js} +3 -3
  226. package/dist/mcp/register-tools.js +70 -69
  227. package/dist/mcp/server.js +73 -72
  228. package/dist/mcp/tools/complete-reminder.js +5 -4
  229. package/dist/mcp/tools/create-reminder.js +5 -4
  230. package/dist/mcp/tools/create-task.js +16 -15
  231. package/dist/mcp/tools/deactivate-behavior.js +8 -7
  232. package/dist/mcp/tools/list-reminders.js +5 -4
  233. package/dist/mcp/tools/list-tasks.js +16 -15
  234. package/dist/mcp/tools/send-message.js +15 -14
  235. package/dist/mcp/tools/update-task.js +15 -14
  236. package/dist/{mcp-http-config-7LFFPNGV.js → mcp-http-config-DKDEXFVS.js} +4 -3
  237. package/dist/{memory-cards-BGI6MJRN.js → memory-cards-OMPYI2GY.js} +3 -2
  238. package/dist/{memory-graph-extractor-DTRRNWHZ.js → memory-graph-extractor-QARQCG53.js} +6 -5
  239. package/dist/{memory-poisoning-defense-MOCWJK6S.js → memory-poisoning-defense-4J5CPLJT.js} +3 -2
  240. package/dist/{memory-queue-client-DD4E5SIZ.js → memory-queue-client-2NURVA62.js} +3 -3
  241. package/dist/{memory-reflection-UKKTGZYR.js → memory-reflection-KSOFEZNV.js} +3 -2
  242. package/dist/{notifications-AMJ2K3MM.js → notifications-FAHXEFR2.js} +12 -11
  243. package/dist/{orchestration-events-MB4QVHTD.js → orchestration-events-2MVWZ2ET.js} +4 -3
  244. package/dist/orchestration-health-read-EJWVUARG.js +191 -0
  245. package/dist/{orchestrator-M7DJH6TN.js → orchestrator-DMM6AJVL.js} +14 -13
  246. package/dist/{pipeline-router-4LT5YG5P.js → pipeline-router-M3ESJZYK.js} +4 -3
  247. package/dist/{plan-limits-ENS7OJWP.js → plan-limits-BDOAH4DJ.js} +6 -5
  248. package/dist/{project-boot-6W5JPGSB.js → project-boot-DQZXRY5C.js} +25 -1
  249. package/dist/{projection-worker-WWXZK7QD.js → projection-worker-UBPNRKPX.js} +2 -2
  250. package/dist/{prospective-memory-VNGLPMS6.js → prospective-memory-OALGUYBS.js} +3 -2
  251. package/dist/{reranker-6XBS55FA.js → reranker-CTZ4SYCN.js} +1 -1
  252. package/dist/{retrieval-health-ATX5TIQ2.js → retrieval-health-P4ZMY4H3.js} +1 -1
  253. package/dist/{review-polling-E4ALHTQE.js → review-polling-VQUPLRWQ.js} +13 -12
  254. package/dist/runtime/index.js +20 -18
  255. package/dist/{session-events-3J6APMKN.js → session-events-4GHYBXXH.js} +13 -12
  256. package/dist/session-kill-telemetry-S445CSOH.js +63 -0
  257. package/dist/{session-scope-E54VIYTJ.js → session-scope-PPGKSJEL.js} +12 -11
  258. package/dist/{setup-wizard-XV736U5D.js → setup-wizard-ERES36LR.js} +1 -1
  259. package/dist/shared-rate-limit-store-QZPQEVE2.js +80 -0
  260. package/dist/{skill-refinement-FTURVWFN.js → skill-refinement-KITOKH6T.js} +3 -2
  261. package/dist/{stack-update-BIK2QHOE.js → stack-update-V7AJ425P.js} +5 -3
  262. package/dist/{steward-gate-YGU4VXPP.js → steward-gate-VLN2FTQD.js} +4 -3
  263. package/dist/{task-enforcement-GY3VTY6L.js → task-enforcement-XAPBTPBF.js} +12 -11
  264. package/dist/{task-scope-PYOK7OQ6.js → task-scope-ZGFHVDG5.js} +12 -11
  265. package/dist/{tasks-crud-HLJEJ2UU.js → tasks-crud-KIZREZSN.js} +12 -11
  266. package/dist/{tasks-notify-NGS3DWUJ.js → tasks-notify-3W3YIVF4.js} +13 -12
  267. package/dist/{tasks-review-BP5V2X34.js → tasks-review-F5PPITAJ.js} +12 -11
  268. package/dist/{telemetry-upload-AXYPFAPS.js → telemetry-upload-ZUFI5VH4.js} +8 -7
  269. package/dist/token-budget-7BD6ROHF.js +16 -0
  270. package/dist/{tool-capability-index-RHPVMMDD.js → tool-capability-index-335P663V.js} +1 -1
  271. package/dist/{tool-telemetry-BLSVABFJ.js → tool-telemetry-P6XVPFOF.js} +1 -1
  272. package/dist/tui/App.js +20 -19
  273. package/dist/{tui-data-EYPOFYLS.js → tui-data-QWJVWVTP.js} +12 -11
  274. package/dist/{worker-gate-SMFOBMMX.js → worker-gate-IHCMOXDF.js} +1 -1
  275. package/dist/{workflow-engine-TDJZ3TXB.js → workflow-engine-PJ7JAO6E.js} +2 -2
  276. package/dist/{worktree-XISIYRM4.js → worktree-4BR3ZQWN.js} +5 -4
  277. package/dist/{worktree-sweep-AS6GAR6X.js → worktree-sweep-XCPOF3RD.js} +6 -5
  278. package/package.json +8 -4
  279. package/release-notes.json +50 -88
  280. package/stack.release.json +47 -0
  281. package/dist/chunk-PHOQAVTK.js +0 -42
  282. package/dist/daemon-auth-BK7ON5ZH.js +0 -13
  283. package/dist/session-kill-telemetry-KOAHLRBT.js +0 -31
  284. package/dist/{chunk-NLHUA3YB.js → chunk-6JJBGPMF.js} +0 -0
  285. package/dist/{chunk-XDE5IGBX.js → chunk-ERAHOO7R.js} +0 -0
  286. package/dist/{chunk-LUGWK6QB.js → chunk-FF5DA4XP.js} +3 -3
  287. /package/dist/{chunk-FKFVNV7X.js → chunk-KYF3KSFT.js} +0 -0
  288. /package/dist/{chunk-GZSG2ZOX.js → chunk-O573H7LS.js} +0 -0
  289. /package/dist/{chunk-JTMKRUX6.js → chunk-PCU4ZMUA.js} +0 -0
  290. /package/dist/{chunk-PTDBHQX7.js → chunk-PGQMMD5P.js} +0 -0
  291. /package/dist/{chunk-GBV5PV5T.js → chunk-PP3RXZID.js} +0 -0
  292. /package/dist/{chunk-BGOMFGSW.js → chunk-Q5V3B6BP.js} +0 -0
  293. /package/dist/{chunk-UZATXREL.js → chunk-YH2IYVPW.js} +0 -0
  294. /package/dist/{chunk-YM3367VC.js → chunk-Z77S2QQU.js} +0 -0
  295. /package/dist/{core-memory-4OM7LR77.js → core-memory-VIWP7GJL.js} +0 -0
  296. /package/dist/{entity-boost-SFHIRXT5.js → entity-boost-VZ7ZEDVR.js} +0 -0
  297. /package/dist/{message-queue-client-QTC3VHJT.js → message-queue-client-32HOVUK7.js} +0 -0
  298. /package/dist/{oauth-server-BPN55EJM.js → oauth-server-V4H3SY7H.js} +0 -0
  299. /package/dist/{wiki-acl-45SIGG4O.js → wiki-acl-HNP5GGDA.js} +0 -0
@@ -6,9 +6,13 @@ Customer VPS deployments (Hygo/High/etc.) must start from `.env.customer.example
6
6
 
7
7
  Full production stack for a single client VPS: CRM + wiki + gateway + exed
8
8
  backed by Postgres, ClickHouse, and Redis. Pinned image tags, healthchecks on
9
- every service, named volumes for persistence, and host-nginx-friendly port
9
+ all core services, named volumes for persistence, and host-nginx-friendly port
10
10
  publishing on `127.0.0.1`.
11
11
 
12
+ > **Note:** `exe-otel-collector` does not have a container-level healthcheck
13
+ > (scratch image with no shell) — it exposes a health extension on `:13133`
14
+ > which is monitored externally.
15
+
12
16
  This is the **Lane A-2** deliverable from the v1.6 execution plan
13
17
  (`exe/output/v16-execution-plan.md`). Pairs with **Lane A-1** Ansible roles
14
18
  which provision the host and install nginx-tls; the `nginx-tls` role includes
@@ -56,8 +60,8 @@ nginx (Lane A-1 `nginx-tls`) can reverse-proxy them. Override the host port via
56
60
 
57
61
  `exe-monitor-agent` reports host/container health to the monitoring hub, but it
58
62
  needs **sensitive read-only host access** to do so: `/var/run/docker.sock`
59
- (root-equivalent — can inspect every container), `/proc`, `/sys`, and
60
- `/etc/os-release`. Because that access is effectively root on the box, the agent
63
+ (root-equivalent — can inspect every container) and `/etc/os-release`.
64
+ Because that access is effectively root on the box, the agent
61
65
  is **gated behind the `monitor-agent` compose profile** and does **not** start by
62
66
  default.
63
67
 
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env bash
2
+ # exe-os overlay-apply / post-deploy hook (add-routes.sh)
3
+ #
4
+ # Re-applies CUSTOMER OVERLAY files into the exe-gateway container after every
5
+ # image upgrade / container recreate, then reloads the gateway so the overlays
6
+ # take effect.
7
+ #
8
+ # ─────────────────────────────────────────────────────────────────────────────
9
+ # BUG HISTORY (02bc1bb8, P1)
10
+ # ─────────────────────────────────────────────────────────────────────────────
11
+ # On a stack image upgrade the gateway container is RECREATED. Anything that lived
12
+ # only inside the old container's writable layer (a customer overlay such as
13
+ # po-intake.js, copied in by hand) is DROPPED with no warning. HYGO's working
14
+ # po-intake.js overlay — 37 orders processed — was killed on v0.9.17 → v0.9.22.
15
+ # The intended post-deploy hook (this file) shipped as a 0-BYTE empty file, and
16
+ # /opt/exe-stack/overlay/ existed on the host but was never mounted into the
17
+ # container, so nothing re-applied it.
18
+ #
19
+ # This script + the compose bind-mount (`./overlay:/data/overlay:ro`) close the
20
+ # gap two ways:
21
+ # (a) overlays survive recreate via the persistent host bind-mount, AND
22
+ # (b) this hook re-applies them into the container's app dir + reloads after
23
+ # every recreate. Idempotent + safe: a no-op when no overlays exist.
24
+ #
25
+ # Mechanism:
26
+ # Host overlay dir : ${OVERLAY_DIR:-/opt/exe-stack/overlay}
27
+ # In-container view : /data/overlay (read-only bind-mount, always present)
28
+ # Applied into : ${OVERLAY_TARGET:-/app/overlay} inside the container
29
+ #
30
+ # Overlays are loaded by the gateway from EXE_GATEWAY_OVERLAY_DIR (defaults to
31
+ # /data/overlay — the bind-mount — so on a modern gateway no copy is even needed;
32
+ # the copy below is belt-and-suspenders for gateway builds that load from /app).
33
+
34
+ set -uo pipefail
35
+
36
+ STACK_DIR="${EXE_STACK_DIR:-/opt/exe-stack}"
37
+ OVERLAY_DIR="${OVERLAY_DIR:-${STACK_DIR}/overlay}"
38
+ CONTAINER="${EXE_GATEWAY_CONTAINER:-exe-gateway}"
39
+ # Where the gateway image expects overlays if it does NOT read the bind-mount directly.
40
+ OVERLAY_TARGET="${OVERLAY_TARGET:-/app/overlay}"
41
+
42
+ ts() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
43
+ log() { echo "[$(ts)] add-routes: $*"; }
44
+
45
+ # Nothing to do if there is no overlay dir or it is empty. This is the common
46
+ # (no customer overlay) case — exit cleanly so the post-deploy step is a no-op.
47
+ if [[ ! -d "$OVERLAY_DIR" ]]; then
48
+ log "no overlay dir at ${OVERLAY_DIR} — nothing to apply."
49
+ exit 0
50
+ fi
51
+
52
+ shopt -s nullglob dotglob
53
+ overlay_files=("$OVERLAY_DIR"/*)
54
+ shopt -u nullglob dotglob
55
+ if [[ ${#overlay_files[@]} -eq 0 ]]; then
56
+ log "overlay dir ${OVERLAY_DIR} is empty — nothing to apply."
57
+ exit 0
58
+ fi
59
+
60
+ # Gateway must be running to receive the overlay copy + reload.
61
+ running=$(docker inspect --format '{{.State.Running}}' "$CONTAINER" 2>/dev/null || echo "false")
62
+ if [[ "$running" != "true" ]]; then
63
+ log "WARN gateway container ${CONTAINER} is not running — cannot apply overlays now. They remain at ${OVERLAY_DIR} and the read-only bind-mount (/data/overlay) still exposes them once the gateway is up."
64
+ exit 0
65
+ fi
66
+
67
+ log "applying ${#overlay_files[@]} overlay file(s) from ${OVERLAY_DIR} into ${CONTAINER}:${OVERLAY_TARGET}"
68
+
69
+ # Ensure the in-container target dir exists, then copy each overlay file in.
70
+ # `docker cp` works even on a read-only-bind-mounted source because we copy from
71
+ # the HOST path, not from the (ro) /data/overlay mount.
72
+ docker exec "$CONTAINER" mkdir -p "$OVERLAY_TARGET" >/dev/null 2>&1 || {
73
+ log "ERROR could not create ${OVERLAY_TARGET} inside ${CONTAINER}."
74
+ exit 1
75
+ }
76
+
77
+ applied=0
78
+ for f in "${overlay_files[@]}"; do
79
+ [[ -f "$f" ]] || continue # skip sub-directories for the flat copy
80
+ base=$(basename "$f")
81
+ if docker cp "$f" "${CONTAINER}:${OVERLAY_TARGET}/${base}" >/dev/null 2>&1; then
82
+ log " applied ${base}"
83
+ applied=$((applied + 1))
84
+ else
85
+ log " ERROR failed to apply ${base}"
86
+ fi
87
+ done
88
+
89
+ if [[ "$applied" -eq 0 ]]; then
90
+ log "no overlay files applied."
91
+ exit 0
92
+ fi
93
+
94
+ # Reload the gateway so the overlays take effect. A graceful in-container reload is
95
+ # preferred; fall back to a container restart if the image exposes no reload hook.
96
+ # (The restart here is INTENTIONAL + one-shot after a deploy — not the watchdog
97
+ # loop — and only happens when overlays were actually applied.)
98
+ if docker exec "$CONTAINER" sh -c 'test -x /app/reload.sh' >/dev/null 2>&1; then
99
+ log "reloading gateway via /app/reload.sh"
100
+ docker exec "$CONTAINER" /app/reload.sh >/dev/null 2>&1 || log "WARN /app/reload.sh exited non-zero"
101
+ else
102
+ log "no in-container reload hook; restarting ${CONTAINER} once to load overlays"
103
+ docker restart "$CONTAINER" >/dev/null 2>&1 || log "WARN docker restart ${CONTAINER} failed"
104
+ fi
105
+
106
+ log "applied ${applied} overlay file(s)."
107
+ exit 0
@@ -3,10 +3,10 @@
3
3
  # Services: exe-db (postgres) + clickhouse + redis + exe-crm + exe-wiki + exe-os + exe-gateway + exe-erp + otel-collector
4
4
  # ONE postgres (exe-db) — all services connect to it via DATABASE_URL.
5
5
  # Optional: exe-monitor-agent reports fleet health to the monitoring hub. It needs
6
- # sensitive host access (docker.sock, /proc, /sys) so it is gated behind the
6
+ # sensitive host access (docker.sock, /etc/os-release) so it is gated behind the
7
7
  # "monitor-agent" compose profile and stays OFF until the operator consents.
8
8
  # See deploy/monitor/HOST-ACCESS-CONSENT.md.
9
- # All image tags pinned per-client via .env (no :latest). Healthchecks on every service.
9
+ # All image tags pinned per-client via .env (no :latest). Healthchecks on all core services.
10
10
  # Named volumes for state; explicit subnets; depends_on with service_healthy gates.
11
11
  #
12
12
  # Validate without secrets:
@@ -26,7 +26,10 @@ services:
26
26
  # ------------------------------------------------------------------
27
27
 
28
28
  exe-db:
29
- image: ${EXE_DB_IMAGE:-pgvector/pgvector:pg16}
29
+ # bug ee16e7ef: pin a versioned tag, not the mutable :pg16 rolling tag.
30
+ # pgvector/pgvector:pg16 can change upstream without notice; 0.8.0-pg16
31
+ # is the tested stable release. Override via EXE_DB_IMAGE in .env.
32
+ image: ${EXE_DB_IMAGE:-pgvector/pgvector:0.8.0-pg16}
30
33
  container_name: exe-db
31
34
  restart: unless-stopped
32
35
  # SECURITY (bug 67d62490): no blanket `env_file: .env`. Each service
@@ -236,17 +239,21 @@ services:
236
239
  image: ${MONITOR_HUB_IMAGE_TAG:-ghcr.io/askexe/exe-monitor-hub:v0.9.4}
237
240
  container_name: exe-monitor-hub
238
241
  restart: unless-stopped
239
- # bug d3d49101: PocketBase's data dir for this hub is a RELATIVE path
240
- # (DefaultDataDir="exe-monitor_data", see exe-monitor internal/cmd/hub).
241
- # The scratch image runs the binary from / with no WORKDIR, so it would
242
- # persist to /exe-monitor_data NOT /app/pb_data and NOT /beszel_data.
243
- # Pin it explicitly with --dir=/beszel_data so the binary writes to the same
244
- # path the named volume is mounted at (and the same path the image VOLUME
245
- # declares), so data survives container recreation.
242
+ # bug d3d49101 / 1fdb8be9: PocketBase's data dir for this hub is a RELATIVE
243
+ # path (DefaultDataDir="exe-monitor_data", see exe-monitor internal/cmd/hub).
244
+ # The image's WORKDIR is /app, so DefaultDataDir resolves to /app/exe-monitor_data.
245
+ # Pin it explicitly with --dir=/app/exe-monitor_data so the binary writes to
246
+ # the same path the named volume is mounted at, so data survives container
247
+ # recreation. Previous bug: mount was at /beszel_data but data went to
248
+ # /app/exe-monitor_data writable layer wiped on every recreate.
246
249
  # bug 182c6da3 / a125785d: EXE_MONITOR_KEY enables /api/exe-monitor/errors
247
250
  # ingestion (fail-closed in the hub when unset) and GATEWAY_ALERT_URL enables
248
251
  # error-spike alerts (silently disabled in the hub when unset).
249
- command: ["serve", "--http=0.0.0.0:8090", "--dir=/beszel_data"]
252
+ # bug 1fdb8be9: the custom hub binary resolves DefaultDataDir="exe-monitor_data"
253
+ # relative to the WORKDIR (/app), so actual data lives at /app/exe-monitor_data.
254
+ # --dir must point there (NOT /beszel_data) and the volume must mount there too,
255
+ # otherwise container recreates wipe all monitor history.
256
+ command: ["serve", "--http=0.0.0.0:8090", "--dir=/app/exe-monitor_data"]
250
257
  environment:
251
258
  EXE_MONITOR_ADMIN_TOKEN: ${EXE_MONITOR_ADMIN_TOKEN:-}
252
259
  # bug a125785d: shared secret for POST /api/exe-monitor/errors. The hub
@@ -266,7 +273,11 @@ services:
266
273
  ports:
267
274
  - "127.0.0.1:${MONITOR_HUB_PORT:-8090}:8090"
268
275
  volumes:
269
- - monitor_hub_data:/beszel_data
276
+ # bug 1fdb8be9: mount at the ACTUAL data dir the hub binary writes to.
277
+ # Was /beszel_data (stock Beszel path), but the exe-monitor fork uses
278
+ # /app/exe-monitor_data. Mismatch meant data lived in the writable layer
279
+ # and was lost on every container recreate / stack-update.
280
+ - monitor_hub_data:/app/exe-monitor_data
270
281
  networks:
271
282
  - backend
272
283
  healthcheck:
@@ -329,6 +340,11 @@ services:
329
340
  NODE_PORT: "3000"
330
341
  EXE_LICENSE_KEY: ${EXE_LICENSE_KEY:?EXE_LICENSE_KEY is required — purchase at https://askexe.com}
331
342
  SERVER_URL: ${CRM_SERVER_URL:-https://crm.askexe.com}
343
+ # bug b99d3762 / 83192765: the CRM error-forwarder POSTs to the monitor hub and must
344
+ # send X-Monitor-Key once the hub requires it on every path (mirrors gateway/erp).
345
+ ERROR_REPORTING_ENABLED: "true"
346
+ MONITOR_ERROR_URL: http://exe-monitor-hub:8090/api/exe-monitor/errors
347
+ MONITOR_API_KEY: ${MONITOR_API_KEY:-${EXE_MONITOR_KEY:-}}
332
348
  APP_SECRET: ${CRM_APP_SECRET:?CRM_APP_SECRET is required}
333
349
  EXE_CRM_ADMIN_TOKEN: ${EXE_CRM_ADMIN_TOKEN:-}
334
350
  EXE_CRM_ADMIN_EMAIL: ${EXE_CRM_ADMIN_EMAIL:-}
@@ -444,6 +460,11 @@ services:
444
460
  SERVER_PORT: "3001"
445
461
  EXE_LICENSE_KEY: ${EXE_LICENSE_KEY:?EXE_LICENSE_KEY is required — purchase at https://askexe.com}
446
462
  STORAGE_DIR: /app/server/storage
463
+ # bug b99d3762 / 83192765: the wiki error-reporter POSTs to the monitor hub and must
464
+ # send X-Monitor-Key once the hub requires it on every path (mirrors gateway/erp).
465
+ ERROR_REPORTING_ENABLED: "true"
466
+ MONITOR_ERROR_URL: http://exe-monitor-hub:8090/api/exe-monitor/errors
467
+ MONITOR_API_KEY: ${MONITOR_API_KEY:-${EXE_MONITOR_KEY:-}}
447
468
  DATABASE_URL: postgres://${POSTGRES_USER:-exe}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@exe-db:5432/${POSTGRES_DB:-exedb}?schema=${WIKI_DB_SCHEMA:-wiki}&sslmode=disable
448
469
  AUTH_TOKEN: ${WIKI_AUTH_TOKEN:?WIKI_AUTH_TOKEN is required}
449
470
  EXE_WIKI_ADMIN_TOKEN: ${EXE_WIKI_ADMIN_TOKEN:-}
@@ -597,6 +618,14 @@ services:
597
618
  - exe_os_data:/home/exed/.exe-os
598
619
  networks:
599
620
  - backend
621
+ healthcheck:
622
+ # The worker is a long-running node process (embed-worker.js). Verify the
623
+ # PID-1 node process is alive via /proc/1/cmdline.
624
+ test: ["CMD-SHELL", "test -f /proc/1/cmdline"]
625
+ interval: 30s
626
+ timeout: 5s
627
+ start_period: 30s
628
+ retries: 3
600
629
  deploy:
601
630
  resources:
602
631
  limits:
@@ -637,6 +666,13 @@ services:
637
666
  NODE_ENV: production
638
667
  EXE_GATEWAY_HOME: /data
639
668
  EXE_GATEWAY_CONFIG: /data/gateway.json
669
+ # bug 02bc1bb8: customer overlay dir. The host ./overlay is bind-mounted
670
+ # read-only at /data/overlay (see volumes below) so customer overlays
671
+ # (e.g. po-intake.js) SURVIVE every image upgrade / container recreate
672
+ # instead of being silently dropped from the old container layer. The
673
+ # gateway loads overlays from this dir; the post-deploy add-routes.sh hook
674
+ # additionally re-applies them for gateway builds that load from /app.
675
+ EXE_GATEWAY_OVERLAY_DIR: /data/overlay
640
676
  DATABASE_URL: postgres://${POSTGRES_USER:-exe}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@exe-db:5432/${POSTGRES_DB:-exedb}?sslmode=disable
641
677
  EXE_GATEWAY_PORT: "3100"
642
678
  EXE_GATEWAY_HOST: "0.0.0.0"
@@ -675,6 +711,14 @@ services:
675
711
  - gateway_data:/data
676
712
  - gateway_whatsapp_auth:/app/auth_info
677
713
  - ./gateway.json:/data/gateway.json:ro
714
+ # bug 02bc1bb8: PERSISTENT customer-overlay bind-mount. Anything the
715
+ # customer drops in the host ./overlay dir (e.g. po-intake.js) is exposed
716
+ # read-only inside the container at /data/overlay and therefore SURVIVES
717
+ # image upgrades / `docker compose up` recreate — unlike files that only
718
+ # ever lived in the container's writable layer (which a recreate drops).
719
+ # bootstrapStackHost ensures ./overlay exists so docker never auto-creates
720
+ # the bind path as a stray file. Empty for customers with no overlay.
721
+ - ./overlay:/data/overlay:ro
678
722
  networks:
679
723
  - backend
680
724
  - frontend
@@ -718,8 +762,9 @@ services:
718
762
  exe-monitor-hub:
719
763
  condition: service_healthy
720
764
  environment:
721
- # Reports to the local hub by default. stack-update auto-pairs this agent
722
- # with the hub via /api/register-agent and writes TOKEN + KEY back to .env.
765
+ # Reports to the local hub by default. The agent must be paired manually
766
+ # from the hub UI (Settings Add System) the hub does not expose a
767
+ # REST registration endpoint. Write the generated TOKEN + KEY into .env.
723
768
  HUB_URL: ${MONITOR_HUB_URL:-http://exe-monitor-hub:8090}
724
769
  # TOKEN and KEY are written automatically by `exe-os stack-update` during
725
770
  # bootstrap. They start empty; the agent retries connecting until the hub
@@ -1154,6 +1199,13 @@ services:
1154
1199
  - erp_assets:/home/frappe/frappe-bench/sites/assets
1155
1200
  networks:
1156
1201
  - backend
1202
+ healthcheck:
1203
+ # Verify the socketio node process is listening on port 9000.
1204
+ test: ["CMD-SHELL", "node -e \"const s=require('net').connect(9000,'127.0.0.1',()=>{s.destroy();process.exit(0)});s.setTimeout(4000,()=>{s.destroy();process.exit(1)});s.on('error',()=>process.exit(1))\""]
1205
+ interval: 30s
1206
+ timeout: 5s
1207
+ start_period: 30s
1208
+ retries: 3
1157
1209
  deploy:
1158
1210
  resources:
1159
1211
  limits:
@@ -1193,6 +1245,13 @@ services:
1193
1245
  - erp_assets:/home/frappe/frappe-bench/sites/assets
1194
1246
  networks:
1195
1247
  - backend
1248
+ healthcheck:
1249
+ # Verify the bench worker process is alive and Redis queue is reachable.
1250
+ test: ["CMD-SHELL", "pgrep -f 'rq worker' > /dev/null"]
1251
+ interval: 30s
1252
+ timeout: 5s
1253
+ start_period: 30s
1254
+ retries: 3
1196
1255
  deploy:
1197
1256
  resources:
1198
1257
  limits:
@@ -1232,6 +1291,13 @@ services:
1232
1291
  - erp_assets:/home/frappe/frappe-bench/sites/assets
1233
1292
  networks:
1234
1293
  - backend
1294
+ healthcheck:
1295
+ # Verify the bench schedule process is alive.
1296
+ test: ["CMD-SHELL", "pgrep -f 'bench schedule' > /dev/null"]
1297
+ interval: 30s
1298
+ timeout: 5s
1299
+ start_period: 30s
1300
+ retries: 3
1235
1301
  deploy:
1236
1302
  resources:
1237
1303
  limits:
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env bash
2
+ # exe-os gateway watchdog — verifies the exe-gateway WhatsApp adapter is actually
3
+ # CONNECTED (not just that the HTTP server answers) and restarts the gateway only
4
+ # when it is genuinely wedged.
5
+ #
6
+ # Install (cron, every 2 min):
7
+ # chmod +x /opt/exe-stack/gateway-watchdog.sh
8
+ # crontab -l | { cat; echo "*/2 * * * * /opt/exe-stack/gateway-watchdog.sh 2>&1 | logger -t exe-gw-watchdog"; } | crontab -
9
+ #
10
+ # ─────────────────────────────────────────────────────────────────────────────
11
+ # BUG HISTORY (facd5078, P0)
12
+ # ─────────────────────────────────────────────────────────────────────────────
13
+ # The previous watchdog curled the UNAUTHENTICATED http://localhost:3100/health
14
+ # and grepped for `"connected":true`. But the unauth /health only returns
15
+ # {status, uptime, adapterCount} — it carries NO adapter connection state — so the
16
+ # grep ALWAYS failed → 3 strikes every ~6 min → `docker restart exe-gateway`
17
+ # 24/7 (1,668 restarts in 72h on HYGO). `docker restart` does NOT increment the
18
+ # container RestartCount, which masked the loop entirely.
19
+ #
20
+ # This rewrite makes the check ACCURATE and FAIL-SAFE:
21
+ # 1. AUTHENTICATED probe of /admin/webhooks/health (Bearer EXE_GATEWAY_AUTH_TOKEN),
22
+ # which reports real per-adapter state. Preferred — correct regardless of
23
+ # gateway version.
24
+ # 2. If the running gateway already exposes `adapters_connected` on the UNAUTH
25
+ # /health (exe-gateway bug 7e9fc713), that is honored too — but we do NOT
26
+ # hard-depend on it shipping first.
27
+ # 3. FAIL SAFE: if EXE_GATEWAY_AUTH_TOKEN is unset, OR the health body cannot be
28
+ # parsed into a definite connected/disconnected verdict, the result is
29
+ # INCONCLUSIVE → we LOG and do NOT restart. A watchdog must never restart on
30
+ # an unknown.
31
+ # 4. STARTUP GRACE: the gateway is given GRACE_SECS of container uptime before
32
+ # any strike is counted, so a gateway that is merely (re)starting / pairing
33
+ # WhatsApp is never restart-looped.
34
+ # 5. BACKOFF: after a restart we refuse to restart again for COOLDOWN_SECS, so we
35
+ # never hammer a gateway that needs time to reconnect.
36
+
37
+ set -uo pipefail
38
+
39
+ # ── Config ───────────────────────────────────────────────────────────────────
40
+ STACK_DIR="${EXE_STACK_DIR:-/opt/exe-stack}"
41
+ ENV_FILE="${EXE_STACK_ENV_FILE:-${STACK_DIR}/.env}"
42
+ CONTAINER="${EXE_GATEWAY_CONTAINER:-exe-gateway}"
43
+ PORT="${EXE_GATEWAY_HTTP_PORT:-3100}"
44
+ HOST="${EXE_GATEWAY_HOST:-127.0.0.1}"
45
+ TIMEOUT="${EXE_GATEWAY_WATCHDOG_TIMEOUT:-5}"
46
+
47
+ # Strikes before a restart. Each cron tick = one probe.
48
+ MAX_STRIKES="${EXE_GATEWAY_WATCHDOG_STRIKES:-3}"
49
+ # Don't count strikes until the container has been up at least this long.
50
+ GRACE_SECS="${EXE_GATEWAY_WATCHDOG_GRACE_SECS:-120}"
51
+ # After a restart, refuse to restart again for this long (let it reconnect).
52
+ COOLDOWN_SECS="${EXE_GATEWAY_WATCHDOG_COOLDOWN_SECS:-300}"
53
+
54
+ STATE_DIR="${EXE_GATEWAY_WATCHDOG_STATE_DIR:-${STACK_DIR}/.gateway-watchdog}"
55
+ STRIKE_FILE="${STATE_DIR}/strikes"
56
+ LAST_RESTART_FILE="${STATE_DIR}/last-restart"
57
+
58
+ mkdir -p "$STATE_DIR" 2>/dev/null || true
59
+
60
+ ts() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
61
+ log() { echo "[$(ts)] gateway-watchdog: $*"; }
62
+
63
+ # Read a single KEY=VALUE from the stack .env WITHOUT sourcing it (the .env holds
64
+ # many secrets and arbitrary values; sourcing could execute / mangle them).
65
+ read_env() {
66
+ local key="$1"
67
+ [[ -f "$ENV_FILE" ]] || return 0
68
+ # Last assignment wins; strip surrounding quotes; ignore comments.
69
+ sed -n "s/^[[:space:]]*${key}=//p" "$ENV_FILE" 2>/dev/null | tail -n1 \
70
+ | sed -e 's/^"\(.*\)"$/\1/' -e "s/^'\(.*\)'$/\1/"
71
+ }
72
+
73
+ reset_strikes() { echo 0 > "$STRIKE_FILE" 2>/dev/null || true; }
74
+ read_strikes() { local s; s=$(cat "$STRIKE_FILE" 2>/dev/null || echo 0); [[ "$s" =~ ^[0-9]+$ ]] && echo "$s" || echo 0; }
75
+ bump_strikes() { local s; s=$(read_strikes); echo $((s + 1)) > "$STRIKE_FILE" 2>/dev/null || true; echo $((s + 1)); }
76
+
77
+ # Container uptime in seconds (0 if not running / unknown).
78
+ container_uptime_secs() {
79
+ local started running
80
+ running=$(docker inspect --format '{{.State.Running}}' "$CONTAINER" 2>/dev/null || echo "false")
81
+ [[ "$running" == "true" ]] || { echo 0; return; }
82
+ started=$(docker inspect --format '{{.State.StartedAt}}' "$CONTAINER" 2>/dev/null || echo "")
83
+ [[ -n "$started" ]] || { echo 0; return; }
84
+ local start_epoch now_epoch
85
+ start_epoch=$(date -u -d "$started" +%s 2>/dev/null || date -u -j -f "%Y-%m-%dT%H:%M:%S" "${started%%.*}" +%s 2>/dev/null || echo 0)
86
+ now_epoch=$(date -u +%s)
87
+ if [[ "$start_epoch" -gt 0 && "$now_epoch" -ge "$start_epoch" ]]; then
88
+ echo $((now_epoch - start_epoch))
89
+ else
90
+ echo 0
91
+ fi
92
+ }
93
+
94
+ seconds_since_last_restart() {
95
+ local last now
96
+ last=$(cat "$LAST_RESTART_FILE" 2>/dev/null || echo 0)
97
+ [[ "$last" =~ ^[0-9]+$ ]] || last=0
98
+ now=$(date -u +%s)
99
+ echo $((now - last))
100
+ }
101
+
102
+ # ── Adapter-state probe ──────────────────────────────────────────────────────
103
+ # Echoes exactly one of: connected | disconnected | inconclusive
104
+ probe_adapter_state() {
105
+ local token unauth auth
106
+
107
+ # 1) Forward-compat: unauth /health MAY expose adapters_connected (gw bug 7e9fc713).
108
+ unauth=$(curl -sk --max-time "$TIMEOUT" "http://${HOST}:${PORT}/health" 2>/dev/null || echo "")
109
+ if [[ -n "$unauth" ]]; then
110
+ if echo "$unauth" | grep -Eq '"adapters_connected"[[:space:]]*:[[:space:]]*true'; then
111
+ echo "connected"; return
112
+ fi
113
+ if echo "$unauth" | grep -Eq '"adapters_connected"[[:space:]]*:[[:space:]]*false'; then
114
+ echo "disconnected"; return
115
+ fi
116
+ # No adapters_connected field → this gateway predates 7e9fc713. Fall through to
117
+ # the authenticated probe; the bare {status,uptime,adapterCount} body is NOT a
118
+ # connection verdict and must never be treated as one.
119
+ fi
120
+
121
+ # 2) Authenticated /admin/webhooks/health — reports real per-adapter state.
122
+ token=$(read_env EXE_GATEWAY_AUTH_TOKEN)
123
+ if [[ -z "$token" ]]; then
124
+ # FAIL SAFE: no token → we cannot make an authoritative judgement. Never restart.
125
+ echo "inconclusive"; return
126
+ fi
127
+
128
+ auth=$(curl -sk --max-time "$TIMEOUT" \
129
+ -H "Authorization: Bearer ${token}" \
130
+ "http://${HOST}:${PORT}/admin/webhooks/health" 2>/dev/null || echo "")
131
+
132
+ if [[ -z "$auth" ]]; then
133
+ # Couldn't reach the authed endpoint at all → inconclusive (could be a transient
134
+ # network blip; the HTTP layer may still be fine). Don't restart on this alone.
135
+ echo "inconclusive"; return
136
+ fi
137
+
138
+ # Real connection verdict. The authed health reports adapter connection via
139
+ # "connected":true and/or a per-account "whatsapp":{... "connected":true}.
140
+ if echo "$auth" | grep -Eq '"connected"[[:space:]]*:[[:space:]]*true'; then
141
+ echo "connected"; return
142
+ fi
143
+ if echo "$auth" | grep -Eq '"handlerRegistered"[[:space:]]*:[[:space:]]*true' \
144
+ && echo "$auth" | grep -Eq '"connected"[[:space:]]*:[[:space:]]*false'; then
145
+ echo "disconnected"; return
146
+ fi
147
+ # An authed body we can't classify (schema drift) is NOT grounds to restart.
148
+ echo "inconclusive"
149
+ }
150
+
151
+ # ── Main ─────────────────────────────────────────────────────────────────────
152
+ main() {
153
+ # Gateway not running at all? Let Docker's own `restart: unless-stopped` handle
154
+ # a crashed container — the watchdog only acts on a RUNNING-but-wedged gateway.
155
+ local uptime
156
+ uptime=$(container_uptime_secs)
157
+ if [[ "$uptime" -eq 0 ]]; then
158
+ log "container ${CONTAINER} not running (or uptime unknown) — deferring to Docker restart policy; not counting a strike."
159
+ reset_strikes
160
+ exit 0
161
+ fi
162
+
163
+ local state
164
+ state=$(probe_adapter_state)
165
+
166
+ case "$state" in
167
+ connected)
168
+ reset_strikes
169
+ log "OK adapters connected (uptime ${uptime}s)."
170
+ exit 0
171
+ ;;
172
+ inconclusive)
173
+ # NEVER restart on an inconclusive check. Log and reset so a transient
174
+ # unknown can't accumulate toward a restart.
175
+ reset_strikes
176
+ log "INCONCLUSIVE health check (token unset or unparseable body) — NOT restarting."
177
+ exit 0
178
+ ;;
179
+ disconnected)
180
+ : # fall through to strike logic below
181
+ ;;
182
+ *)
183
+ reset_strikes
184
+ log "unexpected probe result '${state}' — treating as inconclusive, NOT restarting."
185
+ exit 0
186
+ ;;
187
+ esac
188
+
189
+ # ── disconnected ──
190
+ # Startup grace: a gateway that is merely (re)starting / re-pairing WhatsApp must
191
+ # not be counted against. Require GRACE_SECS of uptime before any strike.
192
+ if [[ "$uptime" -lt "$GRACE_SECS" ]]; then
193
+ log "adapter disconnected but within startup grace (uptime ${uptime}s < ${GRACE_SECS}s) — not counting a strike."
194
+ exit 0
195
+ fi
196
+
197
+ # Backoff: don't restart again within the cooldown window — give the gateway time
198
+ # to reconnect after the previous restart.
199
+ local since_restart
200
+ since_restart=$(seconds_since_last_restart)
201
+ if [[ "$since_restart" -lt "$COOLDOWN_SECS" ]]; then
202
+ log "adapter disconnected but within post-restart cooldown (${since_restart}s < ${COOLDOWN_SECS}s) — backing off, not restarting."
203
+ exit 0
204
+ fi
205
+
206
+ local strikes
207
+ strikes=$(bump_strikes)
208
+ log "adapter DISCONNECTED — strike ${strikes}/${MAX_STRIKES} (uptime ${uptime}s)."
209
+
210
+ if [[ "$strikes" -ge "$MAX_STRIKES" ]]; then
211
+ log "ALERT strike threshold reached — restarting ${CONTAINER}."
212
+ if docker restart "$CONTAINER" >/dev/null 2>&1; then
213
+ date -u +%s > "$LAST_RESTART_FILE" 2>/dev/null || true
214
+ reset_strikes
215
+ log "restarted ${CONTAINER}; entering ${COOLDOWN_SECS}s cooldown."
216
+ else
217
+ log "ERROR docker restart ${CONTAINER} failed."
218
+ fi
219
+ fi
220
+ exit 0
221
+ }
222
+
223
+ main "$@"
@@ -52,11 +52,10 @@ info "Step 2: Generating .env file"
52
52
  if [[ -f .env ]]; then
53
53
  warn ".env already exists — skipping generation. Delete .env to regenerate."
54
54
  else
55
- if command -v node >/dev/null 2>&1; then
56
- node -e "
57
- const { generateEnv } = require('../../dist/deploy/compose/generate-env.js');
58
- console.log(generateEnv({ clientName: '$CLIENT', domain: '$DOMAIN', licenseKey: '${LICENSE:-}' || undefined }));
59
- " > .env 2>/dev/null || {
55
+ if command -v node >/dev/null 2>&1 && command -v npx >/dev/null 2>&1; then
56
+ # generate-env.ts is a TypeScript ESM file shipped at deploy/compose/generate-env.ts.
57
+ # Run it via npx tsx (the project is pure ESM — require() is forbidden).
58
+ npx --yes tsx "$SCRIPT_DIR/generate-env.ts" "$CLIENT" "$DOMAIN" "${LICENSE:-}" > .env 2>/dev/null || {
60
59
  # Fallback: generate inline
61
60
  info "Generating secrets inline..."
62
61
  gen() { openssl rand -hex "$1"; }
@@ -177,8 +176,10 @@ fi
177
176
  # exe-monitor-agent reports fleet health to the monitoring hub, but to do so it
178
177
  # needs SENSITIVE read-only host access:
179
178
  # - /var/run/docker.sock (root-equivalent: enumerate/inspect ALL containers)
180
- # - /proc, /sys (host process list + system-wide CPU/mem/hardware)
181
179
  # - /etc/os-release (host OS identity)
180
+ # (bug 107cde54: /proc and /sys mounts were REMOVED — the Beszel-based agent
181
+ # does not read bind-mounted host /proc or /sys, so they were pure attack
182
+ # surface with no metrics benefit.)
182
183
  # The service is gated behind the "monitor-agent" compose profile and stays OFF
183
184
  # until the operator explicitly acknowledges this scope here. Acknowledgement is
184
185
  # recorded by adding "monitor-agent" to COMPOSE_PROFILES in .env. Pre-answer in
@@ -196,8 +197,6 @@ else
196
197
  ├──────────────────────────────────────────────────────────────────────┤
197
198
  │ Enabling the monitor agent grants it READ-ONLY access to the host: │
198
199
  │ • /var/run/docker.sock — inspect ALL containers (root-equivalent) │
199
- │ • /proc — host process list + system CPU/mem stats │
200
- │ • /sys — host hardware/kernel metrics │
201
200
  │ • /etc/os-release — host OS name/version │
202
201
  │ │
203
202
  │ The Docker socket is effectively root on this machine. Only enable │
@@ -1344,12 +1344,13 @@
1344
1344
  "breakingChanges": [],
1345
1345
  "services": {
1346
1346
  "exe-db": {
1347
- "image": "pgvector/pgvector:pg16",
1347
+ "image": "pgvector/pgvector:0.8.0-pg16",
1348
1348
  "env": "EXE_DB_IMAGE",
1349
1349
  "composeService": "exe-db",
1350
1350
  "required": true,
1351
1351
  "category": "core",
1352
1352
  "description": "Postgres (pgvector) \u2014 shared system of record for all services",
1353
+ "_comment": "bug ee16e7ef: pinned to versioned 0.8.0-pg16 tag \u2014 the mutable :pg16 rolling tag can change upstream without notice.",
1353
1354
  "migrations": {
1354
1355
  "command": "exec -T exe-db psql -v ON_ERROR_STOP=1 -U ${POSTGRES_USER:-exe} -d ${POSTGRES_DB:-exedb} -f /docker-entrypoint-initdb.d/01-init.sql"
1355
1356
  }
@@ -1438,6 +1439,48 @@
1438
1439
  "category": "core",
1439
1440
  "description": "GoTrue auth backend \u2014 issues SSO/login tokens for CRM/Wiki/ERP",
1440
1441
  "_comment": "bug 2976868a: the stack health gate previously omitted GoTrue, so SSO/login could be fully down while the gate reported green. Gate the backend directly."
1442
+ },
1443
+ "redis": {
1444
+ "image": "redis:7.4-alpine",
1445
+ "composeService": "redis",
1446
+ "required": true,
1447
+ "category": "infra",
1448
+ "description": "Redis \u2014 session cache, job queues (CRM, gateway, ERP)",
1449
+ "_comment": "bug 4948fc48: compose-pinned infra dep \u2014 no env override, image tag lives in docker-compose.yml."
1450
+ },
1451
+ "clickhouse": {
1452
+ "image": "clickhouse/clickhouse-server:24.8.4.13-alpine",
1453
+ "env": "CLICKHOUSE_IMAGE",
1454
+ "composeService": "clickhouse",
1455
+ "required": true,
1456
+ "category": "infra",
1457
+ "description": "ClickHouse \u2014 analytics and event storage (CRM)",
1458
+ "_comment": "bug 4948fc48: compose-pinned infra dep required by CRM."
1459
+ },
1460
+ "cloudflared": {
1461
+ "image": "cloudflare/cloudflared@sha256:ba461b8aa9c042156dbd39c38657fe7431bafa063220eab8d5330a523863da9f",
1462
+ "composeService": "cloudflared",
1463
+ "required": true,
1464
+ "category": "infra",
1465
+ "description": "Cloudflare Tunnel \u2014 secure ingress proxy (replaces nginx + SSL certs)",
1466
+ "_comment": "bug 4948fc48: digest-pinned in compose, no env override."
1467
+ },
1468
+ "exe-sso-edge": {
1469
+ "image": "nginx:alpine",
1470
+ "composeService": "exe-sso-edge",
1471
+ "required": true,
1472
+ "category": "infra",
1473
+ "description": "SSO edge \u2014 reverse proxy that gates CRM/wiki/ERP behind unified auth",
1474
+ "_comment": "bug 4948fc48: stock nginx:alpine, config templated from DOMAIN."
1475
+ },
1476
+ "exe-otel-collector": {
1477
+ "image": "otel/opentelemetry-collector-contrib:0.114.0",
1478
+ "env": "OTEL_COLLECTOR_IMAGE",
1479
+ "composeService": "exe-otel-collector",
1480
+ "required": false,
1481
+ "category": "observability",
1482
+ "description": "OpenTelemetry Collector \u2014 receives OTLP traces/metrics/logs from all services",
1483
+ "_comment": "bug 4948fc48: compose-pinned observability infra."
1441
1484
  }
1442
1485
  }
1443
1486
  }