@askexenow/exe-os 0.9.302 → 0.9.310
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.
- package/deploy/compose/.env.example +8 -2
- package/deploy/compose/README.md +7 -3
- package/deploy/compose/add-routes.sh +107 -0
- package/deploy/compose/docker-compose.yml +91 -18
- package/deploy/compose/gateway-watchdog.sh +223 -0
- package/deploy/compose/generate-env.ts +16 -4
- package/deploy/compose/setup.sh +7 -8
- package/deploy/stack-manifests/v0.9.json +125 -4
- package/dist/{active-agent-ODI6GGVD.js → active-agent-ORX47ZKW.js} +4 -3
- package/dist/{active-agent-GGQAP4QE.js → active-agent-S5T2YMOR.js} +4 -3
- package/dist/{agentic-ontology-M7T72M6V.js → agentic-ontology-6KBAXLSJ.js} +1 -1
- package/dist/{backfill-metadata-GSZP2BEW.js → backfill-metadata-FV7GX6AS.js} +5 -4
- package/dist/{behaviors-JM6KFSXL.js → behaviors-47CPEKJ7.js} +6 -5
- package/dist/bin/agentic-ontology-backfill.js +6 -5
- package/dist/bin/agentic-reflection-backfill.js +7 -6
- package/dist/bin/agentic-semantic-label.js +6 -5
- package/dist/bin/backfill-conversations.js +8 -7
- package/dist/bin/backfill-responses.js +8 -7
- package/dist/bin/backfill-vectors.js +10 -9
- package/dist/bin/bulk-sync-postgres.js +14 -13
- package/dist/bin/cc-doctor.js +5 -4
- package/dist/bin/cleanup-stale-review-tasks.js +13 -12
- package/dist/bin/cli.js +20 -17
- package/dist/bin/deferred-daemon-restart.js +4 -4
- package/dist/bin/exe-agent-config.js +3 -2
- package/dist/bin/exe-agent.js +12 -11
- package/dist/bin/exe-assign.js +10 -9
- package/dist/bin/exe-boot.js +21 -20
- package/dist/bin/exe-call.js +5 -4
- package/dist/bin/exe-cloud.js +14 -13
- package/dist/bin/exe-config-dump.js +525 -0
- package/dist/bin/exe-dispatch.js +13 -12
- package/dist/bin/exe-doctor.js +2 -2
- package/dist/bin/exe-export-behaviors.js +8 -7
- package/dist/bin/exe-forget.js +7 -6
- package/dist/bin/exe-gateway.js +22 -11
- package/dist/bin/exe-healthcheck.js +7 -4
- package/dist/bin/exe-heartbeat.js +13 -12
- package/dist/bin/exe-kill.js +16 -15
- package/dist/bin/exe-launch-agent.js +41 -25
- package/dist/bin/exe-new-employee.js +9 -8
- package/dist/bin/exe-pending-messages.js +14 -13
- package/dist/bin/exe-pending-notifications.js +13 -12
- package/dist/bin/exe-pending-reviews.js +13 -12
- package/dist/bin/exe-preflight.js +108 -0
- package/dist/bin/exe-rename.js +5 -4
- package/dist/bin/exe-review.js +15 -14
- package/dist/bin/exe-search-quality.js +282 -0
- package/dist/bin/exe-search.js +6 -5
- package/dist/bin/exe-session-cleanup.js +18 -17
- package/dist/bin/exe-settings.js +15 -14
- package/dist/bin/exe-start-codex.js +13 -12
- package/dist/bin/exe-start-opencode.js +9 -8
- package/dist/bin/exe-status.js +14 -13
- package/dist/bin/exe-support.js +4 -4
- package/dist/bin/exe-team.js +4 -3
- package/dist/bin/exe-timers.js +237 -0
- package/dist/bin/git-sweep.js +14 -13
- package/dist/bin/graph-backfill.js +7 -6
- package/dist/bin/graph-export.js +6 -5
- package/dist/bin/import-history.js +10 -9
- package/dist/bin/install-launchd.js +5 -2
- package/dist/bin/install.js +16 -15
- package/dist/bin/intercom-check.js +4 -4
- package/dist/bin/mcp-sessions.js +2 -2
- package/dist/bin/orchestration-metrics.js +5 -4
- package/dist/bin/postgres-agentic-reflection-backfill.js +2 -2
- package/dist/bin/postgres-agentic-semantic-backfill.js +1 -1
- package/dist/bin/prune-orphan-chunks.js +43 -10
- package/dist/bin/scan-tasks.js +13 -12
- package/dist/bin/setup.js +1 -1
- package/dist/bin/shard-migrate.js +5 -4
- package/dist/bin/stack-update.js +30 -5
- package/dist/bin/vps-health-gate.js +1 -1
- package/dist/{capability-cards-BCTFKT4G.js → capability-cards-WQVY7WSS.js} +3 -2
- package/dist/{capacity-monitor-B4S3XEDO.js → capacity-monitor-BUKYPSWR.js} +14 -13
- package/dist/{catchup-brief-I35KHK62.js → catchup-brief-X5IVQAOH.js} +16 -15
- package/dist/{chunk-74TUYU5A.js → chunk-255DIG4B.js} +1 -1
- package/dist/{chunk-CXKR5Z5F.js → chunk-2EJ636HI.js} +4 -0
- package/dist/{chunk-ST3X3YMZ.js → chunk-2NGPZCWX.js} +4 -4
- package/dist/{chunk-LD2JKEJT.js → chunk-2NVEKSSP.js} +7 -7
- package/dist/{chunk-V6P7TPPA.js → chunk-2UVN2NBN.js} +148 -2
- package/dist/{chunk-OZRO37JW.js → chunk-3KH6GXXX.js} +2 -2
- package/dist/{chunk-IMJNYREY.js → chunk-43R2GILO.js} +1 -1
- package/dist/{chunk-6X2YNVLB.js → chunk-4BANMHJ5.js} +3 -3
- package/dist/{chunk-QNJPDIWY.js → chunk-4L4R6AQJ.js} +1 -1
- package/dist/{chunk-QWLPAEG5.js → chunk-4Q5VNQTV.js} +3 -3
- package/dist/{chunk-VLSYKMCJ.js → chunk-4RJJ5FJ2.js} +14 -2
- package/dist/{chunk-4AN6KMVG.js → chunk-5DPE7B7K.js} +1 -1
- package/dist/{chunk-H7ZNSQ2E.js → chunk-62UZWGFX.js} +2 -2
- package/dist/{chunk-ZQ2QHGWE.js → chunk-66JPZELX.js} +20 -24
- package/dist/{chunk-BUGMW7YF.js → chunk-6LNSZADA.js} +11 -11
- package/dist/{chunk-PBCRUNIK.js → chunk-6OD2RVIA.js} +492 -157
- package/dist/chunk-7ODTYODO.js +350 -0
- package/dist/{chunk-5YL4Y27D.js → chunk-7WDIGRSO.js} +1 -1
- package/dist/{chunk-WIL6LXNG.js → chunk-AC6DKMVS.js} +1 -1
- package/dist/{chunk-HZEAAKCR.js → chunk-B2J3BBJC.js} +3 -3
- package/dist/{chunk-LE7CDIOA.js → chunk-BUOMSIXH.js} +1 -1
- package/dist/{chunk-XKGNKH6V.js → chunk-DFUCFLZH.js} +1 -1
- package/dist/{chunk-U724Z3S3.js → chunk-DKHBMIVE.js} +100 -37
- package/dist/{chunk-MY36XDUK.js → chunk-DNFU56ZS.js} +1 -1
- package/dist/{chunk-GSWGOGP5.js → chunk-EOS7VPVT.js} +5 -5
- package/dist/{chunk-P33JVGSP.js → chunk-FM7XZDZI.js} +1 -1
- package/dist/chunk-G6EJKYK3.js +33 -0
- package/dist/{chunk-OMCNFTSI.js → chunk-GMZMBLHT.js} +1 -1
- package/dist/{chunk-4LZ54YGA.js → chunk-GSY6HL3T.js} +5 -5
- package/dist/{chunk-OVKNFTCL.js → chunk-HED6D5KR.js} +132 -21
- package/dist/{chunk-ALH6QLYF.js → chunk-HQODUV3G.js} +2 -2
- package/dist/{chunk-US64UR5O.js → chunk-IIHE3FNC.js} +3 -3
- package/dist/{chunk-H2ISTRWU.js → chunk-IRIPGGGL.js} +1 -1
- package/dist/{chunk-Z3EXECOX.js → chunk-ISILDO7P.js} +1 -1
- package/dist/{chunk-6ERZFDBS.js → chunk-IZPO6IOZ.js} +1 -1
- package/dist/{chunk-W65Z4SJS.js → chunk-J3QZPDNH.js} +1 -1
- package/dist/{chunk-3IJCP6ZH.js → chunk-JDYO76PI.js} +1 -1
- package/dist/{chunk-ORNKGGFX.js → chunk-JNARUXDC.js} +1 -1
- package/dist/{chunk-VME3UH4D.js → chunk-JV225JBX.js} +1 -1
- package/dist/{chunk-EI7PBUIA.js → chunk-JZW5AOWI.js} +1 -1
- package/dist/{chunk-TLOILHSL.js → chunk-K7KBAJQL.js} +36 -18
- package/dist/{chunk-RHMWSUJB.js → chunk-KAVUTP5G.js} +2 -2
- package/dist/{chunk-7FNELLMX.js → chunk-KPVVTHPK.js} +8 -8
- package/dist/{chunk-35U72CD2.js → chunk-KSMITUGY.js} +3 -3
- package/dist/{chunk-D6UICP3C.js → chunk-LMX7UKGX.js} +81 -3
- package/dist/{chunk-7ZWC5HVG.js → chunk-MTIJ2NXY.js} +1 -1
- package/dist/{chunk-KVHPBQV4.js → chunk-NCC3F6GM.js} +1 -1
- package/dist/{chunk-AYMSIV7X.js → chunk-NIZQ3CIS.js} +1 -1
- package/dist/{chunk-LTI2DLHY.js → chunk-OBJXZII2.js} +2 -2
- package/dist/{chunk-WHOT5NZ7.js → chunk-OR3TAZL3.js} +14 -2
- package/dist/{chunk-RQUHAXMM.js → chunk-PHFPIUWU.js} +2 -0
- package/dist/{chunk-N76VTMMF.js → chunk-PMEOSD6S.js} +2 -2
- package/dist/{chunk-TCJEFW5V.js → chunk-PPXQE7AS.js} +4 -4
- package/dist/{chunk-KHZQVLNZ.js → chunk-QE7UVQDP.js} +46 -0
- package/dist/{chunk-SXJACGQ5.js → chunk-QEGXNJGR.js} +4 -4
- package/dist/{chunk-W3E4VEEJ.js → chunk-RPBTYQKJ.js} +1 -1
- package/dist/chunk-RQ7OVXUZ.js +81 -0
- package/dist/{chunk-7L2EV3XX.js → chunk-RVXWGPD3.js} +39 -6
- package/dist/{chunk-YNALY3SX.js → chunk-RZJ6DXVE.js} +1 -1
- package/dist/{chunk-P3SIFDES.js → chunk-SCYWCWKY.js} +16 -4
- package/dist/{chunk-7I43GVER.js → chunk-SOT23KWW.js} +3 -3
- package/dist/{chunk-T62GAN7I.js → chunk-SPOXL7HF.js} +7 -7
- package/dist/{chunk-3YJMI422.js → chunk-TN2YCFQE.js} +4 -4
- package/dist/{chunk-YMSWCERY.js → chunk-U63BZI2B.js} +3 -3
- package/dist/{chunk-KA5RK5HZ.js → chunk-V42BSVX6.js} +2 -2
- package/dist/{chunk-PJTRAFL4.js → chunk-V4BW5HFQ.js} +3 -3
- package/dist/{chunk-4FG6IX7U.js → chunk-VFSYVQX4.js} +1 -1
- package/dist/chunk-VQRTO7IS.js +290 -0
- package/dist/{chunk-OR7J7TIA.js → chunk-VT6P7FV4.js} +1 -1
- package/dist/{chunk-BEIYF4NS.js → chunk-W2J2RF5N.js} +3 -3
- package/dist/{chunk-Y5ASPRP7.js → chunk-WBJXJ2R4.js} +17 -13
- package/dist/chunk-WCGNCH3Q.js +72 -0
- package/dist/{chunk-2LJDLQNO.js → chunk-WJ2PZGRV.js} +1 -1
- package/dist/{chunk-AAPPSMPH.js → chunk-WZBUKC5N.js} +1 -1
- package/dist/{chunk-RO7EXVSJ.js → chunk-X4AFBL4N.js} +1 -1
- package/dist/{chunk-BR74NYEF.js → chunk-XV5HRIXG.js} +5 -5
- package/dist/chunk-XXJRZ75E.js +43 -0
- package/dist/{chunk-VDHUHXFF.js → chunk-XYG722JW.js} +2 -2
- package/dist/{chunk-MM7SWUQ4.js → chunk-YB7U4WTY.js} +4 -4
- package/dist/{chunk-RMRZXPUU.js → chunk-Z6H6Y4BS.js} +1 -1
- package/dist/{token-budget-UMHRRDBT.js → chunk-ZOVHXTGC.js} +2 -9
- package/dist/{chunk-HNLBHP6P.js → chunk-ZZXADKAP.js} +3 -3
- package/dist/{co-activation-OKAHEMPE.js → co-activation-WN25AJOC.js} +3 -2
- package/dist/{co-occurrence-IHSPB53O.js → co-occurrence-YP2Q3OEA.js} +5 -4
- package/dist/{code-context-index-KGY4LKCA.js → code-context-index-ZWONKBKX.js} +4 -4
- package/dist/{conversation-entity-extractor-KXJSHRGJ.js → conversation-entity-extractor-KYOBXNID.js} +2 -2
- package/dist/{crdt-sync-K2G3C6XY.js → crdt-sync-ZFIFL2QM.js} +1 -1
- package/dist/{crm-webhook-ERZSQ3CW.js → crm-webhook-DGPHULOO.js} +2 -2
- package/dist/{cto-delegation-gate-ZCJ53LBQ.js → cto-delegation-gate-OF2A3TSS.js} +12 -11
- package/dist/daemon-auth-N6P2MZAV.js +17 -0
- package/dist/{daemon-orchestration-V6HALVDS.js → daemon-orchestration-G4RRTPP7.js} +16 -15
- package/dist/daemon-ready-signal-3L2MJT7C.js +13 -0
- package/dist/{db-backup-VZHSFUVA.js → db-backup-EORG4LBO.js} +1 -1
- package/dist/{doc-graph-extractor-LQ2IFND2.js → doc-graph-extractor-APZS3DFD.js} +5 -4
- package/dist/{dreaming-QAKUASYV.js → dreaming-Q4E3WBGY.js} +13 -12
- package/dist/{exe-drift-H3UWNFFY.js → exe-drift-CI5R6GUT.js} +4 -3
- package/dist/{exe-export-MN5D4JMT.js → exe-export-HQIQASIT.js} +7 -6
- package/dist/{exe-import-IFFMEVD7.js → exe-import-ULZK56WC.js} +7 -6
- package/dist/{exe-key-RSZC72QK.js → exe-key-ILNSBGRN.js} +5 -4
- package/dist/{exe-snapshot-I622MPLS.js → exe-snapshot-RUZTWZOF.js} +16 -15
- package/dist/{factory-CTFAGZUA.js → factory-YHQZ6RZZ.js} +14 -6
- package/dist/{fast-db-init-QEFVT7Q3.js → fast-db-init-KNJRL7T3.js} +1 -1
- package/dist/{founder-context-3SOKEW76.js → founder-context-QCTMEK66.js} +2 -2
- package/dist/gateway/index.js +12 -11
- package/dist/{git-staleness-E7RPQA5U.js → git-staleness-KIA5YI4H.js} +3 -2
- package/dist/{git-task-sweep-D3A2C5BW.js → git-task-sweep-WH6QE7HL.js} +13 -12
- package/dist/{global-procedures-POG4V6IK.js → global-procedures-SYZZCGDS.js} +4 -3
- package/dist/{graph-auto-extract-E7KALNWA.js → graph-auto-extract-J2TIWZEE.js} +5 -4
- package/dist/{graph-rag-3RZN7JR3.js → graph-rag-HITZZ63L.js} +2 -2
- package/dist/hooks/bug-report-worker.js +15 -14
- package/dist/hooks/codex-stop-task-finalizer.js +15 -14
- package/dist/hooks/commit-complete.js +15 -14
- package/dist/hooks/error-recall.js +8 -7
- package/dist/hooks/exe-heartbeat-hook.js +4 -3
- package/dist/hooks/ingest-worker.js +4 -4
- package/dist/hooks/ingest.js +8 -7
- package/dist/hooks/instructions-loaded.js +5 -4
- package/dist/hooks/manifest.json +20 -20
- package/dist/hooks/notification.js +5 -4
- package/dist/hooks/post-compact.js +14 -13
- package/dist/hooks/post-tool-combined.js +7 -7
- package/dist/hooks/pre-compact.js +19 -18
- package/dist/hooks/pre-tool-use.js +18 -17
- package/dist/hooks/prompt-submit.js +28 -27
- package/dist/hooks/session-end.js +24 -23
- package/dist/hooks/session-start.js +46 -22
- package/dist/hooks/stop.js +22 -21
- package/dist/hooks/subagent-stop.js +14 -13
- package/dist/hooks/summary-worker.js +22 -21
- package/dist/index.js +25 -23
- package/dist/{installer-IXUX73JN.js → installer-3NB3IBHF.js} +10 -9
- package/dist/{installer-RCUPPZ7Y.js → installer-IIJFCTMH.js} +10 -9
- package/dist/{installer-HDD6OH5Z.js → installer-PSDYMGN7.js} +7 -6
- package/dist/lib/cloud-sync.js +14 -13
- package/dist/lib/consolidation.js +8 -7
- package/dist/lib/database.js +3 -2
- package/dist/lib/db-daemon-client.js +4 -4
- package/dist/lib/db.js +3 -2
- package/dist/lib/device-registry.js +3 -1
- package/dist/lib/embedder.js +4 -4
- package/dist/lib/employee-templates.js +5 -4
- package/dist/lib/employees.js +3 -2
- package/dist/lib/exe-daemon-client.js +3 -3
- package/dist/lib/exe-daemon.js +256 -406
- package/dist/lib/hybrid-search.js +6 -5
- package/dist/lib/identity.js +3 -2
- package/dist/lib/license.js +2 -2
- package/dist/lib/messaging.js +13 -12
- package/dist/lib/reminders.js +4 -3
- package/dist/lib/schedules.js +6 -5
- package/dist/lib/session-registry.js +5 -4
- package/dist/lib/skill-learning.js +7 -6
- package/dist/lib/store.js +7 -4
- package/dist/lib/task-router.js +4 -3
- package/dist/lib/tasks.js +14 -13
- package/dist/lib/tmux-routing.js +16 -11
- package/dist/lib/token-spend.js +4 -3
- package/dist/{license-gate-V2UCK4KJ.js → license-gate-7SDHCJUO.js} +3 -3
- package/dist/mcp/register-tools.js +74 -73
- package/dist/mcp/server.js +97 -79
- package/dist/mcp/tools/complete-reminder.js +5 -4
- package/dist/mcp/tools/create-reminder.js +5 -4
- package/dist/mcp/tools/create-task.js +16 -15
- package/dist/mcp/tools/deactivate-behavior.js +8 -7
- package/dist/mcp/tools/list-reminders.js +5 -4
- package/dist/mcp/tools/list-tasks.js +16 -15
- package/dist/mcp/tools/send-message.js +15 -14
- package/dist/mcp/tools/update-task.js +15 -14
- package/dist/{mcp-http-config-7LFFPNGV.js → mcp-http-config-QSOVFXHW.js} +4 -3
- package/dist/mcp-tool-surface-CSPFXL3F.js +15 -0
- package/dist/{memory-cards-BGI6MJRN.js → memory-cards-II67WFL2.js} +3 -2
- package/dist/{memory-graph-extractor-DTRRNWHZ.js → memory-graph-extractor-PM4Q2KJG.js} +6 -5
- package/dist/{memory-poisoning-defense-MOCWJK6S.js → memory-poisoning-defense-ECXSX7HK.js} +3 -2
- package/dist/{memory-queue-client-DD4E5SIZ.js → memory-queue-client-HLURQEV6.js} +4 -4
- package/dist/{memory-reflection-UKKTGZYR.js → memory-reflection-PXF6YFDU.js} +3 -2
- package/dist/{notifications-AMJ2K3MM.js → notifications-UP7WQGG4.js} +12 -11
- package/dist/{orchestration-events-MB4QVHTD.js → orchestration-events-PZLLPCKY.js} +4 -3
- package/dist/orchestration-health-read-EJWVUARG.js +191 -0
- package/dist/{orchestrator-M7DJH6TN.js → orchestrator-PUT45J4O.js} +14 -13
- package/dist/{pipeline-router-4LT5YG5P.js → pipeline-router-5CRWKESM.js} +4 -3
- package/dist/{plan-limits-ENS7OJWP.js → plan-limits-SH2NDFNX.js} +6 -5
- package/dist/{project-boot-6W5JPGSB.js → project-boot-2TCPDZ4S.js} +25 -1
- package/dist/{projection-worker-WWXZK7QD.js → projection-worker-HWY37MNC.js} +2 -2
- package/dist/{prospective-memory-VNGLPMS6.js → prospective-memory-FQSVMCGG.js} +3 -2
- package/dist/{reranker-6XBS55FA.js → reranker-AALRMGMM.js} +1 -1
- package/dist/{retrieval-health-ATX5TIQ2.js → retrieval-health-PNWJSJNG.js} +1 -1
- package/dist/{review-polling-E4ALHTQE.js → review-polling-65JZUE3T.js} +13 -12
- package/dist/runtime/index.js +24 -22
- package/dist/{session-events-3J6APMKN.js → session-events-EJ3OSAA6.js} +13 -12
- package/dist/session-kill-telemetry-3HGM7SNQ.js +63 -0
- package/dist/{session-scope-E54VIYTJ.js → session-scope-JGDYAMJO.js} +12 -11
- package/dist/{setup-wizard-XV736U5D.js → setup-wizard-44LMFM2C.js} +1 -1
- package/dist/shared-rate-limit-store-QZPQEVE2.js +80 -0
- package/dist/{skill-refinement-FTURVWFN.js → skill-refinement-DUOO6B7V.js} +3 -2
- package/dist/{stack-release-JZVTSBJJ.js → stack-release-XYMKU7NT.js} +53 -4
- package/dist/{stack-update-BIK2QHOE.js → stack-update-XHQJRJEP.js} +5 -3
- package/dist/{steward-gate-YGU4VXPP.js → steward-gate-6NWRDLX2.js} +4 -3
- package/dist/{task-enforcement-GY3VTY6L.js → task-enforcement-UOULTGFB.js} +12 -11
- package/dist/{task-scope-PYOK7OQ6.js → task-scope-P5STKYAC.js} +12 -11
- package/dist/{tasks-crud-HLJEJ2UU.js → tasks-crud-24HMTULR.js} +12 -11
- package/dist/{tasks-notify-NGS3DWUJ.js → tasks-notify-LAIU7UUI.js} +13 -12
- package/dist/{tasks-review-BP5V2X34.js → tasks-review-AFJAU2DY.js} +12 -11
- package/dist/{telemetry-upload-AXYPFAPS.js → telemetry-upload-DWFGC2XJ.js} +8 -7
- package/dist/token-budget-4RFZKWGH.js +16 -0
- package/dist/{tool-capability-index-RHPVMMDD.js → tool-capability-index-ZEXBWKUZ.js} +1 -1
- package/dist/{tool-telemetry-BLSVABFJ.js → tool-telemetry-BS5JXM5M.js} +1 -1
- package/dist/tui/App.js +21 -20
- package/dist/{tui-data-EYPOFYLS.js → tui-data-UNO5B7PU.js} +12 -11
- package/dist/{worker-gate-SMFOBMMX.js → worker-gate-C6CTTYMD.js} +1 -1
- package/dist/{workflow-engine-TDJZ3TXB.js → workflow-engine-4W2KYCDV.js} +2 -2
- package/dist/{worktree-XISIYRM4.js → worktree-2CXY6NEB.js} +5 -4
- package/dist/{worktree-sweep-AS6GAR6X.js → worktree-sweep-K3CJFSUL.js} +6 -5
- package/package.json +9 -4
- package/release-notes.json +17 -88
- package/stack.release.json +51 -4
- package/dist/chunk-PHOQAVTK.js +0 -42
- package/dist/daemon-auth-BK7ON5ZH.js +0 -13
- package/dist/session-kill-telemetry-KOAHLRBT.js +0 -31
- package/dist/{chunk-XDE5IGBX.js → chunk-2NS3QRMJ.js} +0 -0
- package/dist/{chunk-JTMKRUX6.js → chunk-A2YZG4AD.js} +0 -0
- package/dist/{chunk-UZATXREL.js → chunk-AE74NOE6.js} +0 -0
- package/dist/{chunk-LUGWK6QB.js → chunk-FF5DA4XP.js} +3 -3
- /package/dist/{chunk-BGOMFGSW.js → chunk-GWPICNSO.js} +0 -0
- /package/dist/{chunk-NLHUA3YB.js → chunk-I3DPCSDG.js} +0 -0
- /package/dist/{chunk-YM3367VC.js → chunk-J3SR5MGG.js} +0 -0
- /package/dist/{chunk-FKFVNV7X.js → chunk-K4BKJJ4F.js} +0 -0
- /package/dist/{chunk-PTDBHQX7.js → chunk-MQCTK6EF.js} +0 -0
- /package/dist/{chunk-GBV5PV5T.js → chunk-QNXM2HXW.js} +0 -0
- /package/dist/{chunk-GZSG2ZOX.js → chunk-YUBTHJVV.js} +0 -0
- /package/dist/{core-memory-4OM7LR77.js → core-memory-OC5ITKKV.js} +0 -0
- /package/dist/{entity-boost-SFHIRXT5.js → entity-boost-NF3LE6OJ.js} +0 -0
- /package/dist/{message-queue-client-QTC3VHJT.js → message-queue-client-4VDU6BZY.js} +0 -0
- /package/dist/{oauth-server-BPN55EJM.js → oauth-server-EWUL7DMD.js} +0 -0
- /package/dist/{wiki-acl-45SIGG4O.js → wiki-acl-CONNF6ES.js} +0 -0
|
@@ -123,8 +123,14 @@ EXE_GATEWAY_WHATSAPP_VERIFY_TOKEN=CHANGEME_EXE_GATEWAY_WHATSAPP_VERIFY_TOKEN
|
|
|
123
123
|
WHATSAPP_ACCESS_TOKEN=CHANGEME_WHATSAPP_ACCESS_TOKEN
|
|
124
124
|
API_ROUTER_URL=https://gateway.askexe.com
|
|
125
125
|
API_ROUTER_KEY=CHANGEME_API_ROUTER_KEY
|
|
126
|
-
#
|
|
127
|
-
#
|
|
126
|
+
# Billing enforcement contract (bug df8823d0). These are the vars the gateway
|
|
127
|
+
# ACTUALLY reads to decide metered vs BYOK. Default: metered (fail-closed) —
|
|
128
|
+
# direct provider keys are rejected and all AI traffic is billed via the router.
|
|
129
|
+
EXE_BILLING_MODE=metered
|
|
130
|
+
EXE_ALLOW_DIRECT_PROVIDER_KEYS=false
|
|
131
|
+
# BYOK: to use your own API keys instead of the Exe API Router, set
|
|
132
|
+
# BYOK_ENABLED=true (the customer-facing alias for EXE_BILLING_MODE=byok +
|
|
133
|
+
# EXE_ALLOW_DIRECT_PROVIDER_KEYS=true) and provide ANTHROPIC_API_KEY below.
|
|
128
134
|
# BYOK_ENABLED=false
|
|
129
135
|
# ANTHROPIC_API_KEY=CHANGEME_ANTHROPIC_API_KEY
|
|
130
136
|
GATEWAY_HTTP_HOST_PORT=3100
|
package/deploy/compose/README.md
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
60
|
-
|
|
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, /
|
|
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
|
|
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
|
-
|
|
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@sha256:a132765ec351c65111b5b675928a3a0515a466a40f97277329db8b8209ad8bc9}
|
|
30
33
|
container_name: exe-db
|
|
31
34
|
restart: unless-stopped
|
|
32
35
|
# SECURITY (bug 67d62490): no blanket `env_file: .env`. Each service
|
|
@@ -78,7 +81,7 @@ services:
|
|
|
78
81
|
options: { max-size: "10m", max-file: "3" }
|
|
79
82
|
|
|
80
83
|
clickhouse:
|
|
81
|
-
image: clickhouse/clickhouse-server:24.8.4.13-alpine
|
|
84
|
+
image: clickhouse/clickhouse-server:24.8.4.13-alpine@sha256:88a45f9e328549b2579256c46ee38e5c0e25ae58303d9eb6d9c7ed8d6d2bbf3c
|
|
82
85
|
container_name: clickhouse
|
|
83
86
|
restart: unless-stopped
|
|
84
87
|
# SECURITY (bug 67d62490): no blanket `env_file: .env`. Each service
|
|
@@ -119,7 +122,7 @@ services:
|
|
|
119
122
|
options: { max-size: "10m", max-file: "3" }
|
|
120
123
|
|
|
121
124
|
redis:
|
|
122
|
-
image: redis:7.4-alpine
|
|
125
|
+
image: redis:7.4-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99
|
|
123
126
|
container_name: redis
|
|
124
127
|
restart: unless-stopped
|
|
125
128
|
# 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
|
|
240
|
-
# (DefaultDataDir="exe-monitor_data", see exe-monitor internal/cmd/hub).
|
|
241
|
-
# The
|
|
242
|
-
#
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
@@ -665,6 +701,13 @@ services:
|
|
|
665
701
|
# one secret without extra wiring.
|
|
666
702
|
MONITOR_API_KEY: ${MONITOR_API_KEY:-${EXE_MONITOR_KEY:-}}
|
|
667
703
|
EXE_DAEMON_TOKEN: ${EXED_MCP_TOKEN:?EXED_MCP_TOKEN is required}
|
|
704
|
+
# Billing enforcement contract (bug df8823d0). The gateway reads THESE to
|
|
705
|
+
# decide metered vs BYOK. Default metered + direct keys disallowed (fail
|
|
706
|
+
# closed). BYOK_ENABLED is the customer-facing alias: =true implies
|
|
707
|
+
# EXE_BILLING_MODE=byok + EXE_ALLOW_DIRECT_PROVIDER_KEYS=true. A mismatch
|
|
708
|
+
# (BYOK_ENABLED=true but EXE_BILLING_MODE=metered) resolves to metered.
|
|
709
|
+
EXE_BILLING_MODE: ${EXE_BILLING_MODE:-metered}
|
|
710
|
+
EXE_ALLOW_DIRECT_PROVIDER_KEYS: ${EXE_ALLOW_DIRECT_PROVIDER_KEYS:-false}
|
|
668
711
|
BYOK_ENABLED: ${BYOK_ENABLED:-false}
|
|
669
712
|
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
|
670
713
|
EXE_LICENSE_KEY: ${EXE_LICENSE_KEY:?EXE_LICENSE_KEY is required — purchase at https://askexe.com}
|
|
@@ -675,6 +718,14 @@ services:
|
|
|
675
718
|
- gateway_data:/data
|
|
676
719
|
- gateway_whatsapp_auth:/app/auth_info
|
|
677
720
|
- ./gateway.json:/data/gateway.json:ro
|
|
721
|
+
# bug 02bc1bb8: PERSISTENT customer-overlay bind-mount. Anything the
|
|
722
|
+
# customer drops in the host ./overlay dir (e.g. po-intake.js) is exposed
|
|
723
|
+
# read-only inside the container at /data/overlay and therefore SURVIVES
|
|
724
|
+
# image upgrades / `docker compose up` recreate — unlike files that only
|
|
725
|
+
# ever lived in the container's writable layer (which a recreate drops).
|
|
726
|
+
# bootstrapStackHost ensures ./overlay exists so docker never auto-creates
|
|
727
|
+
# the bind path as a stray file. Empty for customers with no overlay.
|
|
728
|
+
- ./overlay:/data/overlay:ro
|
|
678
729
|
networks:
|
|
679
730
|
- backend
|
|
680
731
|
- frontend
|
|
@@ -718,8 +769,9 @@ services:
|
|
|
718
769
|
exe-monitor-hub:
|
|
719
770
|
condition: service_healthy
|
|
720
771
|
environment:
|
|
721
|
-
# Reports to the local hub by default.
|
|
722
|
-
#
|
|
772
|
+
# Reports to the local hub by default. The agent must be paired manually
|
|
773
|
+
# from the hub UI (Settings → Add System) — the hub does not expose a
|
|
774
|
+
# REST registration endpoint. Write the generated TOKEN + KEY into .env.
|
|
723
775
|
HUB_URL: ${MONITOR_HUB_URL:-http://exe-monitor-hub:8090}
|
|
724
776
|
# TOKEN and KEY are written automatically by `exe-os stack-update` during
|
|
725
777
|
# bootstrap. They start empty; the agent retries connecting until the hub
|
|
@@ -838,7 +890,7 @@ services:
|
|
|
838
890
|
# ------------------------------------------------------------------
|
|
839
891
|
|
|
840
892
|
exe-sso-edge:
|
|
841
|
-
image: nginx:alpine
|
|
893
|
+
image: nginx:alpine@sha256:20316569d8f81a160065d7d2a5eeffc7ca97d79022462ee255fd23fa103a6b5c
|
|
842
894
|
container_name: exe-sso-edge
|
|
843
895
|
restart: unless-stopped
|
|
844
896
|
depends_on:
|
|
@@ -1069,7 +1121,7 @@ services:
|
|
|
1069
1121
|
# API to gunicorn, WebSocket to socketio. Without this, gunicorn returns
|
|
1070
1122
|
# 404 for all /assets/* requests (bug 6776edf0).
|
|
1071
1123
|
exe-erp-nginx:
|
|
1072
|
-
image: nginx:alpine
|
|
1124
|
+
image: nginx:alpine@sha256:20316569d8f81a160065d7d2a5eeffc7ca97d79022462ee255fd23fa103a6b5c
|
|
1073
1125
|
container_name: exe-erp-nginx
|
|
1074
1126
|
restart: unless-stopped
|
|
1075
1127
|
depends_on:
|
|
@@ -1154,6 +1206,13 @@ services:
|
|
|
1154
1206
|
- erp_assets:/home/frappe/frappe-bench/sites/assets
|
|
1155
1207
|
networks:
|
|
1156
1208
|
- backend
|
|
1209
|
+
healthcheck:
|
|
1210
|
+
# Verify the socketio node process is listening on port 9000.
|
|
1211
|
+
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))\""]
|
|
1212
|
+
interval: 30s
|
|
1213
|
+
timeout: 5s
|
|
1214
|
+
start_period: 30s
|
|
1215
|
+
retries: 3
|
|
1157
1216
|
deploy:
|
|
1158
1217
|
resources:
|
|
1159
1218
|
limits:
|
|
@@ -1193,6 +1252,13 @@ services:
|
|
|
1193
1252
|
- erp_assets:/home/frappe/frappe-bench/sites/assets
|
|
1194
1253
|
networks:
|
|
1195
1254
|
- backend
|
|
1255
|
+
healthcheck:
|
|
1256
|
+
# Verify the bench worker process is alive and Redis queue is reachable.
|
|
1257
|
+
test: ["CMD-SHELL", "pgrep -f 'rq worker' > /dev/null"]
|
|
1258
|
+
interval: 30s
|
|
1259
|
+
timeout: 5s
|
|
1260
|
+
start_period: 30s
|
|
1261
|
+
retries: 3
|
|
1196
1262
|
deploy:
|
|
1197
1263
|
resources:
|
|
1198
1264
|
limits:
|
|
@@ -1232,6 +1298,13 @@ services:
|
|
|
1232
1298
|
- erp_assets:/home/frappe/frappe-bench/sites/assets
|
|
1233
1299
|
networks:
|
|
1234
1300
|
- backend
|
|
1301
|
+
healthcheck:
|
|
1302
|
+
# Verify the bench schedule process is alive.
|
|
1303
|
+
test: ["CMD-SHELL", "pgrep -f 'bench schedule' > /dev/null"]
|
|
1304
|
+
interval: 30s
|
|
1305
|
+
timeout: 5s
|
|
1306
|
+
start_period: 30s
|
|
1307
|
+
retries: 3
|
|
1235
1308
|
deploy:
|
|
1236
1309
|
resources:
|
|
1237
1310
|
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 "$@"
|
|
@@ -174,8 +174,14 @@ export function generateEnv(options: GenerateEnvOptions): string {
|
|
|
174
174
|
"WHATSAPP_ACCESS_TOKEN=",
|
|
175
175
|
`API_ROUTER_URL=${DEFAULT_API_ROUTER_URL}`,
|
|
176
176
|
`API_ROUTER_KEY=${randomSecret(48)}`,
|
|
177
|
-
"#
|
|
178
|
-
"#
|
|
177
|
+
"# Billing enforcement contract (bug df8823d0). These are the vars the gateway",
|
|
178
|
+
"# ACTUALLY reads to decide metered vs BYOK. Default: metered (fail-closed) —",
|
|
179
|
+
"# direct provider keys are rejected and all AI traffic is billed via the router.",
|
|
180
|
+
"EXE_BILLING_MODE=metered",
|
|
181
|
+
"EXE_ALLOW_DIRECT_PROVIDER_KEYS=false",
|
|
182
|
+
"# BYOK: to use your own API keys instead of the Exe API Router, set",
|
|
183
|
+
"# BYOK_ENABLED=true (the customer-facing alias for EXE_BILLING_MODE=byok +",
|
|
184
|
+
"# EXE_ALLOW_DIRECT_PROVIDER_KEYS=true) and provide ANTHROPIC_API_KEY below.",
|
|
179
185
|
"# BYOK_ENABLED=false",
|
|
180
186
|
"# ANTHROPIC_API_KEY=",
|
|
181
187
|
`GATEWAY_HTTP_HOST_PORT=${GATEWAY_HTTP_HOST_PORT}`,
|
|
@@ -403,8 +409,14 @@ export function generateExampleEnv(): string {
|
|
|
403
409
|
"WHATSAPP_ACCESS_TOKEN=CHANGEME_WHATSAPP_ACCESS_TOKEN",
|
|
404
410
|
`API_ROUTER_URL=${DEFAULT_API_ROUTER_URL}`,
|
|
405
411
|
"API_ROUTER_KEY=CHANGEME_API_ROUTER_KEY",
|
|
406
|
-
"#
|
|
407
|
-
"#
|
|
412
|
+
"# Billing enforcement contract (bug df8823d0). These are the vars the gateway",
|
|
413
|
+
"# ACTUALLY reads to decide metered vs BYOK. Default: metered (fail-closed) —",
|
|
414
|
+
"# direct provider keys are rejected and all AI traffic is billed via the router.",
|
|
415
|
+
"EXE_BILLING_MODE=metered",
|
|
416
|
+
"EXE_ALLOW_DIRECT_PROVIDER_KEYS=false",
|
|
417
|
+
"# BYOK: to use your own API keys instead of the Exe API Router, set",
|
|
418
|
+
"# BYOK_ENABLED=true (the customer-facing alias for EXE_BILLING_MODE=byok +",
|
|
419
|
+
"# EXE_ALLOW_DIRECT_PROVIDER_KEYS=true) and provide ANTHROPIC_API_KEY below.",
|
|
408
420
|
"# BYOK_ENABLED=false",
|
|
409
421
|
"# ANTHROPIC_API_KEY=CHANGEME_ANTHROPIC_API_KEY",
|
|
410
422
|
`GATEWAY_HTTP_HOST_PORT=${GATEWAY_HTTP_HOST_PORT}`,
|