@askexenow/exe-os 0.9.127 → 0.9.129

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 (373) hide show
  1. package/dist/active-agent-3FDJYREJ.js +22 -0
  2. package/dist/active-agent-WC7YK3L5.js +23 -0
  3. package/dist/agent-context-CDWUZEXY.js +11 -0
  4. package/dist/agent-loop-G4VUR5ME.js +10 -0
  5. package/dist/agentic-ontology-2VYNTMNO.js +25 -0
  6. package/dist/backfill-metadata-OJWOCLSZ.js +601 -0
  7. package/dist/background-jobs-LGRDXGXC.js +25 -0
  8. package/dist/bash-Y5PEW5UQ.js +8 -0
  9. package/dist/behaviors-CSB2YLDT.js +22 -0
  10. package/dist/bin/age-ontology-load.js +10 -258
  11. package/dist/bin/agentic-ontology-backfill.js +23 -5296
  12. package/dist/bin/agentic-reflection-backfill.js +19 -4739
  13. package/dist/bin/agentic-semantic-label.js +28 -4723
  14. package/dist/bin/backfill-conversations.js +42 -5543
  15. package/dist/bin/backfill-responses.js +41 -5542
  16. package/dist/bin/backfill-vectors.js +43 -4795
  17. package/dist/bin/bulk-sync-postgres.js +22 -5313
  18. package/dist/bin/cc-doctor.js +10 -602
  19. package/dist/bin/cleanup-stale-review-tasks.js +32 -6803
  20. package/dist/bin/cli.js +169 -38993
  21. package/dist/bin/exe-agent-config.js +21 -253
  22. package/dist/bin/exe-agent.js +63 -2429
  23. package/dist/bin/exe-assign.js +34 -5810
  24. package/dist/bin/exe-boot.js +180 -12726
  25. package/dist/bin/exe-call.js +48 -1409
  26. package/dist/bin/exe-cloud.js +64 -8516
  27. package/dist/bin/exe-dispatch.js +35 -10818
  28. package/dist/bin/exe-doctor.js +24 -7537
  29. package/dist/bin/exe-export-behaviors.js +18 -6122
  30. package/dist/bin/exe-forget.js +22 -6274
  31. package/dist/bin/exe-gateway.js +62 -15286
  32. package/dist/bin/exe-healthcheck.js +9 -602
  33. package/dist/bin/exe-heartbeat.js +51 -6898
  34. package/dist/bin/exe-kill.js +30 -5961
  35. package/dist/bin/exe-launch-agent.js +101 -6788
  36. package/dist/bin/exe-new-employee.js +42 -3774
  37. package/dist/bin/exe-pending-messages.js +28 -6833
  38. package/dist/bin/exe-pending-notifications.js +31 -6869
  39. package/dist/bin/exe-pending-reviews.js +38 -6910
  40. package/dist/bin/exe-rename.js +57 -5766
  41. package/dist/bin/exe-review.js +29 -5948
  42. package/dist/bin/exe-search.js +22 -7692
  43. package/dist/bin/exe-session-cleanup.js +52 -11147
  44. package/dist/bin/exe-settings.js +35 -524
  45. package/dist/bin/exe-start-codex.js +70 -6641
  46. package/dist/bin/exe-start-opencode.js +54 -6584
  47. package/dist/bin/exe-status.js +26 -7081
  48. package/dist/bin/exe-support.js +10 -546
  49. package/dist/bin/exe-team.js +18 -5942
  50. package/dist/bin/git-sweep.js +26 -10909
  51. package/dist/bin/graph-backfill.js +24 -5979
  52. package/dist/bin/graph-export.js +31 -6128
  53. package/dist/bin/graph-layer-benchmark.js +4 -8
  54. package/dist/bin/install.js +47 -2322
  55. package/dist/bin/intercom-check.js +25 -11501
  56. package/dist/bin/list-providers.js +4 -18
  57. package/dist/bin/postgres-agentic-reflection-backfill.js +14 -357
  58. package/dist/bin/postgres-agentic-semantic-backfill.js +14 -404
  59. package/dist/bin/pre-publish.js +4 -298
  60. package/dist/bin/registry-proxy.js +8 -172
  61. package/dist/bin/scan-tasks.js +47 -10881
  62. package/dist/bin/setup.js +6 -9492
  63. package/dist/bin/shard-migrate.js +30 -5149
  64. package/dist/bin/stack-update.js +75 -203
  65. package/dist/bin/update.js +20 -654
  66. package/dist/capacity-monitor-3GZIHLB4.js +46 -0
  67. package/dist/catchup-brief-67QDCINS.js +148 -0
  68. package/dist/chunk-2QD7KBJN.js +664 -0
  69. package/dist/chunk-2WGSIVI4.js +149 -0
  70. package/dist/chunk-35TVNLJI.js +57 -0
  71. package/dist/chunk-3SYV36U5.js +47 -0
  72. package/dist/chunk-46SQTBQW.js +207 -0
  73. package/dist/chunk-4CMVLKEI.js +171 -0
  74. package/dist/chunk-4FLUOZTR.js +116 -0
  75. package/dist/chunk-4NYQAS33.js +17 -0
  76. package/dist/chunk-4QNPADWL.js +902 -0
  77. package/dist/chunk-4Y6QZBEO.js +58 -0
  78. package/dist/chunk-557C2IGL.js +0 -0
  79. package/dist/chunk-55S56YDO.js +305 -0
  80. package/dist/chunk-5BS5ELMC.js +85 -0
  81. package/dist/chunk-5DHM77JE.js +256 -0
  82. package/dist/chunk-5QTWQKNF.js +137 -0
  83. package/dist/chunk-6A7P74QV.js +53 -0
  84. package/dist/chunk-6BFIFI5F.js +45 -0
  85. package/dist/chunk-6KFICEAT.js +20 -0
  86. package/dist/chunk-6LKDJ5WX.js +317 -0
  87. package/dist/chunk-6M5II4EV.js +148 -0
  88. package/dist/chunk-6QEFN3DV.js +302 -0
  89. package/dist/chunk-6WTMJAG3.js +171 -0
  90. package/dist/chunk-6Y4B3QF6.js +19 -0
  91. package/dist/chunk-7RK3AYJE.js +163 -0
  92. package/dist/chunk-7TCP2XTP.js +390 -0
  93. package/dist/chunk-A2NZP64U.js +2307 -0
  94. package/dist/chunk-AFYIYM2K.js +322 -0
  95. package/dist/chunk-AJWFPKQS.js +573 -0
  96. package/dist/chunk-AVE2B4DQ.js +140 -0
  97. package/dist/chunk-B4TT2QQA.js +87 -0
  98. package/dist/chunk-BCMOO4OQ.js +1066 -0
  99. package/dist/chunk-BU3ID2WT.js +432 -0
  100. package/dist/chunk-CFIM43JH.js +862 -0
  101. package/dist/chunk-CURJUF3W.js +66 -0
  102. package/dist/chunk-DC4ZD76Y.js +288 -0
  103. package/dist/chunk-DL52JPGF.js +706 -0
  104. package/dist/chunk-DRSC6ZRA.js +36 -0
  105. package/dist/chunk-E2Q4Y22A.js +86 -0
  106. package/dist/chunk-ECGTESAP.js +236 -0
  107. package/dist/chunk-EHOJUXYG.js +365 -0
  108. package/dist/chunk-EIT6J37V.js +119 -0
  109. package/dist/chunk-ELE2C7L4.js +31 -0
  110. package/dist/chunk-EQSQ4HBW.js +9 -0
  111. package/dist/chunk-FWFFZGSC.js +94 -0
  112. package/dist/chunk-GCNWCYJI.js +249 -0
  113. package/dist/chunk-GGTUUI7J.js +6 -0
  114. package/dist/chunk-GU56L7OY.js +223 -0
  115. package/dist/chunk-HF25XPZE.js +83 -0
  116. package/dist/chunk-HOKOOBDL.js +52 -0
  117. package/dist/chunk-HP43GLZ6.js +89 -0
  118. package/dist/chunk-HSRM6PNM.js +682 -0
  119. package/dist/chunk-HVRTW3OP.js +164 -0
  120. package/dist/chunk-IHDHL5ML.js +125 -0
  121. package/dist/chunk-IHF5RXJM.js +168 -0
  122. package/dist/chunk-IJ73YE73.js +409 -0
  123. package/dist/chunk-INO4ZD5Y.js +162 -0
  124. package/dist/chunk-IRQTM7DY.js +475 -0
  125. package/dist/chunk-JD4FAPYG.js +1208 -0
  126. package/dist/chunk-JN44LQ53.js +316 -0
  127. package/dist/chunk-JQ6TLNIV.js +133 -0
  128. package/dist/chunk-JT76KRZQ.js +35 -0
  129. package/dist/chunk-K6OEWZTB.js +262 -0
  130. package/dist/chunk-KCUYRZSX.js +72 -0
  131. package/dist/chunk-KFQGP6VL.js +33 -0
  132. package/dist/chunk-KRLOFIPI.js +83 -0
  133. package/dist/chunk-KVPG5UT6.js +305 -0
  134. package/dist/chunk-L3TB7CC3.js +170 -0
  135. package/dist/chunk-L4X5EUHR.js +283 -0
  136. package/dist/chunk-LAN26C3W.js +655 -0
  137. package/dist/chunk-LPGBKR4H.js +54 -0
  138. package/dist/chunk-LVNMOVGD.js +132 -0
  139. package/dist/chunk-LX5WEEZ7.js +157 -0
  140. package/dist/chunk-MBH42QNH.js +295 -0
  141. package/dist/chunk-MFO7MNSD.js +102 -0
  142. package/dist/chunk-MILTNDZH.js +736 -0
  143. package/dist/chunk-MJAE4NB7.js +1090 -0
  144. package/dist/chunk-MLCUPTP7.js +132 -0
  145. package/dist/chunk-MPURH6JI.js +445 -0
  146. package/dist/chunk-MVBWW2EO.js +89 -0
  147. package/dist/chunk-MZIGUUDQ.js +42 -0
  148. package/dist/chunk-N3DTSVKE.js +157 -0
  149. package/dist/chunk-N5RRQOAC.js +134 -0
  150. package/dist/chunk-N7L33TED.js +1664 -0
  151. package/dist/chunk-NDYSNVI5.js +122 -0
  152. package/dist/chunk-NEAHEC5G.js +155 -0
  153. package/dist/chunk-NGVOA6ZQ.js +84 -0
  154. package/dist/chunk-NHK5KW6Y.js +193 -0
  155. package/dist/chunk-NMQKGFEP.js +458 -0
  156. package/dist/chunk-NPT4EHZK.js +352 -0
  157. package/dist/chunk-NXFJHTED.js +55 -0
  158. package/dist/chunk-NZLCDI6Y.js +216 -0
  159. package/dist/chunk-O377P7GM.js +100 -0
  160. package/dist/chunk-O4VFZRA5.js +33 -0
  161. package/dist/chunk-O7YD7JYE.js +237 -0
  162. package/dist/chunk-OFK72LTZ.js +81 -0
  163. package/dist/chunk-ORCCI2VV.js +140 -0
  164. package/dist/chunk-OVQR4KMC.js +579 -0
  165. package/dist/chunk-P3CVUMEH.js +54 -0
  166. package/dist/chunk-P4HIF72O.js +30 -0
  167. package/dist/chunk-PEDLSJBL.js +1039 -0
  168. package/dist/chunk-PFICKZZ5.js +219 -0
  169. package/dist/chunk-PGW7TIV6.js +142 -0
  170. package/dist/chunk-PJHHFJAN.js +63 -0
  171. package/dist/chunk-PP4SNOJ5.js +471 -0
  172. package/dist/chunk-PRKVT4KN.js +90 -0
  173. package/dist/chunk-Q2O4VJHH.js +305 -0
  174. package/dist/chunk-Q36CMZIX.js +38 -0
  175. package/dist/chunk-Q3V7K4ME.js +149 -0
  176. package/dist/chunk-QGYPJGHB.js +81 -0
  177. package/dist/chunk-R23EMFJV.js +198 -0
  178. package/dist/chunk-R4M6XCMU.js +39 -0
  179. package/dist/chunk-R4WMF32C.js +81 -0
  180. package/dist/chunk-RJT7H2KR.js +349 -0
  181. package/dist/chunk-RJZMRZNV.js +280 -0
  182. package/dist/chunk-RMFDPCA2.js +72 -0
  183. package/dist/chunk-RUG3N6P4.js +292 -0
  184. package/dist/chunk-RWO327FJ.js +84 -0
  185. package/dist/chunk-SG7EFHQ7.js +108 -0
  186. package/dist/chunk-SH45SJQW.js +0 -0
  187. package/dist/chunk-SPKE5ANC.js +42 -0
  188. package/dist/chunk-SVMFVPBF.js +104 -0
  189. package/dist/chunk-SVXDCELZ.js +163 -0
  190. package/dist/chunk-TMEE7AT7.js +72 -0
  191. package/dist/chunk-TWYEE4FB.js +125 -0
  192. package/dist/chunk-TYLF4LSW.js +110 -0
  193. package/dist/chunk-V4TZI6EO.js +60 -0
  194. package/dist/chunk-VFZTXKQI.js +372 -0
  195. package/dist/chunk-VQ4K2MLM.js +199 -0
  196. package/dist/chunk-VU447UAO.js +173 -0
  197. package/dist/chunk-WMC3TXJF.js +50 -0
  198. package/dist/chunk-WPOA7XAE.js +84 -0
  199. package/dist/chunk-WZQ4CPRG.js +64 -0
  200. package/dist/chunk-X2IMCCM5.js +49 -0
  201. package/dist/chunk-X2Z5GT3V.js +118 -0
  202. package/dist/chunk-XEKO37NM.js +43 -0
  203. package/dist/chunk-XUHFQHGZ.js +505 -0
  204. package/dist/chunk-YGAAZN3E.js +47 -0
  205. package/dist/chunk-YKOW7KMK.js +558 -0
  206. package/dist/chunk-YOQKMB6N.js +145 -0
  207. package/dist/chunk-YZFZDJWZ.js +107 -0
  208. package/dist/chunk-ZBY4AUD4.js +10342 -0
  209. package/dist/chunk-ZOJYPNCM.js +127 -0
  210. package/dist/chunk-ZS7MGQSD.js +1494 -0
  211. package/dist/chunk-ZTZQ5A6B.js +191 -0
  212. package/dist/code-context-index-HXGC4IJL.js +28 -0
  213. package/dist/content-extractor-UVZXRIPC.js +8 -0
  214. package/dist/conversation-entity-extractor-HKSNDF4L.js +113 -0
  215. package/dist/conversation-wiki-populator-NKDAQEZ5.js +105 -0
  216. package/dist/crdt-sync-T7MHVQCS.js +33 -0
  217. package/dist/crm-bridge-Q5JBVJ6P.js +19 -0
  218. package/dist/crm-webhook-OAFTLJYA.js +10 -0
  219. package/dist/cto-delegation-gate-XWHZCUJH.js +208 -0
  220. package/dist/daemon-auth-GWUXTUYO.js +15 -0
  221. package/dist/daemon-orchestration-2C3TVOHO.js +91 -0
  222. package/dist/daemon-protocol-RLH2XM2K.js +19 -0
  223. package/dist/db-backup-CH2KG3JY.js +23 -0
  224. package/dist/devtools-PLJCAU53.js +8 -0
  225. package/dist/discord-G4BQJ2VX.js +7 -0
  226. package/dist/email-XMRNMFXR.js +137 -0
  227. package/dist/entity-boost-IOER4VRR.js +249 -0
  228. package/dist/exe-drift-2IJFQV43.js +68 -0
  229. package/dist/exe-export-Y2RFXGNB.js +71 -0
  230. package/dist/exe-import-LN6PE2YT.js +74 -0
  231. package/dist/exe-key-DHKBWLIT.js +571 -0
  232. package/dist/exe-org-F7LZRHFB.js +73 -0
  233. package/dist/factory-YTD23AW7.js +67 -0
  234. package/dist/fast-db-init-IIP6EGWT.js +7 -0
  235. package/dist/file-edit-FJVEO7XZ.js +8 -0
  236. package/dist/file-read-Q2NH3J35.js +8 -0
  237. package/dist/file-write-UNSFXNH4.js +8 -0
  238. package/dist/gateway/index.js +87 -16563
  239. package/dist/gateway-client-APTYJNCL.js +11 -0
  240. package/dist/git-staleness-I2BUEDE2.js +111 -0
  241. package/dist/git-task-sweep-UEOTXRW4.js +35 -0
  242. package/dist/glob-GRMXLDMB.js +7 -0
  243. package/dist/global-procedures-MFE5LGRF.js +19 -0
  244. package/dist/graph-query-657O5JYJ.js +25 -0
  245. package/dist/graph-rag-ZE5N7C65.js +28 -0
  246. package/dist/grep-NBV6SKYZ.js +7 -0
  247. package/dist/hooks/bug-report-worker.js +41 -10823
  248. package/dist/hooks/codex-stop-task-finalizer.js +38 -9055
  249. package/dist/hooks/commit-complete.js +32 -10914
  250. package/dist/hooks/error-recall.js +35 -7955
  251. package/dist/hooks/exe-heartbeat-hook.js +17 -327
  252. package/dist/hooks/ingest-worker.js +16 -565
  253. package/dist/hooks/ingest.js +98 -11436
  254. package/dist/hooks/instructions-loaded.js +25 -6209
  255. package/dist/hooks/notification.js +16 -6098
  256. package/dist/hooks/post-compact.js +34 -7041
  257. package/dist/hooks/post-tool-combined.js +27 -9751
  258. package/dist/hooks/pre-compact.js +38 -11130
  259. package/dist/hooks/pre-tool-use.js +61 -7233
  260. package/dist/hooks/prompt-submit.js +113 -13358
  261. package/dist/hooks/session-end.js +57 -11503
  262. package/dist/hooks/session-start.js +46 -9423
  263. package/dist/hooks/stop.js +70 -7210
  264. package/dist/hooks/subagent-stop.js +33 -6897
  265. package/dist/hooks/summary-worker.js +86 -8629
  266. package/dist/hooks-KD3N2D7P.js +9 -0
  267. package/dist/imessage-L7WKA2WD.js +7 -0
  268. package/dist/index.js +49 -19391
  269. package/dist/installer-5N24GXFB.js +32 -0
  270. package/dist/installer-QSD76YRY.js +291 -0
  271. package/dist/installer-ZPQUKHU5.js +331 -0
  272. package/dist/intercom-queue-PEEGAJ33.js +19 -0
  273. package/dist/key-backup-status-57UB74DU.js +41 -0
  274. package/dist/lib/agent-config.js +19 -224
  275. package/dist/lib/cloud-sync.js +44 -5398
  276. package/dist/lib/cloudflare-dns.js +7 -111
  277. package/dist/lib/config.js +18 -278
  278. package/dist/lib/consolidation.js +21 -1047
  279. package/dist/lib/crypto.js +7 -45
  280. package/dist/lib/database.js +20 -2932
  281. package/dist/lib/db-daemon-client.js +24 -460
  282. package/dist/lib/db.js +22 -2932
  283. package/dist/lib/device-registry.js +20 -2992
  284. package/dist/lib/embedder.js +14 -822
  285. package/dist/lib/employee-templates.js +21 -1102
  286. package/dist/lib/employees.js +30 -497
  287. package/dist/lib/error-detector.js +9 -149
  288. package/dist/lib/exe-daemon-client.js +16 -570
  289. package/dist/lib/exe-daemon.js +509 -18398
  290. package/dist/lib/file-grep.js +2 -0
  291. package/dist/lib/hybrid-search.js +18 -7674
  292. package/dist/lib/identity-templates.js +2 -0
  293. package/dist/lib/identity.js +13 -309
  294. package/dist/lib/keychain.js +9 -499
  295. package/dist/lib/license.js +21 -549
  296. package/dist/lib/messaging.js +35 -1631
  297. package/dist/lib/post-tool-memory.js +8 -370
  298. package/dist/lib/registry-proxy.js +8 -155
  299. package/dist/lib/reminders.js +11 -231
  300. package/dist/lib/runtime-table.js +7 -20
  301. package/dist/lib/schedules.js +16 -4614
  302. package/dist/lib/session-registry.js +9 -57
  303. package/dist/lib/session-wrappers.js +3 -127
  304. package/dist/lib/skill-learning.js +20 -2101
  305. package/dist/lib/status-brief.js +4 -368
  306. package/dist/lib/store.js +25 -5593
  307. package/dist/lib/task-router.js +12 -254
  308. package/dist/lib/tasks.js +44 -5801
  309. package/dist/lib/tmux-routing.js +45 -5804
  310. package/dist/lib/tmux-status.js +16 -248
  311. package/dist/lib/tmux-transport.js +6 -90
  312. package/dist/lib/token-spend.js +9 -337
  313. package/dist/lib/transport.js +8 -134
  314. package/dist/lib/ws-auth.js +6 -14
  315. package/dist/lib/ws-client.js +4 -22
  316. package/dist/license-gate-NXXDOOAQ.js +14 -0
  317. package/dist/mcp/register-tools.js +90 -32454
  318. package/dist/mcp/server.js +139 -32625
  319. package/dist/mcp/tools/complete-reminder.js +10 -237
  320. package/dist/mcp/tools/create-reminder.js +10 -222
  321. package/dist/mcp/tools/create-task.js +28 -6730
  322. package/dist/mcp/tools/deactivate-behavior.js +13 -476
  323. package/dist/mcp/tools/list-reminders.js +10 -232
  324. package/dist/mcp/tools/list-tasks.js +28 -1571
  325. package/dist/mcp/tools/send-message.js +25 -1672
  326. package/dist/mcp/tools/update-task.js +27 -6123
  327. package/dist/mcp-diagnostics-OOZ3TDPY.js +200 -0
  328. package/dist/memory-cards-RIL5BZHK.js +175 -0
  329. package/dist/memory-queue-JYIJ7TMQ.js +13 -0
  330. package/dist/memory-queue-client-PE7757HU.js +14 -0
  331. package/dist/notifications-CJFULBMN.js +42 -0
  332. package/dist/orchestration-phase-Y2TYNW5H.js +23 -0
  333. package/dist/orchestrator-RXIPT6PZ.js +29 -0
  334. package/dist/permission-presets-WI524OSE.js +25 -0
  335. package/dist/permissions-TYT6YMNZ.js +14 -0
  336. package/dist/pg-ssl-KFHNJPGO.js +7 -0
  337. package/dist/plan-limits-5WY5YK6X.js +27 -0
  338. package/dist/preferences-MW3BYTQ5.js +10 -0
  339. package/dist/project-name-PL2ZDWNU.js +11 -0
  340. package/dist/projection-worker-OY4WYZW6.js +661 -0
  341. package/dist/reranker-LFJ73RRS.js +19 -0
  342. package/dist/review-gate-E6K7QQE5.js +120 -0
  343. package/dist/runtime/index.js +82 -13408
  344. package/dist/self-query-router-IZANO632.js +83 -0
  345. package/dist/session-events-TQ6S5KQN.js +32 -0
  346. package/dist/session-kill-telemetry-46YIJWE2.js +30 -0
  347. package/dist/session-scope-3MYK3YRV.js +34 -0
  348. package/dist/setup-wizard-ZJ7UCRBP.js +12 -0
  349. package/dist/shard-manager-RCFZ6RXY.js +29 -0
  350. package/dist/signal-XN2C7PIG.js +7 -0
  351. package/dist/slack-Z77TFJCK.js +7 -0
  352. package/dist/task-enforcement-337DZI3I.js +252 -0
  353. package/dist/task-scanner-IMIR3T7T.js +11 -0
  354. package/dist/task-scope-MUU4KXE6.js +32 -0
  355. package/dist/tasks-crud-PMLK3MM4.js +53 -0
  356. package/dist/tasks-review-56LVKKNL.js +44 -0
  357. package/dist/telegram-CRPZO7JT.js +7 -0
  358. package/dist/telemetry-SPVGPKU7.js +9 -0
  359. package/dist/tool-capability-index-XNBSDCH3.js +10 -0
  360. package/dist/tool-gates-LGH6CSY4.js +14 -0
  361. package/dist/tool-gates-W7SZCYTX.js +13 -0
  362. package/dist/tool-registry-436LVVLY.js +11 -0
  363. package/dist/tool-telemetry-L77WNAYT.js +15 -0
  364. package/dist/tui/App.js +253 -14668
  365. package/dist/tui-data-SB45REYU.js +161 -0
  366. package/dist/update-check-LZGZISRJ.js +11 -0
  367. package/dist/webhook-VVP7KQVC.js +96 -0
  368. package/dist/whatsapp-J6QNDBGY.js +9 -0
  369. package/dist/whatsapp-accounts-RTJF3LM6.js +15 -0
  370. package/dist/wiki-client-EKCIKCVC.js +136 -0
  371. package/dist/worker-gate-UYNN62GJ.js +19 -0
  372. package/dist/worktree-4T4BPWPN.js +205 -0
  373. package/package.json +1 -1
@@ -1,4617 +1,19 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
- }) : x)(function(x) {
6
- if (typeof require !== "undefined") return require.apply(this, arguments);
7
- throw Error('Dynamic require of "' + x + '" is not supported');
8
- });
9
- var __esm = (fn, res) => function __init() {
10
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
- };
12
- var __export = (target, all) => {
13
- for (var name in all)
14
- __defProp(target, name, { get: all[name], enumerable: true });
15
- };
16
-
17
- // src/lib/db-retry.ts
18
- function isBusyError(err) {
19
- if (err instanceof Error) {
20
- const msg = err.message.toLowerCase();
21
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
22
- }
23
- return false;
24
- }
25
- function delay(ms) {
26
- return new Promise((resolve) => setTimeout(resolve, ms));
27
- }
28
- async function retryOnBusy(fn, label) {
29
- let lastError;
30
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
31
- try {
32
- return await fn();
33
- } catch (err) {
34
- lastError = err;
35
- if (!isBusyError(err) || attempt === MAX_RETRIES) {
36
- throw err;
37
- }
38
- const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
39
- const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
40
- process.stderr.write(
41
- `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
42
- `
43
- );
44
- await delay(backoff + jitter);
45
- }
46
- }
47
- throw lastError;
48
- }
49
- function wrapWithRetry(client) {
50
- return new Proxy(client, {
51
- get(target, prop, receiver) {
52
- if (prop === "execute") {
53
- return (sql) => retryOnBusy(() => target.execute(sql), "execute");
54
- }
55
- if (prop === "batch") {
56
- return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
57
- }
58
- return Reflect.get(target, prop, receiver);
59
- }
60
- });
61
- }
62
- var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
63
- var init_db_retry = __esm({
64
- "src/lib/db-retry.ts"() {
65
- "use strict";
66
- MAX_RETRIES = 5;
67
- BASE_DELAY_MS = 250;
68
- MAX_JITTER_MS = 400;
69
- }
70
- });
71
-
72
- // src/lib/secure-files.ts
73
- import { chmodSync, existsSync, mkdirSync } from "fs";
74
- import { chmod, mkdir } from "fs/promises";
75
- async function ensurePrivateDir(dirPath) {
76
- await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
77
- try {
78
- await chmod(dirPath, PRIVATE_DIR_MODE);
79
- } catch {
80
- }
81
- }
82
- function ensurePrivateDirSync(dirPath) {
83
- mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
84
- try {
85
- chmodSync(dirPath, PRIVATE_DIR_MODE);
86
- } catch {
87
- }
88
- }
89
- async function enforcePrivateFile(filePath) {
90
- try {
91
- await chmod(filePath, PRIVATE_FILE_MODE);
92
- } catch {
93
- }
94
- }
95
- function enforcePrivateFileSync(filePath) {
96
- try {
97
- if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
98
- } catch {
99
- }
100
- }
101
- var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
102
- var init_secure_files = __esm({
103
- "src/lib/secure-files.ts"() {
104
- "use strict";
105
- PRIVATE_DIR_MODE = 448;
106
- PRIVATE_FILE_MODE = 384;
107
- }
108
- });
109
-
110
- // src/lib/config.ts
111
- import { readFile, writeFile } from "fs/promises";
112
- import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
113
- import path from "path";
114
- import os from "os";
115
- function resolveDataDir() {
116
- if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
117
- if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
118
- const newDir = path.join(os.homedir(), ".exe-os");
119
- const legacyDir = path.join(os.homedir(), ".exe-mem");
120
- if (!existsSync2(newDir) && existsSync2(legacyDir)) {
121
- try {
122
- renameSync(legacyDir, newDir);
123
- process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
124
- `);
125
- } catch {
126
- return legacyDir;
127
- }
128
- }
129
- return newDir;
130
- }
131
- function migrateLegacyConfig(raw) {
132
- if ("r2" in raw) {
133
- process.stderr.write(
134
- "[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
135
- );
136
- delete raw.r2;
137
- }
138
- if ("syncIntervalMs" in raw) {
139
- delete raw.syncIntervalMs;
140
- }
141
- return raw;
142
- }
143
- function migrateConfig(raw) {
144
- const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
145
- let currentVersion = fromVersion;
146
- let migrated = false;
147
- if (currentVersion > CURRENT_CONFIG_VERSION) {
148
- return { config: raw, migrated: false, fromVersion };
149
- }
150
- for (const migration of CONFIG_MIGRATIONS) {
151
- if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
152
- raw = migration.migrate(raw);
153
- currentVersion = migration.to;
154
- migrated = true;
155
- }
156
- }
157
- return { config: raw, migrated, fromVersion };
158
- }
159
- function normalizeScalingRoadmap(raw) {
160
- const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
161
- const userRoadmap = raw.scalingRoadmap ?? {};
162
- const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
163
- if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
164
- userAuto.enabled = raw.rerankerEnabled;
165
- }
166
- raw.scalingRoadmap = {
167
- ...userRoadmap,
168
- rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
169
- };
170
- }
171
- function normalizeSessionLifecycle(raw) {
172
- const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
173
- const userSL = raw.sessionLifecycle ?? {};
174
- raw.sessionLifecycle = { ...defaultSL, ...userSL };
175
- }
176
- function normalizeAutoUpdate(raw) {
177
- const defaultAU = DEFAULT_CONFIG.autoUpdate;
178
- const userAU = raw.autoUpdate ?? {};
179
- raw.autoUpdate = { ...defaultAU, ...userAU };
180
- }
181
- function normalizeOrchestration(raw) {
182
- const defaultOrg = DEFAULT_CONFIG.orchestration;
183
- const userOrg = raw.orchestration ?? {};
184
- raw.orchestration = { ...defaultOrg, ...userOrg };
185
- }
186
- function normalizeCloudEndpoint(raw) {
187
- const cloud = raw.cloud;
188
- if (!cloud?.endpoint) return;
189
- const ep = String(cloud.endpoint);
190
- if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
191
- cloud.endpoint = "https://cloud.askexe.com";
192
- process.stderr.write(
193
- "[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
194
- );
195
- }
196
- }
197
- async function loadConfig() {
198
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
199
- await ensurePrivateDir(dir);
200
- const configPath = path.join(dir, "config.json");
201
- if (!existsSync2(configPath)) {
202
- return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
203
- }
204
- const raw = await readFile(configPath, "utf-8");
205
- try {
206
- let parsed = JSON.parse(raw);
207
- parsed = migrateLegacyConfig(parsed);
208
- const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
209
- if (migrated) {
210
- process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
211
- `);
212
- try {
213
- await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
214
- await enforcePrivateFile(configPath);
215
- } catch {
216
- }
217
- }
218
- normalizeScalingRoadmap(migratedCfg);
219
- normalizeSessionLifecycle(migratedCfg);
220
- normalizeAutoUpdate(migratedCfg);
221
- normalizeOrchestration(migratedCfg);
222
- normalizeCloudEndpoint(migratedCfg);
223
- const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
224
- if (config.dbPath.startsWith("~")) {
225
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
226
- }
227
- const envDbPath = path.join(dir, "memories.db");
228
- if (process.env.EXE_OS_DIR && config.dbPath !== envDbPath && !existsSync2(config.dbPath) && existsSync2(envDbPath)) {
229
- config.dbPath = envDbPath;
230
- }
231
- return config;
232
- } catch {
233
- return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
234
- }
235
- }
236
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
237
- var init_config = __esm({
238
- "src/lib/config.ts"() {
239
- "use strict";
240
- init_secure_files();
241
- EXE_AI_DIR = resolveDataDir();
242
- DB_PATH = path.join(EXE_AI_DIR, "memories.db");
243
- MODELS_DIR = path.join(EXE_AI_DIR, "models");
244
- CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
245
- LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
246
- CURRENT_CONFIG_VERSION = 1;
247
- DEFAULT_CONFIG = {
248
- config_version: CURRENT_CONFIG_VERSION,
249
- dbPath: DB_PATH,
250
- modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
251
- embeddingDim: 1024,
252
- batchSize: 20,
253
- flushIntervalMs: 1e4,
254
- autoIngestion: true,
255
- autoRetrieval: true,
256
- searchMode: "hybrid",
257
- hookSearchMode: "hybrid",
258
- fileGrepEnabled: true,
259
- splashEffect: true,
260
- consolidationEnabled: true,
261
- consolidationIntervalMs: 6 * 60 * 60 * 1e3,
262
- consolidationModel: "claude-haiku-4-5-20251001",
263
- consolidationMaxCallsPerRun: 20,
264
- selfQueryRouter: true,
265
- selfQueryModel: "claude-haiku-4-5-20251001",
266
- rerankerEnabled: true,
267
- scalingRoadmap: {
268
- rerankerAutoTrigger: {
269
- enabled: true,
270
- broadQueryMinCardinality: 5e4,
271
- fetchTopK: 150,
272
- returnTopK: 5
273
- }
274
- },
275
- graphRagEnabled: true,
276
- wikiEnabled: false,
277
- wikiUrl: "",
278
- wikiApiKey: "",
279
- wikiSyncIntervalMs: 30 * 60 * 1e3,
280
- wikiWorkspaceMapping: {},
281
- wikiAutoUpdate: true,
282
- wikiAutoUpdateThreshold: 0.5,
283
- wikiAutoUpdateCreateNew: true,
284
- skillLearning: true,
285
- skillThreshold: 3,
286
- skillModel: "claude-haiku-4-5-20251001",
287
- exeHeartbeat: {
288
- enabled: true,
289
- intervalSeconds: 60,
290
- staleInProgressThresholdHours: 2
291
- },
292
- sessionLifecycle: {
293
- idleKillEnabled: true,
294
- idleKillTicksRequired: 3,
295
- idleKillIntercomAckWindowMs: 1e4,
296
- maxAutoInstances: 10
297
- },
298
- autoUpdate: {
299
- checkOnBoot: true,
300
- autoInstall: false,
301
- checkIntervalMs: 24 * 60 * 60 * 1e3
302
- },
303
- support: {
304
- bugReportEndpoint: "https://askexe.com/v1/support/bug-reports"
305
- },
306
- orchestration: {
307
- phase: "phase_1_coo",
308
- phaseSetBy: "default"
309
- }
310
- };
311
- CONFIG_MIGRATIONS = [
312
- {
313
- from: 0,
314
- to: 1,
315
- migrate: (cfg) => {
316
- cfg.config_version = 1;
317
- return cfg;
318
- }
319
- }
320
- ];
321
- }
322
- });
323
-
324
- // src/lib/employees.ts
325
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
326
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
327
- import { execSync } from "child_process";
328
- import path2 from "path";
329
- import os2 from "os";
330
- function normalizeRole(role) {
331
- return (role ?? "").trim().toLowerCase();
332
- }
333
- function isCoordinatorRole(role) {
334
- return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
335
- }
336
- function getCoordinatorEmployee(employees) {
337
- return employees.find((e) => isCoordinatorRole(e.role));
338
- }
339
- function getCoordinatorName(employees = loadEmployeesSync()) {
340
- return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
341
- }
342
- function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
343
- if (!existsSync3(employeesPath)) return [];
344
- try {
345
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
346
- } catch {
347
- return [];
348
- }
349
- }
350
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
351
- var init_employees = __esm({
352
- "src/lib/employees.ts"() {
353
- "use strict";
354
- init_config();
355
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
356
- DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
357
- COORDINATOR_ROLE = "COO";
358
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
359
- }
360
- });
361
-
362
- // src/lib/database-adapter.ts
363
- import os3 from "os";
364
- import path3 from "path";
365
- import { createRequire } from "module";
366
- import { pathToFileURL } from "url";
367
- function quotedIdentifier(identifier) {
368
- return `"${identifier.replace(/"/g, '""')}"`;
369
- }
370
- function unqualifiedTableName(name) {
371
- const raw = name.trim().replace(/^"|"$/g, "");
372
- const parts = raw.split(".");
373
- return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
374
- }
375
- function stripTrailingSemicolon(sql) {
376
- return sql.trim().replace(/;+\s*$/u, "");
377
- }
378
- function appendClause(sql, clause) {
379
- const trimmed = stripTrailingSemicolon(sql);
380
- const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
381
- if (!returningMatch) {
382
- return `${trimmed}${clause}`;
383
- }
384
- const idx = returningMatch.index;
385
- return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
386
- }
387
- function normalizeStatement(stmt) {
388
- if (typeof stmt === "string") {
389
- return { kind: "positional", sql: stmt, args: [] };
390
- }
391
- const sql = stmt.sql;
392
- if (Array.isArray(stmt.args) || stmt.args === void 0) {
393
- return { kind: "positional", sql, args: stmt.args ?? [] };
394
- }
395
- return { kind: "named", sql, args: stmt.args };
396
- }
397
- function rewriteBooleanLiterals(sql) {
398
- let out = sql;
399
- for (const column of BOOLEAN_COLUMN_NAMES) {
400
- const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
401
- out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
402
- out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
403
- out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
404
- out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
405
- out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
406
- out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
407
- }
408
- return out;
409
- }
410
- function rewriteInsertOrIgnore(sql) {
411
- if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
412
- return sql;
413
- }
414
- const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
415
- return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
416
- }
417
- function rewriteInsertOrReplace(sql) {
418
- const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
419
- if (!match) {
420
- return sql;
421
- }
422
- const rawTable = match[1];
423
- const rawColumns = match[2];
424
- const remainder = match[3];
425
- const tableName = unqualifiedTableName(rawTable);
426
- const conflictKeys = UPSERT_KEYS[tableName];
427
- if (!conflictKeys?.length) {
428
- return sql;
429
- }
430
- const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
431
- const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
432
- const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
433
- const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
434
- return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
435
- }
436
- function rewriteSql(sql) {
437
- let out = sql;
438
- out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
439
- out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
440
- out = rewriteBooleanLiterals(out);
441
- out = rewriteInsertOrReplace(out);
442
- out = rewriteInsertOrIgnore(out);
443
- return stripTrailingSemicolon(out);
444
- }
445
- function toBoolean(value) {
446
- if (value === null || value === void 0) return value;
447
- if (typeof value === "boolean") return value;
448
- if (typeof value === "number") return value !== 0;
449
- if (typeof value === "bigint") return value !== 0n;
450
- if (typeof value === "string") {
451
- const normalized = value.trim().toLowerCase();
452
- if (normalized === "0" || normalized === "false") return false;
453
- if (normalized === "1" || normalized === "true") return true;
454
- }
455
- return Boolean(value);
456
- }
457
- function countQuestionMarks(sql, end) {
458
- let count = 0;
459
- let inSingle = false;
460
- let inDouble = false;
461
- let inLineComment = false;
462
- let inBlockComment = false;
463
- for (let i = 0; i < end; i++) {
464
- const ch = sql[i];
465
- const next = sql[i + 1];
466
- if (inLineComment) {
467
- if (ch === "\n") inLineComment = false;
468
- continue;
469
- }
470
- if (inBlockComment) {
471
- if (ch === "*" && next === "/") {
472
- inBlockComment = false;
473
- i += 1;
474
- }
475
- continue;
476
- }
477
- if (!inSingle && !inDouble && ch === "-" && next === "-") {
478
- inLineComment = true;
479
- i += 1;
480
- continue;
481
- }
482
- if (!inSingle && !inDouble && ch === "/" && next === "*") {
483
- inBlockComment = true;
484
- i += 1;
485
- continue;
486
- }
487
- if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
488
- inSingle = !inSingle;
489
- continue;
490
- }
491
- if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
492
- inDouble = !inDouble;
493
- continue;
494
- }
495
- if (!inSingle && !inDouble && ch === "?") {
496
- count += 1;
497
- }
498
- }
499
- return count;
500
- }
501
- function findBooleanPlaceholderIndexes(sql) {
502
- const indexes = /* @__PURE__ */ new Set();
503
- for (const column of BOOLEAN_COLUMN_NAMES) {
504
- const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
505
- for (const match of sql.matchAll(pattern)) {
506
- const matchText = match[0];
507
- const qIndex = match.index + matchText.lastIndexOf("?");
508
- indexes.add(countQuestionMarks(sql, qIndex + 1));
509
- }
510
- }
511
- return indexes;
512
- }
513
- function coerceInsertBooleanArgs(sql, args) {
514
- const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
515
- if (!match) return;
516
- const rawTable = match[1];
517
- const rawColumns = match[2];
518
- const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
519
- if (!boolColumns?.size) return;
520
- const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
521
- for (const [index, column] of columns.entries()) {
522
- if (boolColumns.has(column) && index < args.length) {
523
- args[index] = toBoolean(args[index]);
524
- }
525
- }
526
- }
527
- function coerceUpdateBooleanArgs(sql, args) {
528
- const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
529
- if (!match) return;
530
- const rawTable = match[1];
531
- const setClause = match[2];
532
- const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
533
- if (!boolColumns?.size) return;
534
- const assignments = setClause.split(",");
535
- let placeholderIndex = 0;
536
- for (const assignment of assignments) {
537
- if (!assignment.includes("?")) continue;
538
- placeholderIndex += 1;
539
- const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
540
- if (colMatch && boolColumns.has(colMatch[1])) {
541
- args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
542
- }
543
- }
544
- }
545
- function coerceBooleanArgs(sql, args) {
546
- const nextArgs = [...args];
547
- coerceInsertBooleanArgs(sql, nextArgs);
548
- coerceUpdateBooleanArgs(sql, nextArgs);
549
- const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
550
- for (const index of placeholderIndexes) {
551
- if (index > 0 && index <= nextArgs.length) {
552
- nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
553
- }
554
- }
555
- return nextArgs;
556
- }
557
- function convertQuestionMarksToDollarParams(sql) {
558
- let out = "";
559
- let placeholder = 0;
560
- let inSingle = false;
561
- let inDouble = false;
562
- let inLineComment = false;
563
- let inBlockComment = false;
564
- for (let i = 0; i < sql.length; i++) {
565
- const ch = sql[i];
566
- const next = sql[i + 1];
567
- if (inLineComment) {
568
- out += ch;
569
- if (ch === "\n") inLineComment = false;
570
- continue;
571
- }
572
- if (inBlockComment) {
573
- out += ch;
574
- if (ch === "*" && next === "/") {
575
- out += next;
576
- inBlockComment = false;
577
- i += 1;
578
- }
579
- continue;
580
- }
581
- if (!inSingle && !inDouble && ch === "-" && next === "-") {
582
- out += ch + next;
583
- inLineComment = true;
584
- i += 1;
585
- continue;
586
- }
587
- if (!inSingle && !inDouble && ch === "/" && next === "*") {
588
- out += ch + next;
589
- inBlockComment = true;
590
- i += 1;
591
- continue;
592
- }
593
- if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
594
- inSingle = !inSingle;
595
- out += ch;
596
- continue;
597
- }
598
- if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
599
- inDouble = !inDouble;
600
- out += ch;
601
- continue;
602
- }
603
- if (!inSingle && !inDouble && ch === "?") {
604
- placeholder += 1;
605
- out += `$${placeholder}`;
606
- continue;
607
- }
608
- out += ch;
609
- }
610
- return out;
611
- }
612
- function translateStatementForPostgres(stmt) {
613
- const normalized = normalizeStatement(stmt);
614
- if (normalized.kind === "named") {
615
- throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
616
- }
617
- const rewrittenSql = rewriteSql(normalized.sql);
618
- const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
619
- return {
620
- sql: convertQuestionMarksToDollarParams(rewrittenSql),
621
- args: coercedArgs
622
- };
623
- }
624
- function shouldBypassPostgres(stmt) {
625
- const normalized = normalizeStatement(stmt);
626
- if (normalized.kind === "named") {
627
- return true;
628
- }
629
- return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
630
- }
631
- function shouldFallbackOnError(error) {
632
- const message = error instanceof Error ? error.message : String(error);
633
- return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
634
- }
635
- function isReadQuery(sql) {
636
- const trimmed = sql.trimStart();
637
- return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
638
- }
639
- function buildRow(row, columns) {
640
- const values = columns.map((column) => row[column]);
641
- return Object.assign(values, row);
642
- }
643
- function buildResultSet(rows, rowsAffected = 0) {
644
- const columns = rows[0] ? Object.keys(rows[0]) : [];
645
- const resultRows = rows.map((row) => buildRow(row, columns));
646
- return {
647
- columns,
648
- columnTypes: columns.map(() => ""),
649
- rows: resultRows,
650
- rowsAffected,
651
- lastInsertRowid: void 0,
652
- toJSON() {
653
- return {
654
- columns,
655
- columnTypes: columns.map(() => ""),
656
- rows,
657
- rowsAffected,
658
- lastInsertRowid: void 0
659
- };
660
- }
661
- };
662
- }
663
- async function loadPrismaClient() {
664
- if (!prismaClientPromise) {
665
- prismaClientPromise = (async () => {
666
- const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
667
- if (explicitPath) {
668
- const module2 = await import(pathToFileURL(explicitPath).href);
669
- const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
670
- if (!PrismaClient2) {
671
- throw new Error(`No PrismaClient export found at ${explicitPath}`);
672
- }
673
- return new PrismaClient2();
674
- }
675
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
676
- const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
677
- const prismaEntry = requireFromExeDb.resolve("@prisma/client");
678
- const module = await import(pathToFileURL(prismaEntry).href);
679
- const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
680
- if (!PrismaClient) {
681
- throw new Error(`No PrismaClient export found in ${prismaEntry}`);
682
- }
683
- return new PrismaClient();
684
- })();
685
- }
686
- return prismaClientPromise;
687
- }
688
- async function ensureCompatibilityViews(prisma) {
689
- if (!compatibilityBootstrapPromise) {
690
- compatibilityBootstrapPromise = (async () => {
691
- for (const mapping of VIEW_MAPPINGS) {
692
- const relation = mapping.source.replace(/"/g, "");
693
- const rows = await prisma.$queryRawUnsafe(
694
- "SELECT to_regclass($1)::text AS regclass",
695
- relation
696
- );
697
- if (!rows[0]?.regclass) {
698
- continue;
699
- }
700
- await prisma.$executeRawUnsafe(
701
- `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
702
- );
703
- }
704
- })();
705
- }
706
- return compatibilityBootstrapPromise;
707
- }
708
- async function executeOnPrisma(executor, stmt) {
709
- const translated = translateStatementForPostgres(stmt);
710
- if (isReadQuery(translated.sql)) {
711
- const rows = await executor.$queryRawUnsafe(
712
- translated.sql,
713
- ...translated.args
714
- );
715
- return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
716
- }
717
- const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
718
- return buildResultSet([], rowsAffected);
719
- }
720
- function splitSqlStatements(sql) {
721
- const parts = [];
722
- let current = "";
723
- let inSingle = false;
724
- let inDouble = false;
725
- let inLineComment = false;
726
- let inBlockComment = false;
727
- for (let i = 0; i < sql.length; i++) {
728
- const ch = sql[i];
729
- const next = sql[i + 1];
730
- if (inLineComment) {
731
- current += ch;
732
- if (ch === "\n") inLineComment = false;
733
- continue;
734
- }
735
- if (inBlockComment) {
736
- current += ch;
737
- if (ch === "*" && next === "/") {
738
- current += next;
739
- inBlockComment = false;
740
- i += 1;
741
- }
742
- continue;
743
- }
744
- if (!inSingle && !inDouble && ch === "-" && next === "-") {
745
- current += ch + next;
746
- inLineComment = true;
747
- i += 1;
748
- continue;
749
- }
750
- if (!inSingle && !inDouble && ch === "/" && next === "*") {
751
- current += ch + next;
752
- inBlockComment = true;
753
- i += 1;
754
- continue;
755
- }
756
- if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
757
- inSingle = !inSingle;
758
- current += ch;
759
- continue;
760
- }
761
- if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
762
- inDouble = !inDouble;
763
- current += ch;
764
- continue;
765
- }
766
- if (!inSingle && !inDouble && ch === ";") {
767
- if (current.trim()) {
768
- parts.push(current.trim());
769
- }
770
- current = "";
771
- continue;
772
- }
773
- current += ch;
774
- }
775
- if (current.trim()) {
776
- parts.push(current.trim());
777
- }
778
- return parts;
779
- }
780
- async function createPrismaDbAdapter(fallbackClient) {
781
- const prisma = await loadPrismaClient();
782
- await ensureCompatibilityViews(prisma);
783
- let closed = false;
784
- let adapter;
785
- const fallbackExecute = async (stmt, error) => {
786
- if (!fallbackClient) {
787
- if (error) throw error;
788
- throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
789
- }
790
- if (error) {
791
- process.stderr.write(
792
- `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
793
- `
794
- );
795
- }
796
- return fallbackClient.execute(stmt);
797
- };
798
- adapter = {
799
- async execute(stmt) {
800
- if (shouldBypassPostgres(stmt)) {
801
- return fallbackExecute(stmt);
802
- }
803
- try {
804
- return await executeOnPrisma(prisma, stmt);
805
- } catch (error) {
806
- if (shouldFallbackOnError(error)) {
807
- return fallbackExecute(stmt, error);
808
- }
809
- throw error;
810
- }
811
- },
812
- async batch(stmts, mode) {
813
- if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
814
- if (!fallbackClient) {
815
- throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
816
- }
817
- return fallbackClient.batch(stmts, mode);
818
- }
819
- try {
820
- if (prisma.$transaction) {
821
- return await prisma.$transaction(async (tx) => {
822
- const results2 = [];
823
- for (const stmt of stmts) {
824
- results2.push(await executeOnPrisma(tx, stmt));
825
- }
826
- return results2;
827
- });
828
- }
829
- const results = [];
830
- for (const stmt of stmts) {
831
- results.push(await executeOnPrisma(prisma, stmt));
832
- }
833
- return results;
834
- } catch (error) {
835
- if (fallbackClient && shouldFallbackOnError(error)) {
836
- process.stderr.write(
837
- `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
838
- `
839
- );
840
- return fallbackClient.batch(stmts, mode);
841
- }
842
- throw error;
843
- }
844
- },
845
- async migrate(stmts) {
846
- if (fallbackClient) {
847
- return fallbackClient.migrate(stmts);
848
- }
849
- return adapter.batch(stmts, "deferred");
850
- },
851
- async transaction(mode) {
852
- if (!fallbackClient) {
853
- throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
854
- }
855
- return fallbackClient.transaction(mode);
856
- },
857
- async executeMultiple(sql) {
858
- if (fallbackClient && shouldBypassPostgres(sql)) {
859
- return fallbackClient.executeMultiple(sql);
860
- }
861
- for (const statement of splitSqlStatements(sql)) {
862
- await adapter.execute(statement);
863
- }
864
- },
865
- async sync() {
866
- if (fallbackClient) {
867
- return fallbackClient.sync();
868
- }
869
- return { frame_no: 0, frames_synced: 0 };
870
- },
871
- close() {
872
- closed = true;
873
- prismaClientPromise = null;
874
- compatibilityBootstrapPromise = null;
875
- void prisma.$disconnect?.();
876
- },
877
- get closed() {
878
- return closed;
879
- },
880
- get protocol() {
881
- return "prisma-postgres";
882
- }
883
- };
884
- return adapter;
885
- }
886
- var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
887
- var init_database_adapter = __esm({
888
- "src/lib/database-adapter.ts"() {
889
- "use strict";
890
- VIEW_MAPPINGS = [
891
- { view: "memories", source: "memory.memory_records" },
892
- { view: "tasks", source: "memory.tasks" },
893
- { view: "behaviors", source: "memory.behaviors" },
894
- { view: "entities", source: "memory.entities" },
895
- { view: "relationships", source: "memory.relationships" },
896
- { view: "entity_memories", source: "memory.entity_memories" },
897
- { view: "entity_aliases", source: "memory.entity_aliases" },
898
- { view: "notifications", source: "memory.notifications" },
899
- { view: "messages", source: "memory.messages" },
900
- { view: "users", source: "wiki.users" },
901
- { view: "workspaces", source: "wiki.workspaces" },
902
- { view: "workspace_users", source: "wiki.workspace_users" },
903
- { view: "documents", source: "wiki.workspace_documents" },
904
- { view: "chats", source: "wiki.workspace_chats" }
905
- ];
906
- UPSERT_KEYS = {
907
- memories: ["id"],
908
- tasks: ["id"],
909
- behaviors: ["id"],
910
- entities: ["id"],
911
- relationships: ["id"],
912
- entity_aliases: ["alias"],
913
- notifications: ["id"],
914
- messages: ["id"],
915
- users: ["id"],
916
- workspaces: ["id"],
917
- workspace_users: ["id"],
918
- documents: ["id"],
919
- chats: ["id"]
920
- };
921
- BOOLEAN_COLUMNS_BY_TABLE = {
922
- memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
923
- behaviors: /* @__PURE__ */ new Set(["active"]),
924
- notifications: /* @__PURE__ */ new Set(["read"]),
925
- users: /* @__PURE__ */ new Set(["has_personal_memory"])
926
- };
927
- BOOLEAN_COLUMN_NAMES = new Set(
928
- Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
929
- );
930
- IMMEDIATE_FALLBACK_PATTERNS = [
931
- /\bPRAGMA\b/i,
932
- /\bsqlite_master\b/i,
933
- /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
934
- /\bMATCH\b/i,
935
- /\bvector_distance_cos\s*\(/i,
936
- /\bjson_extract\s*\(/i,
937
- /\bjulianday\s*\(/i,
938
- /\bstrftime\s*\(/i,
939
- /\blast_insert_rowid\s*\(/i
940
- ];
941
- prismaClientPromise = null;
942
- compatibilityBootstrapPromise = null;
943
- }
944
- });
945
-
946
- // src/types/memory.ts
947
- var EMBEDDING_DIM;
948
- var init_memory = __esm({
949
- "src/types/memory.ts"() {
950
- "use strict";
951
- EMBEDDING_DIM = 1024;
952
- }
953
- });
954
-
955
- // src/lib/daemon-auth.ts
956
- import crypto from "crypto";
957
- import path4 from "path";
958
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
959
- function normalizeToken(token) {
960
- if (!token) return null;
961
- const trimmed = token.trim();
962
- return trimmed.length > 0 ? trimmed : null;
963
- }
964
- function readDaemonToken() {
965
- try {
966
- if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
967
- return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
968
- } catch {
969
- return null;
970
- }
971
- }
972
- function ensureDaemonToken(seed) {
973
- const existing = readDaemonToken();
974
- if (existing) return existing;
975
- const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
976
- ensurePrivateDirSync(EXE_AI_DIR);
977
- writeFileSync2(DAEMON_TOKEN_PATH, `${token}
978
- `, "utf8");
979
- enforcePrivateFileSync(DAEMON_TOKEN_PATH);
980
- return token;
981
- }
982
- var DAEMON_TOKEN_PATH;
983
- var init_daemon_auth = __esm({
984
- "src/lib/daemon-auth.ts"() {
985
- "use strict";
986
- init_config();
987
- init_secure_files();
988
- DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
989
- }
990
- });
991
-
992
- // src/lib/exe-daemon-client.ts
993
- import net from "net";
994
- import os4 from "os";
995
- import { spawn, execSync as execSync2 } from "child_process";
996
- import { randomUUID } from "crypto";
997
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
998
- import path5 from "path";
999
- import { fileURLToPath } from "url";
1000
- function handleData(chunk) {
1001
- _buffer += chunk.toString();
1002
- if (_buffer.length > MAX_BUFFER) {
1003
- _buffer = "";
1004
- return;
1005
- }
1006
- let newlineIdx;
1007
- while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
1008
- const line = _buffer.slice(0, newlineIdx).trim();
1009
- _buffer = _buffer.slice(newlineIdx + 1);
1010
- if (!line) continue;
1011
- try {
1012
- const response = JSON.parse(line);
1013
- const id = response.id;
1014
- if (!id) continue;
1015
- const entry = _pending.get(id);
1016
- if (entry) {
1017
- clearTimeout(entry.timer);
1018
- _pending.delete(id);
1019
- entry.resolve(response);
1020
- }
1021
- } catch {
1022
- }
1023
- }
1024
- }
1025
- function isZombie(pid) {
1026
- try {
1027
- const state = execSync2(`ps -p ${pid} -o state=`, { encoding: "utf8", timeout: 2e3 }).trim();
1028
- return state.startsWith("Z");
1029
- } catch {
1030
- return false;
1031
- }
1032
- }
1033
- function cleanupStaleFiles() {
1034
- if (existsSync5(PID_PATH)) {
1035
- try {
1036
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1037
- if (pid > 0) {
1038
- try {
1039
- process.kill(pid, 0);
1040
- if (!isZombie(pid)) {
1041
- return;
1042
- }
1043
- process.stderr.write(`[exed-client] PID ${pid} is a zombie \u2014 cleaning up stale files
1044
- `);
1045
- } catch {
1046
- }
1047
- }
1048
- } catch {
1049
- }
1050
- try {
1051
- unlinkSync2(PID_PATH);
1052
- } catch {
1053
- }
1054
- try {
1055
- unlinkSync2(SOCKET_PATH);
1056
- } catch {
1057
- }
1058
- }
1059
- }
1060
- function findPackageRoot() {
1061
- let dir = path5.dirname(fileURLToPath(import.meta.url));
1062
- const { root } = path5.parse(dir);
1063
- while (dir !== root) {
1064
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
1065
- dir = path5.dirname(dir);
1066
- }
1067
- return null;
1068
- }
1069
- function spawnDaemon() {
1070
- const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1071
- if (totalGB <= 8) {
1072
- process.stderr.write(
1073
- `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
1074
- `
1075
- );
1076
- return;
1077
- }
1078
- const pkgRoot = findPackageRoot();
1079
- if (!pkgRoot) {
1080
- process.stderr.write("[exed-client] WARN: cannot find package root\n");
1081
- return;
1082
- }
1083
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1084
- if (!existsSync5(daemonPath)) {
1085
- process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1086
- `);
1087
- return;
1088
- }
1089
- const resolvedPath = daemonPath;
1090
- const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1091
- process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1092
- `);
1093
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1094
- let stderrFd = "ignore";
1095
- try {
1096
- stderrFd = openSync(logPath, "a");
1097
- } catch {
1098
- }
1099
- const heapCapMB = totalGB <= 8 ? 256 : 512;
1100
- const nodeArgs = [`--max-old-space-size=${heapCapMB}`, resolvedPath];
1101
- const child = spawn(process.execPath, nodeArgs, {
1102
- detached: true,
1103
- stdio: ["ignore", "ignore", stderrFd],
1104
- env: {
1105
- ...process.env,
1106
- TMUX: void 0,
1107
- // Daemon is global — must not inherit session scope
1108
- TMUX_PANE: void 0,
1109
- // Prevents resolveExeSession() from scoping to one session
1110
- EXE_DAEMON_SOCK: SOCKET_PATH,
1111
- EXE_DAEMON_PID: PID_PATH,
1112
- [DAEMON_TOKEN_ENV]: daemonToken
1113
- }
1114
- });
1115
- child.unref();
1116
- if (typeof stderrFd === "number") {
1117
- try {
1118
- closeSync(stderrFd);
1119
- } catch {
1120
- }
1121
- }
1122
- }
1123
- function acquireSpawnLock() {
1124
- try {
1125
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
1126
- closeSync(fd);
1127
- return true;
1128
- } catch {
1129
- try {
1130
- const stat = statSync(SPAWN_LOCK_PATH);
1131
- if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
1132
- try {
1133
- unlinkSync2(SPAWN_LOCK_PATH);
1134
- } catch {
1135
- }
1136
- try {
1137
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
1138
- closeSync(fd);
1139
- return true;
1140
- } catch {
1141
- }
1142
- }
1143
- } catch {
1144
- }
1145
- return false;
1146
- }
1147
- }
1148
- function releaseSpawnLock() {
1149
- try {
1150
- unlinkSync2(SPAWN_LOCK_PATH);
1151
- } catch {
1152
- }
1153
- }
1154
- function connectToSocket() {
1155
- return new Promise((resolve) => {
1156
- if (_socket && _connected) {
1157
- resolve(true);
1158
- return;
1159
- }
1160
- const socket = net.createConnection({ path: SOCKET_PATH });
1161
- const connectTimeout = setTimeout(() => {
1162
- socket.destroy();
1163
- resolve(false);
1164
- }, 2e3);
1165
- socket.on("connect", () => {
1166
- clearTimeout(connectTimeout);
1167
- _socket = socket;
1168
- _connected = true;
1169
- _buffer = "";
1170
- socket.on("data", handleData);
1171
- socket.on("close", () => {
1172
- _connected = false;
1173
- _socket = null;
1174
- for (const [id, entry] of _pending) {
1175
- clearTimeout(entry.timer);
1176
- _pending.delete(id);
1177
- entry.resolve({ error: "Connection closed" });
1178
- }
1179
- });
1180
- socket.on("error", () => {
1181
- _connected = false;
1182
- _socket = null;
1183
- });
1184
- resolve(true);
1185
- });
1186
- socket.on("error", () => {
1187
- clearTimeout(connectTimeout);
1188
- resolve(false);
1189
- });
1190
- });
1191
- }
1192
- async function connectEmbedDaemon() {
1193
- if (_socket && _connected) return true;
1194
- if (await connectToSocket()) return true;
1195
- if (acquireSpawnLock()) {
1196
- try {
1197
- cleanupStaleFiles();
1198
- spawnDaemon();
1199
- } finally {
1200
- releaseSpawnLock();
1201
- }
1202
- }
1203
- const start = Date.now();
1204
- let delay2 = 100;
1205
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1206
- await new Promise((r) => setTimeout(r, delay2));
1207
- if (await connectToSocket()) return true;
1208
- delay2 = Math.min(delay2 * 2, 3e3);
1209
- }
1210
- return false;
1211
- }
1212
- function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1213
- return new Promise((resolve) => {
1214
- if (!_socket || !_connected) {
1215
- resolve({ error: "Not connected" });
1216
- return;
1217
- }
1218
- const id = randomUUID();
1219
- const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1220
- const timer = setTimeout(() => {
1221
- _pending.delete(id);
1222
- resolve({ error: "Request timeout" });
1223
- }, timeoutMs);
1224
- _pending.set(id, { resolve, timer });
1225
- try {
1226
- _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1227
- } catch {
1228
- clearTimeout(timer);
1229
- _pending.delete(id);
1230
- resolve({ error: "Write failed" });
1231
- }
1232
- });
1233
- }
1234
- function isClientConnected() {
1235
- return _connected;
1236
- }
1237
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1238
- var init_exe_daemon_client = __esm({
1239
- "src/lib/exe-daemon-client.ts"() {
1240
- "use strict";
1241
- init_config();
1242
- init_daemon_auth();
1243
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1244
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1245
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1246
- SPAWN_LOCK_STALE_MS = 3e4;
1247
- CONNECT_TIMEOUT_MS = 15e3;
1248
- REQUEST_TIMEOUT_MS = 3e4;
1249
- DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1250
- _socket = null;
1251
- _connected = false;
1252
- _buffer = "";
1253
- _pending = /* @__PURE__ */ new Map();
1254
- MAX_BUFFER = 1e7;
1255
- }
1256
- });
1257
-
1258
- // src/lib/daemon-protocol.ts
1259
- function serializeValue(v) {
1260
- if (v === null || v === void 0) return null;
1261
- if (typeof v === "bigint") return Number(v);
1262
- if (typeof v === "boolean") return v ? 1 : 0;
1263
- if (v instanceof Uint8Array) {
1264
- return { __blob: Buffer.from(v).toString("base64") };
1265
- }
1266
- if (ArrayBuffer.isView(v)) {
1267
- return { __blob: Buffer.from(v.buffer, v.byteOffset, v.byteLength).toString("base64") };
1268
- }
1269
- if (v instanceof ArrayBuffer) {
1270
- return { __blob: Buffer.from(v).toString("base64") };
1271
- }
1272
- if (typeof v === "string" || typeof v === "number") return v;
1273
- return String(v);
1274
- }
1275
- function deserializeValue(v) {
1276
- if (v === null) return null;
1277
- if (typeof v === "object" && v !== null && "__blob" in v) {
1278
- const buf = Buffer.from(v.__blob, "base64");
1279
- return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
1280
- }
1281
- return v;
1282
- }
1283
- function deserializeResultSet(srs) {
1284
- const rows = srs.rows.map((obj) => {
1285
- const values = srs.columns.map(
1286
- (col) => deserializeValue(obj[col] ?? null)
1287
- );
1288
- const row = values;
1289
- for (let i = 0; i < srs.columns.length; i++) {
1290
- const col = srs.columns[i];
1291
- if (col !== void 0) {
1292
- row[col] = values[i] ?? null;
1293
- }
1294
- }
1295
- Object.defineProperty(row, "length", {
1296
- value: values.length,
1297
- enumerable: false
1298
- });
1299
- return row;
1300
- });
1301
- return {
1302
- columns: srs.columns,
1303
- columnTypes: srs.columnTypes ?? [],
1304
- rows,
1305
- rowsAffected: srs.rowsAffected,
1306
- lastInsertRowid: srs.lastInsertRowid != null ? BigInt(srs.lastInsertRowid) : void 0,
1307
- toJSON: () => ({
1308
- columns: srs.columns,
1309
- columnTypes: srs.columnTypes ?? [],
1310
- rows: srs.rows,
1311
- rowsAffected: srs.rowsAffected,
1312
- lastInsertRowid: srs.lastInsertRowid
1313
- })
1314
- };
1315
- }
1316
- var init_daemon_protocol = __esm({
1317
- "src/lib/daemon-protocol.ts"() {
1318
- "use strict";
1319
- }
1320
- });
1321
-
1322
- // src/lib/db-daemon-client.ts
1323
- var db_daemon_client_exports = {};
1324
- __export(db_daemon_client_exports, {
1325
- createDaemonDbClient: () => createDaemonDbClient,
1326
- initDaemonDbClient: () => initDaemonDbClient
1327
- });
1328
- function normalizeStatement2(stmt) {
1329
- if (typeof stmt === "string") {
1330
- return { sql: stmt, args: [] };
1331
- }
1332
- const sql = stmt.sql;
1333
- let args = [];
1334
- if (Array.isArray(stmt.args)) {
1335
- args = stmt.args.map((v) => serializeValue(v));
1336
- } else if (stmt.args && typeof stmt.args === "object") {
1337
- const named = {};
1338
- for (const [key, val] of Object.entries(stmt.args)) {
1339
- named[key] = serializeValue(val);
1340
- }
1341
- return { sql, args: named };
1342
- }
1343
- return { sql, args };
1344
- }
1345
- function createDaemonDbClient(fallbackClient) {
1346
- let _useDaemon = false;
1347
- const client = {
1348
- async execute(stmt) {
1349
- if (!_useDaemon || !isClientConnected()) {
1350
- return fallbackClient.execute(stmt);
1351
- }
1352
- const { sql, args } = normalizeStatement2(stmt);
1353
- const response = await sendDaemonRequest({
1354
- type: "db-execute",
1355
- sql,
1356
- args
1357
- });
1358
- if (response.error) {
1359
- const errMsg = String(response.error);
1360
- if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed" || errMsg === "DB not initialized") {
1361
- process.stderr.write(`[db-daemon] Transport error (${errMsg}), falling back to direct
1362
- `);
1363
- return fallbackClient.execute(stmt);
1364
- }
1365
- throw new Error(errMsg);
1366
- }
1367
- if (response.db) {
1368
- return deserializeResultSet(response.db);
1369
- }
1370
- process.stderr.write("[db-daemon] Unexpected response shape, falling back to direct\n");
1371
- return fallbackClient.execute(stmt);
1372
- },
1373
- async batch(stmts, mode) {
1374
- if (!_useDaemon || !isClientConnected()) {
1375
- return fallbackClient.batch(stmts, mode);
1376
- }
1377
- const statements = stmts.map(normalizeStatement2);
1378
- const response = await sendDaemonRequest({
1379
- type: "db-batch",
1380
- statements,
1381
- mode: mode ?? "deferred"
1382
- });
1383
- if (response.error) {
1384
- const errMsg = String(response.error);
1385
- if (errMsg === "Not connected" || errMsg === "Request timeout" || errMsg === "Write failed" || errMsg === "DB not initialized") {
1386
- process.stderr.write(`[db-daemon] Batch transport error (${errMsg}), falling back to direct
1387
- `);
1388
- return fallbackClient.batch(stmts, mode);
1389
- }
1390
- throw new Error(errMsg);
1391
- }
1392
- const batchResults = response["db-batch"];
1393
- if (batchResults) {
1394
- return batchResults.map(deserializeResultSet);
1395
- }
1396
- process.stderr.write("[db-daemon] Unexpected batch response shape, falling back to direct\n");
1397
- return fallbackClient.batch(stmts, mode);
1398
- },
1399
- // Transaction support — delegate to fallback (transactions need direct connection)
1400
- async transaction(mode) {
1401
- return fallbackClient.transaction(mode);
1402
- },
1403
- // executeMultiple — delegate to fallback (used only for schema migrations)
1404
- async executeMultiple(sql) {
1405
- return fallbackClient.executeMultiple(sql);
1406
- },
1407
- // migrate — delegate to fallback
1408
- async migrate(stmts) {
1409
- return fallbackClient.migrate(stmts);
1410
- },
1411
- // Sync mode — delegate to fallback
1412
- sync() {
1413
- return fallbackClient.sync();
1414
- },
1415
- close() {
1416
- _useDaemon = false;
1417
- },
1418
- get closed() {
1419
- return fallbackClient.closed;
1420
- },
1421
- get protocol() {
1422
- return fallbackClient.protocol;
1423
- }
1424
- };
1425
- return {
1426
- ...client,
1427
- /** Enable daemon routing (call after confirming daemon is connected) */
1428
- _enableDaemon() {
1429
- _useDaemon = true;
1430
- },
1431
- /** Check if daemon routing is active */
1432
- _isDaemonActive() {
1433
- return _useDaemon && isClientConnected();
1434
- }
1435
- };
1436
- }
1437
- async function initDaemonDbClient(fallbackClient) {
1438
- if (process.env.EXE_IS_DAEMON === "1") return null;
1439
- const connected = await connectEmbedDaemon();
1440
- if (!connected) {
1441
- process.stderr.write("[db-daemon] Daemon unavailable \u2014 using direct SQLite\n");
1442
- return null;
1443
- }
1444
- const client = createDaemonDbClient(fallbackClient);
1445
- client._enableDaemon();
1446
- process.stderr.write("[db-daemon] DB routing through daemon (single-writer)\n");
1447
- return client;
1448
- }
1449
- var init_db_daemon_client = __esm({
1450
- "src/lib/db-daemon-client.ts"() {
1451
- "use strict";
1452
- init_exe_daemon_client();
1453
- init_daemon_protocol();
1454
- }
1455
- });
1456
-
1457
- // src/lib/database.ts
1458
- var database_exports = {};
1459
- __export(database_exports, {
1460
- SOFT_DELETE_RETENTION_DAYS: () => SOFT_DELETE_RETENTION_DAYS,
1461
- disposeDatabase: () => disposeDatabase,
1462
- disposeTurso: () => disposeTurso,
1463
- ensureSchema: () => ensureSchema,
1464
- getClient: () => getClient,
1465
- getRawClient: () => getRawClient,
1466
- initDaemonClient: () => initDaemonClient,
1467
- initDatabase: () => initDatabase,
1468
- initTurso: () => initTurso,
1469
- isInitialized: () => isInitialized,
1470
- setExternalClient: () => setExternalClient
1471
- });
1472
- import { chmodSync as chmodSync2, existsSync as existsSync6, statSync as statSync2, copyFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync2 } from "fs";
1473
- import { createClient } from "@libsql/client";
1474
- import { homedir } from "os";
1475
- import { join } from "path";
1476
- function logCatchDebug(context, err) {
1477
- if (_debugDb) {
1478
- process.stderr.write(
1479
- `[database] ${context}: ${err instanceof Error ? err.message : String(err)}
1480
- `
1481
- );
1482
- }
1483
- }
1484
- function acquireDbLock() {
1485
- mkdirSync2(join(homedir(), ".exe-os"), { recursive: true });
1486
- try {
1487
- _lockFd = openSync2(DB_LOCK_PATH, "wx");
1488
- } catch (err) {
1489
- if (err && typeof err === "object" && "code" in err && err.code === "EEXIST") {
1490
- try {
1491
- const lockStat = statSync2(DB_LOCK_PATH);
1492
- if (Date.now() - lockStat.mtimeMs > 6e4) {
1493
- unlinkSync3(DB_LOCK_PATH);
1494
- _lockFd = openSync2(DB_LOCK_PATH, "wx");
1495
- return;
1496
- }
1497
- } catch (e) {
1498
- logCatchDebug("stale lock check", e);
1499
- }
1500
- process.stderr.write(
1501
- "[database] WARN: Another process holds db.lock \u2014 waiting briefly then proceeding.\n"
1502
- );
1503
- return;
1504
- }
1505
- throw err;
1506
- }
1507
- }
1508
- function releaseDbLock() {
1509
- if (_lockFd !== null) {
1510
- try {
1511
- closeSync2(_lockFd);
1512
- } catch (e) {
1513
- logCatchDebug("lock close", e);
1514
- }
1515
- _lockFd = null;
1516
- }
1517
- try {
1518
- unlinkSync3(DB_LOCK_PATH);
1519
- } catch (e) {
1520
- logCatchDebug("lock unlink", e);
1521
- }
1522
- }
1523
- async function initDatabase(config) {
1524
- acquireDbLock();
1525
- if (existsSync6(config.dbPath)) {
1526
- const dbStat = statSync2(config.dbPath);
1527
- if (dbStat.size === 0) {
1528
- const walPath = config.dbPath + "-wal";
1529
- if (existsSync6(walPath) && statSync2(walPath).size > 0) {
1530
- const backupPath = config.dbPath + ".zeroed-" + Date.now();
1531
- copyFileSync(config.dbPath, backupPath);
1532
- unlinkSync3(config.dbPath);
1533
- process.stderr.write(
1534
- `[database] CRITICAL: DB was 0 bytes. Moved to ${backupPath}, attempting WAL recovery.
1535
- `
1536
- );
1537
- } else {
1538
- process.stderr.write(
1539
- `[database] CRITICAL: DB is 0 bytes and no WAL available for recovery. Data may be lost. Check backups at ${config.dbPath}.bak
1540
- `
1541
- );
1542
- }
1543
- }
1544
- }
1545
- if (_walCheckpointTimer) {
1546
- clearInterval(_walCheckpointTimer);
1547
- _walCheckpointTimer = null;
1548
- }
1549
- if (_daemonClient) {
1550
- _daemonClient.close();
1551
- _daemonClient = null;
1552
- }
1553
- if (_adapterClient && _adapterClient !== _resilientClient) {
1554
- _adapterClient.close();
1555
- }
1556
- _adapterClient = null;
1557
- if (_client) {
1558
- _client.close();
1559
- _client = null;
1560
- _resilientClient = null;
1561
- }
1562
- const opts = {
1563
- url: `file:${config.dbPath}`
1564
- };
1565
- if (config.encryptionKey) {
1566
- opts.encryptionKey = config.encryptionKey;
1567
- }
1568
- _client = createClient(opts);
1569
- _resilientClient = wrapWithRetry(_client);
1570
- _adapterClient = _resilientClient;
1571
- await _client.execute("PRAGMA busy_timeout = 30000");
1572
- await _client.execute("PRAGMA journal_mode = WAL");
1573
- if (_walCheckpointTimer) clearInterval(_walCheckpointTimer);
1574
- _walCheckpointTimer = setInterval(() => {
1575
- _client?.execute("PRAGMA wal_checkpoint(PASSIVE)").catch(() => {
1576
- });
1577
- }, 3e4);
1578
- _walCheckpointTimer.unref();
1579
- if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") {
1580
- _adapterClient = await createPrismaDbAdapter(_resilientClient);
1581
- }
1582
- try {
1583
- chmodSync2(config.dbPath, 384);
1584
- for (const suffix of ["-wal", "-shm"]) {
1585
- try {
1586
- chmodSync2(config.dbPath + suffix, 384);
1587
- } catch (chmodErr) {
1588
- process.stderr.write(`[database] chmod ${suffix} failed: ${chmodErr instanceof Error ? chmodErr.message : String(chmodErr)}
1589
- `);
1590
- }
1591
- }
1592
- } catch (chmodErr) {
1593
- process.stderr.write(`[database] chmod db failed: ${chmodErr instanceof Error ? chmodErr.message : String(chmodErr)}
1594
- `);
1595
- }
1596
- releaseDbLock();
1597
- }
1598
- function isInitialized() {
1599
- return _adapterClient !== null || _client !== null;
1600
- }
1601
- function setExternalClient(client) {
1602
- _adapterClient = client;
1603
- }
1604
- function getClient() {
1605
- if (!_adapterClient) {
1606
- throw new Error("Database client not initialized. Call initDatabase() first.");
1607
- }
1608
- if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") {
1609
- return _adapterClient;
1610
- }
1611
- if (process.env.EXE_IS_DAEMON === "1") {
1612
- return _resilientClient;
1613
- }
1614
- if (_daemonClient && _daemonClient._isDaemonActive()) {
1615
- return _daemonClient;
1616
- }
1617
- if (!_resilientClient) {
1618
- return _adapterClient;
1619
- }
1620
- if (process.env.EXE_DB_READONLY === "1" || process.env.EXE_DB_DIRECT === "1") {
1621
- return _resilientClient;
1622
- }
1623
- if (!process.env.EXE_MCP_MODE) {
1624
- process.stderr.write(
1625
- "[database] WARN: Daemon unavailable \u2014 using direct SQLite (single-writer CLI mode).\n"
1626
- );
1627
- return _resilientClient;
1628
- }
1629
- process.stderr.write(
1630
- "[database] ERROR: Daemon is not running \u2014 refusing direct SQLite write access.\n[database] Direct writes from MCP processes bypass the single-writer gate and corrupt FTS5.\n[database] Restart the daemon: kill $(cat ~/.exe-os/exed.pid) && exe-os update\n"
1631
- );
1632
- throw new Error(
1633
- "Daemon not running. Direct SQLite writes from MCP processes are blocked to prevent FTS5 corruption. Restart daemon to restore service."
1634
- );
1635
- }
1636
- async function initDaemonClient() {
1637
- if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") return;
1638
- if (process.env.EXE_IS_DAEMON === "1") return;
1639
- if (process.env.VITEST) return;
1640
- if (!_resilientClient) return;
1641
- if (_daemonClient) return;
1642
- try {
1643
- const { initDaemonDbClient: initDaemonDbClient2 } = await Promise.resolve().then(() => (init_db_daemon_client(), db_daemon_client_exports));
1644
- _daemonClient = await initDaemonDbClient2(_resilientClient);
1645
- } catch (err) {
1646
- process.stderr.write(
1647
- `[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
1648
- `
1649
- );
1650
- }
1651
- }
1652
- function getRawClient() {
1653
- if (!_client) {
1654
- throw new Error("Database client not initialized. Call initDatabase() first.");
1655
- }
1656
- return _client;
1657
- }
1658
- async function ensureSchema() {
1659
- const client = getRawClient();
1660
- await client.execute("PRAGMA journal_mode = WAL");
1661
- await client.execute("PRAGMA busy_timeout = 30000");
1662
- await client.execute("PRAGMA wal_autocheckpoint = 1000");
1663
- try {
1664
- await client.execute("PRAGMA libsql_vector_search_ef = 128");
1665
- } catch (e) {
1666
- logCatchDebug("migration", e);
1667
- }
1668
- await client.executeMultiple(`
1669
- CREATE TABLE IF NOT EXISTS memories (
1670
- id TEXT PRIMARY KEY,
1671
- agent_id TEXT NOT NULL,
1672
- agent_role TEXT NOT NULL,
1673
- session_id TEXT NOT NULL,
1674
- timestamp TEXT NOT NULL,
1675
- tool_name TEXT NOT NULL,
1676
- project_name TEXT NOT NULL,
1677
- has_error INTEGER NOT NULL DEFAULT 0,
1678
- raw_text TEXT NOT NULL,
1679
- vector F32_BLOB(1024),
1680
- version INTEGER NOT NULL DEFAULT 0
1681
- );
1682
-
1683
- CREATE INDEX IF NOT EXISTS idx_memories_agent
1684
- ON memories(agent_id);
1685
-
1686
- CREATE INDEX IF NOT EXISTS idx_memories_timestamp
1687
- ON memories(timestamp);
1688
-
1689
- CREATE INDEX IF NOT EXISTS idx_memories_session
1690
- ON memories(session_id);
1691
-
1692
- CREATE INDEX IF NOT EXISTS idx_memories_project
1693
- ON memories(project_name);
1694
-
1695
- CREATE INDEX IF NOT EXISTS idx_memories_tool
1696
- ON memories(tool_name);
1697
-
1698
- CREATE INDEX IF NOT EXISTS idx_memories_version
1699
- ON memories(version);
1700
-
1701
- CREATE INDEX IF NOT EXISTS idx_memories_agent_project
1702
- ON memories(agent_id, project_name);
1703
- `);
1704
- await client.executeMultiple(`
1705
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
1706
- raw_text,
1707
- content='memories',
1708
- content_rowid='rowid'
1709
- );
1710
-
1711
- CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
1712
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1713
- END;
1714
-
1715
- CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
1716
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1717
- END;
1718
-
1719
- CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories
1720
- WHEN new.status IS NULL OR new.status != 'deleted' BEGIN
1721
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1722
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1723
- END;
1724
-
1725
- -- Soft-delete trigger: remove from FTS when status changes to 'deleted'
1726
- CREATE TRIGGER IF NOT EXISTS memories_fts_soft_delete AFTER UPDATE ON memories
1727
- WHEN new.status = 'deleted' AND (old.status IS NULL OR old.status != 'deleted') BEGIN
1728
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1729
- END;
1730
- `);
1731
- try {
1732
- await client.execute("SELECT COUNT(*) FROM memories_fts LIMIT 1");
1733
- } catch (ftsErr) {
1734
- process.stderr.write(
1735
- `[database] WARN: memories_fts corrupted (${ftsErr instanceof Error ? ftsErr.message : String(ftsErr)}) \u2014 rebuilding FTS index.
1736
- `
1737
- );
1738
- try {
1739
- await client.execute("INSERT INTO memories_fts(memories_fts) VALUES('rebuild')");
1740
- process.stderr.write("[database] FTS index rebuilt successfully.\n");
1741
- } catch (rebuildErr) {
1742
- process.stderr.write(
1743
- `[database] ERROR: FTS rebuild failed: ${rebuildErr instanceof Error ? rebuildErr.message : String(rebuildErr)}
1744
- `
1745
- );
1746
- }
1747
- }
1748
- await client.executeMultiple(`
1749
- CREATE TABLE IF NOT EXISTS sync_meta (
1750
- key TEXT PRIMARY KEY,
1751
- value TEXT NOT NULL
1752
- );
1753
- `);
1754
- await client.executeMultiple(`
1755
- CREATE TABLE IF NOT EXISTS tasks (
1756
- id TEXT PRIMARY KEY,
1757
- title TEXT NOT NULL,
1758
- assigned_to TEXT NOT NULL,
1759
- assigned_by TEXT NOT NULL,
1760
- project_name TEXT NOT NULL,
1761
- priority TEXT NOT NULL DEFAULT 'p1',
1762
- status TEXT NOT NULL DEFAULT 'open',
1763
- task_file TEXT,
1764
- created_at TEXT NOT NULL,
1765
- updated_at TEXT NOT NULL
1766
- );
1767
-
1768
- CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
1769
- ON tasks(assigned_to, status);
1770
- `);
1771
- await client.executeMultiple(`
1772
- CREATE TABLE IF NOT EXISTS behaviors (
1773
- id TEXT PRIMARY KEY,
1774
- agent_id TEXT NOT NULL,
1775
- project_name TEXT,
1776
- domain TEXT,
1777
- content TEXT NOT NULL,
1778
- active INTEGER NOT NULL DEFAULT 1,
1779
- created_at TEXT NOT NULL,
1780
- updated_at TEXT NOT NULL
1781
- );
1782
-
1783
- CREATE INDEX IF NOT EXISTS idx_behaviors_agent
1784
- ON behaviors(agent_id, active);
1785
- `);
1786
- try {
1787
- const coordinatorName = getCoordinatorName();
1788
- const existing = await client.execute({
1789
- sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
1790
- args: [coordinatorName]
1791
- });
1792
- if (Number(existing.rows[0]?.cnt) === 0) {
1793
- const seededAt = "2026-03-25T00:00:00Z";
1794
- for (const [domain, content] of [
1795
- ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
1796
- ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
1797
- ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
1798
- ]) {
1799
- await client.execute({
1800
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
1801
- VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
1802
- args: [coordinatorName, domain, content, seededAt, seededAt]
1803
- });
1804
- }
1805
- }
1806
- } catch (seedErr) {
1807
- logCatchDebug("behavior seed", seedErr);
1808
- }
1809
- try {
1810
- await client.execute({
1811
- sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
1812
- args: []
1813
- });
1814
- } catch (e) {
1815
- logCatchDebug("migration", e);
1816
- }
1817
- try {
1818
- await client.execute({
1819
- sql: `ALTER TABLE behaviors ADD COLUMN vector F32_BLOB(${EMBEDDING_DIM})`,
1820
- args: []
1821
- });
1822
- } catch (e) {
1823
- logCatchDebug("migration", e);
1824
- }
1825
- for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
1826
- try {
1827
- await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
1828
- } catch (e) {
1829
- logCatchDebug("migration", e);
1830
- }
1831
- }
1832
- try {
1833
- await client.execute({
1834
- sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
1835
- args: []
1836
- });
1837
- } catch (e) {
1838
- logCatchDebug("migration", e);
1839
- }
1840
- try {
1841
- await client.execute({
1842
- sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
1843
- args: []
1844
- });
1845
- } catch (e) {
1846
- logCatchDebug("migration", e);
1847
- }
1848
- try {
1849
- await client.execute({
1850
- sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
1851
- ON tasks(parent_task_id)
1852
- WHERE parent_task_id IS NOT NULL`,
1853
- args: []
1854
- });
1855
- } catch (e) {
1856
- logCatchDebug("migration", e);
1857
- }
1858
- try {
1859
- await client.execute({
1860
- sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
1861
- args: []
1862
- });
1863
- } catch (e) {
1864
- logCatchDebug("migration", e);
1865
- }
1866
- try {
1867
- await client.execute({
1868
- sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
1869
- args: []
1870
- });
1871
- } catch (e) {
1872
- logCatchDebug("migration", e);
1873
- }
1874
- try {
1875
- await client.execute({
1876
- sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
1877
- args: []
1878
- });
1879
- } catch (e) {
1880
- logCatchDebug("migration", e);
1881
- }
1882
- try {
1883
- await client.execute({
1884
- sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
1885
- args: []
1886
- });
1887
- } catch (e) {
1888
- logCatchDebug("migration", e);
1889
- }
1890
- try {
1891
- await client.execute({
1892
- sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
1893
- args: []
1894
- });
1895
- } catch (e) {
1896
- logCatchDebug("migration", e);
1897
- }
1898
- try {
1899
- await client.execute({
1900
- sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
1901
- args: []
1902
- });
1903
- } catch (e) {
1904
- logCatchDebug("migration", e);
1905
- }
1906
- try {
1907
- await client.execute({
1908
- sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
1909
- args: []
1910
- });
1911
- } catch (e) {
1912
- logCatchDebug("migration", e);
1913
- }
1914
- try {
1915
- await client.execute({
1916
- sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
1917
- args: []
1918
- });
1919
- } catch (e) {
1920
- logCatchDebug("migration", e);
1921
- }
1922
- try {
1923
- await client.execute({
1924
- sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
1925
- args: []
1926
- });
1927
- } catch (e) {
1928
- logCatchDebug("migration", e);
1929
- }
1930
- try {
1931
- await client.execute({
1932
- sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
1933
- args: []
1934
- });
1935
- } catch (e) {
1936
- logCatchDebug("migration", e);
1937
- }
1938
- try {
1939
- await client.execute({
1940
- sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
1941
- args: []
1942
- });
1943
- } catch (e) {
1944
- logCatchDebug("migration", e);
1945
- }
1946
- try {
1947
- await client.execute({
1948
- sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
1949
- args: []
1950
- });
1951
- } catch (e) {
1952
- logCatchDebug("migration", e);
1953
- }
1954
- try {
1955
- await client.execute({
1956
- sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
1957
- args: []
1958
- });
1959
- } catch (e) {
1960
- logCatchDebug("migration", e);
1961
- }
1962
- await client.executeMultiple(`
1963
- CREATE TABLE IF NOT EXISTS consolidations (
1964
- id TEXT PRIMARY KEY,
1965
- consolidated_memory_id TEXT NOT NULL,
1966
- source_memory_id TEXT NOT NULL,
1967
- created_at TEXT NOT NULL
1968
- );
1969
-
1970
- CREATE INDEX IF NOT EXISTS idx_consolidations_source
1971
- ON consolidations(source_memory_id);
1972
-
1973
- CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
1974
- ON consolidations(consolidated_memory_id);
1975
- `);
1976
- await client.executeMultiple(`
1977
- CREATE TABLE IF NOT EXISTS reminders (
1978
- id TEXT PRIMARY KEY,
1979
- text TEXT NOT NULL,
1980
- created_at TEXT NOT NULL,
1981
- due_date TEXT,
1982
- completed_at TEXT
1983
- );
1984
- `);
1985
- await client.executeMultiple(`
1986
- CREATE TABLE IF NOT EXISTS notifications (
1987
- id TEXT PRIMARY KEY,
1988
- agent_id TEXT NOT NULL,
1989
- agent_role TEXT NOT NULL,
1990
- event TEXT NOT NULL,
1991
- project TEXT NOT NULL,
1992
- summary TEXT NOT NULL,
1993
- task_file TEXT,
1994
- session_scope TEXT,
1995
- read INTEGER NOT NULL DEFAULT 0,
1996
- created_at TEXT NOT NULL
1997
- );
1998
-
1999
- CREATE INDEX IF NOT EXISTS idx_notifications_read
2000
- ON notifications(read);
2001
-
2002
- CREATE INDEX IF NOT EXISTS idx_notifications_agent
2003
- ON notifications(agent_id, session_scope);
2004
-
2005
- CREATE INDEX IF NOT EXISTS idx_notifications_task_file
2006
- ON notifications(task_file);
2007
- `);
2008
- await client.executeMultiple(`
2009
- CREATE TABLE IF NOT EXISTS schedules (
2010
- id TEXT PRIMARY KEY,
2011
- cron TEXT NOT NULL,
2012
- description TEXT NOT NULL,
2013
- job_type TEXT NOT NULL DEFAULT 'report',
2014
- prompt TEXT,
2015
- assigned_to TEXT,
2016
- project_name TEXT,
2017
- active INTEGER NOT NULL DEFAULT 1,
2018
- use_crontab INTEGER NOT NULL DEFAULT 0,
2019
- created_at TEXT NOT NULL
2020
- );
2021
- `);
2022
- await client.executeMultiple(`
2023
- CREATE TABLE IF NOT EXISTS device_registry (
2024
- device_id TEXT PRIMARY KEY,
2025
- friendly_name TEXT NOT NULL,
2026
- hostname TEXT NOT NULL,
2027
- projects TEXT NOT NULL DEFAULT '[]',
2028
- agents TEXT NOT NULL DEFAULT '[]',
2029
- connected INTEGER DEFAULT 0,
2030
- last_seen TEXT NOT NULL
2031
- );
2032
- `);
2033
- await client.executeMultiple(`
2034
- CREATE TABLE IF NOT EXISTS messages (
2035
- id TEXT PRIMARY KEY,
2036
- from_agent TEXT NOT NULL,
2037
- from_device TEXT NOT NULL DEFAULT 'local',
2038
- target_agent TEXT NOT NULL,
2039
- target_project TEXT,
2040
- target_device TEXT NOT NULL DEFAULT 'local',
2041
- session_scope TEXT,
2042
- content TEXT NOT NULL,
2043
- priority TEXT DEFAULT 'normal',
2044
- status TEXT DEFAULT 'pending',
2045
- server_seq INTEGER,
2046
- retry_count INTEGER DEFAULT 0,
2047
- created_at TEXT NOT NULL,
2048
- delivered_at TEXT,
2049
- processed_at TEXT,
2050
- failed_at TEXT,
2051
- failure_reason TEXT
2052
- );
2053
-
2054
- CREATE INDEX IF NOT EXISTS idx_messages_target
2055
- ON messages(target_agent, session_scope, status);
2056
-
2057
- CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2058
- ON messages(target_agent, session_scope, from_agent, server_seq);
2059
- `);
2060
- try {
2061
- await client.execute({
2062
- sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2063
- args: []
2064
- });
2065
- } catch (e) {
2066
- logCatchDebug("migration", e);
2067
- }
2068
- try {
2069
- await client.execute({
2070
- sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2071
- args: []
2072
- });
2073
- } catch (e) {
2074
- logCatchDebug("migration", e);
2075
- }
2076
- await client.executeMultiple(`
2077
- CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2078
- ON notifications(agent_id, session_scope, read, created_at);
2079
-
2080
- CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2081
- ON messages(target_agent, session_scope, status, created_at);
2082
- `);
2083
- try {
2084
- await client.execute({
2085
- sql: `UPDATE memories SET project_name = 'exe-create' WHERE project_name = 'web'`,
2086
- args: []
2087
- });
2088
- await client.execute({
2089
- sql: `UPDATE memories SET project_name = 'exe-os' WHERE project_name = 'worker'`,
2090
- args: []
2091
- });
2092
- await client.execute({
2093
- sql: `UPDATE tasks SET project_name = 'exe-create' WHERE project_name = 'web'`,
2094
- args: []
2095
- });
2096
- await client.execute({
2097
- sql: `UPDATE tasks SET project_name = 'exe-os' WHERE project_name = 'worker'`,
2098
- args: []
2099
- });
2100
- } catch (e) {
2101
- logCatchDebug("migration", e);
2102
- }
2103
- await client.executeMultiple(`
2104
- CREATE TABLE IF NOT EXISTS trajectories (
2105
- id TEXT PRIMARY KEY,
2106
- task_id TEXT NOT NULL,
2107
- agent_id TEXT NOT NULL,
2108
- project_name TEXT NOT NULL,
2109
- task_title TEXT NOT NULL,
2110
- signature TEXT NOT NULL,
2111
- signature_hash TEXT NOT NULL,
2112
- tool_count INTEGER NOT NULL,
2113
- skill_id TEXT,
2114
- created_at TEXT NOT NULL
2115
- );
2116
-
2117
- CREATE INDEX IF NOT EXISTS idx_trajectories_hash
2118
- ON trajectories(signature_hash);
2119
-
2120
- CREATE INDEX IF NOT EXISTS idx_trajectories_agent
2121
- ON trajectories(agent_id);
2122
- `);
2123
- try {
2124
- await client.execute("ALTER TABLE trajectories ADD COLUMN skill_id TEXT");
2125
- } catch (e) {
2126
- logCatchDebug("migration", e);
2127
- }
2128
- await client.executeMultiple(`
2129
- CREATE TABLE IF NOT EXISTS consolidations (
2130
- id TEXT PRIMARY KEY,
2131
- consolidated_memory_id TEXT NOT NULL,
2132
- source_memory_id TEXT NOT NULL,
2133
- created_at TEXT NOT NULL
2134
- );
2135
-
2136
- CREATE INDEX IF NOT EXISTS idx_consolidations_source
2137
- ON consolidations(source_memory_id);
2138
- `);
2139
- await client.executeMultiple(`
2140
- CREATE TABLE IF NOT EXISTS audit_trail (
2141
- id INTEGER PRIMARY KEY AUTOINCREMENT,
2142
- timestamp TEXT NOT NULL,
2143
- session_id TEXT NOT NULL,
2144
- agent_id TEXT NOT NULL,
2145
- tool TEXT NOT NULL,
2146
- input TEXT,
2147
- decision TEXT NOT NULL,
2148
- reason TEXT,
2149
- is_customer_facing INTEGER NOT NULL DEFAULT 0
2150
- );
2151
-
2152
- CREATE INDEX IF NOT EXISTS idx_audit_trail_agent
2153
- ON audit_trail(agent_id, timestamp);
2154
-
2155
- CREATE INDEX IF NOT EXISTS idx_audit_trail_session
2156
- ON audit_trail(session_id);
2157
- `);
2158
- try {
2159
- await client.execute({
2160
- sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
2161
- args: []
2162
- });
2163
- } catch (e) {
2164
- logCatchDebug("migration", e);
2165
- }
2166
- try {
2167
- await client.execute({
2168
- sql: `ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5`,
2169
- args: []
2170
- });
2171
- } catch (e) {
2172
- logCatchDebug("migration", e);
2173
- }
2174
- try {
2175
- await client.execute({
2176
- sql: `ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'`,
2177
- args: []
2178
- });
2179
- } catch (e) {
2180
- logCatchDebug("migration", e);
2181
- }
2182
- try {
2183
- await client.execute({
2184
- sql: `ALTER TABLE memories ADD COLUMN deleted_at TEXT`,
2185
- args: []
2186
- });
2187
- } catch (e) {
2188
- logCatchDebug("migration", e);
2189
- }
2190
- try {
2191
- await client.execute({
2192
- sql: `ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7`,
2193
- args: []
2194
- });
2195
- } catch (e) {
2196
- logCatchDebug("migration", e);
2197
- }
2198
- try {
2199
- await client.execute({
2200
- sql: `ALTER TABLE memories ADD COLUMN last_accessed TEXT`,
2201
- args: []
2202
- });
2203
- } catch (e) {
2204
- logCatchDebug("migration", e);
2205
- }
2206
- try {
2207
- await client.execute({
2208
- sql: `UPDATE memories SET last_accessed = timestamp WHERE last_accessed IS NULL`,
2209
- args: []
2210
- });
2211
- } catch (e) {
2212
- logCatchDebug("migration", e);
2213
- }
2214
- try {
2215
- await client.execute({
2216
- sql: `ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0`,
2217
- args: []
2218
- });
2219
- } catch (e) {
2220
- logCatchDebug("migration", e);
2221
- }
2222
- try {
2223
- await client.execute({
2224
- sql: `ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0`,
2225
- args: []
2226
- });
2227
- } catch (e) {
2228
- logCatchDebug("migration", e);
2229
- }
2230
- for (const col of [
2231
- "ALTER TABLE memories ADD COLUMN content_hash TEXT",
2232
- "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT"
2233
- ]) {
2234
- try {
2235
- await client.execute(col);
2236
- } catch (e) {
2237
- logCatchDebug("migration", e);
2238
- }
2239
- }
2240
- try {
2241
- await client.execute(
2242
- `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
2243
- );
2244
- } catch (e) {
2245
- logCatchDebug("migration", e);
2246
- }
2247
- try {
2248
- await client.execute(
2249
- `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2250
- ON memories(content_hash, agent_id, project_name, memory_type)
2251
- WHERE content_hash IS NOT NULL`
2252
- );
2253
- } catch (e) {
2254
- logCatchDebug("migration", e);
2255
- }
2256
- await client.executeMultiple(`
2257
- CREATE TABLE IF NOT EXISTS entities (
2258
- id TEXT PRIMARY KEY,
2259
- name TEXT NOT NULL,
2260
- type TEXT NOT NULL,
2261
- first_seen TEXT NOT NULL,
2262
- last_seen TEXT NOT NULL,
2263
- properties TEXT DEFAULT '{}',
2264
- UNIQUE(name, type)
2265
- );
2266
-
2267
- CREATE TABLE IF NOT EXISTS relationships (
2268
- id TEXT PRIMARY KEY,
2269
- source_entity_id TEXT NOT NULL,
2270
- target_entity_id TEXT NOT NULL,
2271
- type TEXT NOT NULL,
2272
- weight REAL DEFAULT 1.0,
2273
- timestamp TEXT NOT NULL,
2274
- properties TEXT DEFAULT '{}',
2275
- UNIQUE(source_entity_id, target_entity_id, type)
2276
- );
2277
-
2278
- CREATE TABLE IF NOT EXISTS entity_memories (
2279
- entity_id TEXT NOT NULL,
2280
- memory_id TEXT NOT NULL,
2281
- PRIMARY KEY (entity_id, memory_id)
2282
- );
2283
-
2284
- CREATE TABLE IF NOT EXISTS relationship_memories (
2285
- relationship_id TEXT NOT NULL,
2286
- memory_id TEXT NOT NULL,
2287
- PRIMARY KEY (relationship_id, memory_id)
2288
- );
2289
-
2290
- CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
2291
- CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
2292
- CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
2293
- CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
2294
-
2295
- CREATE TABLE IF NOT EXISTS hyperedges (
2296
- id TEXT PRIMARY KEY,
2297
- label TEXT NOT NULL,
2298
- relation TEXT NOT NULL,
2299
- confidence REAL DEFAULT 1.0,
2300
- timestamp TEXT NOT NULL
2301
- );
2302
-
2303
- CREATE TABLE IF NOT EXISTS hyperedge_nodes (
2304
- hyperedge_id TEXT NOT NULL,
2305
- entity_id TEXT NOT NULL,
2306
- PRIMARY KEY (hyperedge_id, entity_id)
2307
- );
2308
-
2309
- CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
2310
- name,
2311
- content=entities,
2312
- content_rowid=rowid
2313
- );
2314
-
2315
- CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
2316
- INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
2317
- END;
2318
-
2319
- CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
2320
- INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
2321
- END;
2322
-
2323
- CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
2324
- INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
2325
- INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
2326
- END;
2327
- `);
2328
- try {
2329
- await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
2330
- } catch (e) {
2331
- logCatchDebug("migration", e);
2332
- }
2333
- await client.executeMultiple(`
2334
- CREATE TABLE IF NOT EXISTS entity_aliases (
2335
- alias TEXT NOT NULL PRIMARY KEY,
2336
- canonical_entity_id TEXT NOT NULL
2337
- );
2338
- CREATE INDEX IF NOT EXISTS idx_entity_aliases_canonical ON entity_aliases(canonical_entity_id);
2339
- `);
2340
- for (const col of [
2341
- "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
2342
- "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
2343
- ]) {
2344
- try {
2345
- await client.execute(col);
2346
- } catch (e) {
2347
- logCatchDebug("migration", e);
2348
- }
2349
- }
2350
- try {
2351
- await client.execute(
2352
- `CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)`
2353
- );
2354
- } catch (e) {
2355
- logCatchDebug("migration", e);
2356
- }
2357
- await client.executeMultiple(`
2358
- CREATE TABLE IF NOT EXISTS identity (
2359
- id INTEGER PRIMARY KEY AUTOINCREMENT,
2360
- agent_id TEXT NOT NULL UNIQUE,
2361
- content_hash TEXT NOT NULL,
2362
- updated_at TEXT NOT NULL,
2363
- updated_by TEXT NOT NULL
2364
- );
2365
-
2366
- CREATE INDEX IF NOT EXISTS idx_identity_agent ON identity(agent_id);
2367
- `);
2368
- await client.executeMultiple(`
2369
- CREATE TABLE IF NOT EXISTS chat_history (
2370
- id INTEGER PRIMARY KEY AUTOINCREMENT,
2371
- session_id TEXT NOT NULL,
2372
- role TEXT NOT NULL,
2373
- content TEXT NOT NULL,
2374
- tool_name TEXT,
2375
- tool_id TEXT,
2376
- is_error INTEGER NOT NULL DEFAULT 0,
2377
- timestamp INTEGER NOT NULL
2378
- );
2379
-
2380
- CREATE INDEX IF NOT EXISTS idx_chat_history_session
2381
- ON chat_history(session_id, id);
2382
- `);
2383
- await client.executeMultiple(`
2384
- CREATE TABLE IF NOT EXISTS session_events (
2385
- id TEXT PRIMARY KEY,
2386
- agent_id TEXT NOT NULL,
2387
- agent_role TEXT NOT NULL,
2388
- session_id TEXT NOT NULL,
2389
- session_scope TEXT,
2390
- project_name TEXT NOT NULL,
2391
- event_index INTEGER NOT NULL,
2392
- event_type TEXT NOT NULL,
2393
- tool_name TEXT,
2394
- tool_use_id TEXT,
2395
- content TEXT NOT NULL,
2396
- payload_json TEXT,
2397
- has_error INTEGER NOT NULL DEFAULT 0,
2398
- created_at TEXT NOT NULL
2399
- );
2400
-
2401
- CREATE INDEX IF NOT EXISTS idx_session_events_agent_time
2402
- ON session_events(agent_id, created_at DESC);
2403
-
2404
- CREATE INDEX IF NOT EXISTS idx_session_events_session_index
2405
- ON session_events(session_id, event_index);
2406
-
2407
- CREATE INDEX IF NOT EXISTS idx_session_events_scope_agent_time
2408
- ON session_events(session_scope, agent_id, created_at DESC);
2409
- `);
2410
- await client.executeMultiple(`
2411
- CREATE TABLE IF NOT EXISTS workspaces (
2412
- id TEXT PRIMARY KEY,
2413
- slug TEXT NOT NULL UNIQUE,
2414
- name TEXT NOT NULL,
2415
- owner_agent_id TEXT,
2416
- created_at TEXT NOT NULL,
2417
- metadata TEXT
2418
- );
2419
-
2420
- CREATE INDEX IF NOT EXISTS idx_workspaces_slug
2421
- ON workspaces(slug);
2422
- `);
2423
- await client.executeMultiple(`
2424
- CREATE TABLE IF NOT EXISTS documents (
2425
- id TEXT PRIMARY KEY,
2426
- workspace_id TEXT NOT NULL,
2427
- filename TEXT NOT NULL,
2428
- mime TEXT,
2429
- source_type TEXT,
2430
- user_id TEXT,
2431
- uploaded_at TEXT NOT NULL,
2432
- metadata TEXT,
2433
- FOREIGN KEY (workspace_id) REFERENCES workspaces(id)
2434
- );
2435
-
2436
- CREATE INDEX IF NOT EXISTS idx_documents_workspace
2437
- ON documents(workspace_id);
2438
-
2439
- CREATE INDEX IF NOT EXISTS idx_documents_user
2440
- ON documents(user_id);
2441
- `);
2442
- for (const column of [
2443
- "workspace_id TEXT",
2444
- "document_id TEXT",
2445
- "user_id TEXT",
2446
- "char_offset INTEGER",
2447
- "page_number INTEGER"
2448
- ]) {
2449
- try {
2450
- await client.execute({
2451
- sql: `ALTER TABLE memories ADD COLUMN ${column}`,
2452
- args: []
2453
- });
2454
- } catch (e) {
2455
- logCatchDebug("migration", e);
2456
- }
2457
- }
2458
- for (const col of [
2459
- "ALTER TABLE memories ADD COLUMN source_path TEXT",
2460
- "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
2461
- ]) {
2462
- try {
2463
- await client.execute(col);
2464
- } catch (e) {
2465
- logCatchDebug("migration", e);
2466
- }
2467
- }
2468
- await client.executeMultiple(`
2469
- CREATE INDEX IF NOT EXISTS idx_memories_workspace
2470
- ON memories(workspace_id);
2471
-
2472
- CREATE INDEX IF NOT EXISTS idx_memories_document
2473
- ON memories(document_id);
2474
-
2475
- CREATE INDEX IF NOT EXISTS idx_memories_user
2476
- ON memories(user_id);
2477
- `);
2478
- await client.executeMultiple(`
2479
- CREATE TABLE IF NOT EXISTS session_kills (
2480
- id TEXT PRIMARY KEY,
2481
- session_name TEXT NOT NULL,
2482
- agent_id TEXT NOT NULL,
2483
- killed_at TIMESTAMP NOT NULL,
2484
- reason TEXT NOT NULL,
2485
- ticks_idle INTEGER,
2486
- estimated_tokens_saved INTEGER
2487
- );
2488
-
2489
- CREATE INDEX IF NOT EXISTS idx_session_kills_killed_at
2490
- ON session_kills(killed_at);
2491
-
2492
- CREATE INDEX IF NOT EXISTS idx_session_kills_agent
2493
- ON session_kills(agent_id);
2494
- `);
2495
- await client.execute(`
2496
- CREATE TABLE IF NOT EXISTS company_procedures (
2497
- id TEXT PRIMARY KEY,
2498
- title TEXT NOT NULL,
2499
- content TEXT NOT NULL,
2500
- priority TEXT NOT NULL DEFAULT 'p0',
2501
- domain TEXT,
2502
- active INTEGER NOT NULL DEFAULT 1,
2503
- created_at TEXT NOT NULL,
2504
- updated_at TEXT NOT NULL
2505
- )
2506
- `);
2507
- const legacyProcedureObject = await client.execute({
2508
- sql: "SELECT type FROM sqlite_master WHERE name = 'global_procedures'",
2509
- args: []
2510
- });
2511
- const legacyProcedureType = legacyProcedureObject.rows[0]?.type == null ? null : String(legacyProcedureObject.rows[0].type);
2512
- if (legacyProcedureType === "table") {
2513
- await client.execute(`
2514
- INSERT OR IGNORE INTO company_procedures
2515
- (id, title, content, priority, domain, active, created_at, updated_at)
2516
- SELECT id, title, content, priority, domain, active, created_at, updated_at
2517
- FROM global_procedures
2518
- `);
2519
- await client.executeMultiple(`
2520
- CREATE TRIGGER IF NOT EXISTS global_procedures_mirror_insert
2521
- AFTER INSERT ON global_procedures
2522
- BEGIN
2523
- INSERT OR IGNORE INTO company_procedures
2524
- (id, title, content, priority, domain, active, created_at, updated_at)
2525
- VALUES
2526
- (NEW.id, NEW.title, NEW.content, NEW.priority, NEW.domain, NEW.active, NEW.created_at, NEW.updated_at);
2527
- END;
2528
-
2529
- CREATE TRIGGER IF NOT EXISTS global_procedures_mirror_update
2530
- AFTER UPDATE ON global_procedures
2531
- BEGIN
2532
- UPDATE company_procedures
2533
- SET title = NEW.title,
2534
- content = NEW.content,
2535
- priority = NEW.priority,
2536
- domain = NEW.domain,
2537
- active = NEW.active,
2538
- created_at = NEW.created_at,
2539
- updated_at = NEW.updated_at
2540
- WHERE id = OLD.id;
2541
- END;
2542
- `);
2543
- } else {
2544
- await client.execute(`
2545
- CREATE VIEW IF NOT EXISTS global_procedures AS
2546
- SELECT id, title, content, priority, domain, active, created_at, updated_at
2547
- FROM company_procedures
2548
- `);
2549
- await client.executeMultiple(`
2550
- CREATE TRIGGER IF NOT EXISTS global_procedures_insert
2551
- INSTEAD OF INSERT ON global_procedures
2552
- BEGIN
2553
- INSERT INTO company_procedures
2554
- (id, title, content, priority, domain, active, created_at, updated_at)
2555
- VALUES
2556
- (NEW.id, NEW.title, NEW.content, NEW.priority, NEW.domain, NEW.active, NEW.created_at, NEW.updated_at);
2557
- END;
2558
-
2559
- CREATE TRIGGER IF NOT EXISTS global_procedures_update
2560
- INSTEAD OF UPDATE ON global_procedures
2561
- BEGIN
2562
- UPDATE company_procedures
2563
- SET title = NEW.title,
2564
- content = NEW.content,
2565
- priority = NEW.priority,
2566
- domain = NEW.domain,
2567
- active = NEW.active,
2568
- created_at = NEW.created_at,
2569
- updated_at = NEW.updated_at
2570
- WHERE id = OLD.id;
2571
- END;
2572
- `);
2573
- }
2574
- await client.executeMultiple(`
2575
- CREATE TABLE IF NOT EXISTS conversations (
2576
- id TEXT PRIMARY KEY,
2577
- platform TEXT NOT NULL,
2578
- external_id TEXT,
2579
- sender_id TEXT NOT NULL,
2580
- sender_name TEXT,
2581
- sender_phone TEXT,
2582
- sender_email TEXT,
2583
- recipient_id TEXT,
2584
- channel_id TEXT NOT NULL,
2585
- thread_id TEXT,
2586
- reply_to_id TEXT,
2587
- content_text TEXT,
2588
- content_media TEXT,
2589
- content_metadata TEXT,
2590
- agent_response TEXT,
2591
- agent_name TEXT,
2592
- timestamp TEXT NOT NULL,
2593
- ingested_at TEXT NOT NULL
2594
- );
2595
-
2596
- CREATE INDEX IF NOT EXISTS idx_conversations_platform
2597
- ON conversations(platform);
2598
-
2599
- CREATE INDEX IF NOT EXISTS idx_conversations_sender
2600
- ON conversations(sender_id);
2601
-
2602
- CREATE INDEX IF NOT EXISTS idx_conversations_timestamp
2603
- ON conversations(timestamp);
2604
-
2605
- CREATE INDEX IF NOT EXISTS idx_conversations_thread
2606
- ON conversations(thread_id);
2607
-
2608
- CREATE INDEX IF NOT EXISTS idx_conversations_channel
2609
- ON conversations(channel_id);
2610
- `);
2611
- await client.executeMultiple(`
2612
- CREATE TABLE IF NOT EXISTS session_agent_map (
2613
- session_uuid TEXT PRIMARY KEY,
2614
- agent_id TEXT NOT NULL,
2615
- session_name TEXT,
2616
- task_id TEXT,
2617
- project_name TEXT,
2618
- started_at TEXT NOT NULL,
2619
- cache_cold_count INTEGER NOT NULL DEFAULT 0
2620
- );
2621
-
2622
- CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
2623
- ON session_agent_map(agent_id);
2624
- `);
2625
- await client.executeMultiple(`
2626
- CREATE TABLE IF NOT EXISTS agent_file_reads (
2627
- session_uuid TEXT NOT NULL,
2628
- agent_id TEXT NOT NULL,
2629
- file_path TEXT NOT NULL,
2630
- read_at TEXT NOT NULL,
2631
- commit_hash TEXT,
2632
- PRIMARY KEY (session_uuid, file_path)
2633
- );
2634
-
2635
- CREATE INDEX IF NOT EXISTS idx_agent_file_reads_agent_read_at
2636
- ON agent_file_reads(agent_id, read_at);
2637
- `);
2638
- try {
2639
- const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
2640
- if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
2641
- await client.execute({
2642
- sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
2643
- SELECT session_id, agent_id, '', MIN(timestamp)
2644
- FROM memories
2645
- WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
2646
- GROUP BY session_id, agent_id`,
2647
- args: []
2648
- });
2649
- }
2650
- } catch (e) {
2651
- logCatchDebug("session_agent_map backfill", e);
2652
- }
2653
- try {
2654
- await client.execute({
2655
- sql: `ALTER TABLE session_agent_map ADD COLUMN cache_cold_count INTEGER NOT NULL DEFAULT 0`,
2656
- args: []
2657
- });
2658
- } catch (e) {
2659
- logCatchDebug("migration", e);
2660
- }
2661
- try {
2662
- await client.execute({
2663
- sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
2664
- args: []
2665
- });
2666
- } catch (e) {
2667
- logCatchDebug("migration", e);
2668
- }
2669
- try {
2670
- await client.execute({
2671
- sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
2672
- args: []
2673
- });
2674
- } catch (e) {
2675
- logCatchDebug("migration", e);
2676
- }
2677
- try {
2678
- await client.execute({
2679
- sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
2680
- args: []
2681
- });
2682
- } catch (e) {
2683
- logCatchDebug("migration", e);
2684
- }
2685
- try {
2686
- await client.execute({
2687
- sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
2688
- args: []
2689
- });
2690
- } catch (e) {
2691
- logCatchDebug("migration", e);
2692
- }
2693
- try {
2694
- await client.execute({
2695
- sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
2696
- args: []
2697
- });
2698
- } catch (e) {
2699
- logCatchDebug("migration", e);
2700
- }
2701
- try {
2702
- await client.execute({
2703
- sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
2704
- args: []
2705
- });
2706
- } catch (e) {
2707
- logCatchDebug("migration", e);
2708
- }
2709
- await client.executeMultiple(`
2710
- CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
2711
- content_text,
2712
- sender_name,
2713
- agent_response,
2714
- content='conversations',
2715
- content_rowid='rowid'
2716
- );
2717
-
2718
- CREATE TRIGGER IF NOT EXISTS conversations_fts_ai AFTER INSERT ON conversations BEGIN
2719
- INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
2720
- VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
2721
- END;
2722
-
2723
- CREATE TRIGGER IF NOT EXISTS conversations_fts_ad AFTER DELETE ON conversations BEGIN
2724
- INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
2725
- VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
2726
- END;
2727
-
2728
- CREATE TRIGGER IF NOT EXISTS conversations_fts_au AFTER UPDATE ON conversations BEGIN
2729
- INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
2730
- VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
2731
- INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
2732
- VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
2733
- END;
2734
- `);
2735
- await client.executeMultiple(`
2736
- CREATE TABLE IF NOT EXISTS memory_cards (
2737
- id TEXT PRIMARY KEY,
2738
- memory_id TEXT NOT NULL,
2739
- agent_id TEXT NOT NULL,
2740
- session_id TEXT NOT NULL,
2741
- project_name TEXT,
2742
- timestamp TEXT NOT NULL,
2743
- card_type TEXT NOT NULL,
2744
- subject TEXT,
2745
- predicate TEXT,
2746
- object TEXT,
2747
- content TEXT NOT NULL,
2748
- source_ref TEXT,
2749
- confidence REAL DEFAULT 0.6,
2750
- active INTEGER DEFAULT 1,
2751
- created_at TEXT NOT NULL
2752
- );
2753
-
2754
- CREATE INDEX IF NOT EXISTS idx_memory_cards_agent
2755
- ON memory_cards(agent_id, active, timestamp);
2756
-
2757
- CREATE INDEX IF NOT EXISTS idx_memory_cards_memory
2758
- ON memory_cards(memory_id);
2759
-
2760
- CREATE VIRTUAL TABLE IF NOT EXISTS memory_cards_fts
2761
- USING fts5(content, subject, predicate, object, content='memory_cards', content_rowid='rowid');
2762
-
2763
- CREATE TRIGGER IF NOT EXISTS memory_cards_fts_ai AFTER INSERT ON memory_cards BEGIN
2764
- INSERT INTO memory_cards_fts(rowid, content, subject, predicate, object)
2765
- VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
2766
- END;
2767
-
2768
- CREATE TRIGGER IF NOT EXISTS memory_cards_fts_ad AFTER DELETE ON memory_cards BEGIN
2769
- INSERT INTO memory_cards_fts(memory_cards_fts, rowid, content, subject, predicate, object)
2770
- VALUES('delete', old.rowid, old.content, old.subject, old.predicate, old.object);
2771
- END;
2772
-
2773
- CREATE TRIGGER IF NOT EXISTS memory_cards_fts_au AFTER UPDATE ON memory_cards BEGIN
2774
- INSERT INTO memory_cards_fts(memory_cards_fts, rowid, content, subject, predicate, object)
2775
- VALUES('delete', old.rowid, old.content, old.subject, old.predicate, old.object);
2776
- INSERT INTO memory_cards_fts(rowid, content, subject, predicate, object)
2777
- VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
2778
- END;
2779
- `);
2780
- await client.executeMultiple(`
2781
- CREATE TABLE IF NOT EXISTS agent_sessions (
2782
- id TEXT PRIMARY KEY,
2783
- agent_id TEXT NOT NULL,
2784
- project_name TEXT,
2785
- started_at TEXT NOT NULL,
2786
- last_event_at TEXT NOT NULL,
2787
- event_count INTEGER NOT NULL DEFAULT 0,
2788
- properties TEXT DEFAULT '{}'
2789
- );
2790
-
2791
- CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_time
2792
- ON agent_sessions(agent_id, started_at);
2793
-
2794
- CREATE TABLE IF NOT EXISTS agent_goals (
2795
- id TEXT PRIMARY KEY,
2796
- statement TEXT NOT NULL,
2797
- owner_agent_id TEXT,
2798
- project_name TEXT,
2799
- status TEXT NOT NULL DEFAULT 'open',
2800
- priority INTEGER NOT NULL DEFAULT 5,
2801
- success_criteria TEXT,
2802
- parent_goal_id TEXT,
2803
- due_at TEXT,
2804
- achieved_at TEXT,
2805
- supersedes_id TEXT,
2806
- created_at TEXT NOT NULL,
2807
- updated_at TEXT NOT NULL,
2808
- source_memory_id TEXT
2809
- );
2810
-
2811
- CREATE INDEX IF NOT EXISTS idx_agent_goals_project_status
2812
- ON agent_goals(project_name, status, priority);
2813
-
2814
- CREATE TABLE IF NOT EXISTS agent_events (
2815
- id TEXT PRIMARY KEY,
2816
- event_type TEXT NOT NULL,
2817
- occurred_at TEXT NOT NULL,
2818
- sequence_index INTEGER NOT NULL,
2819
- actor_agent_id TEXT,
2820
- agent_role TEXT,
2821
- project_name TEXT,
2822
- session_id TEXT,
2823
- task_id TEXT,
2824
- goal_id TEXT,
2825
- parent_event_id TEXT,
2826
- intention TEXT,
2827
- outcome TEXT,
2828
- evidence_memory_id TEXT,
2829
- impact TEXT,
2830
- payload TEXT DEFAULT '{}',
2831
- created_at TEXT NOT NULL
2832
- );
2833
-
2834
- CREATE INDEX IF NOT EXISTS idx_agent_events_time
2835
- ON agent_events(occurred_at, sequence_index);
2836
-
2837
- CREATE INDEX IF NOT EXISTS idx_agent_events_session_seq
2838
- ON agent_events(session_id, sequence_index);
2839
-
2840
- CREATE INDEX IF NOT EXISTS idx_agent_events_goal_time
2841
- ON agent_events(goal_id, occurred_at);
2842
-
2843
- CREATE INDEX IF NOT EXISTS idx_agent_events_memory
2844
- ON agent_events(evidence_memory_id);
2845
-
2846
- CREATE TABLE IF NOT EXISTS agent_goal_links (
2847
- id TEXT PRIMARY KEY,
2848
- goal_id TEXT NOT NULL,
2849
- link_type TEXT NOT NULL,
2850
- target_id TEXT NOT NULL,
2851
- target_type TEXT NOT NULL,
2852
- created_at TEXT NOT NULL
2853
- );
2854
-
2855
- CREATE INDEX IF NOT EXISTS idx_agent_goal_links_goal
2856
- ON agent_goal_links(goal_id, target_type);
2857
-
2858
- CREATE TABLE IF NOT EXISTS agent_semantic_labels (
2859
- id TEXT PRIMARY KEY,
2860
- source_memory_id TEXT NOT NULL,
2861
- event_id TEXT,
2862
- labeler TEXT NOT NULL,
2863
- schema_version INTEGER NOT NULL DEFAULT 1,
2864
- confidence REAL NOT NULL DEFAULT 0,
2865
- labels TEXT NOT NULL,
2866
- created_at TEXT NOT NULL,
2867
- updated_at TEXT NOT NULL
2868
- );
2869
-
2870
- CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_memory
2871
- ON agent_semantic_labels(source_memory_id, labeler);
2872
-
2873
- CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_event
2874
- ON agent_semantic_labels(event_id);
2875
-
2876
- CREATE TABLE IF NOT EXISTS agent_reflection_checkpoints (
2877
- id TEXT PRIMARY KEY,
2878
- project_name TEXT,
2879
- session_id TEXT,
2880
- window_start_at TEXT NOT NULL,
2881
- window_end_at TEXT NOT NULL,
2882
- event_count INTEGER NOT NULL DEFAULT 0,
2883
- goal_count INTEGER NOT NULL DEFAULT 0,
2884
- success_count INTEGER NOT NULL DEFAULT 0,
2885
- failure_count INTEGER NOT NULL DEFAULT 0,
2886
- risk_count INTEGER NOT NULL DEFAULT 0,
2887
- summary TEXT NOT NULL,
2888
- learnings TEXT NOT NULL DEFAULT '[]',
2889
- next_actions TEXT NOT NULL DEFAULT '[]',
2890
- evidence_event_ids TEXT NOT NULL DEFAULT '[]',
2891
- confidence REAL NOT NULL DEFAULT 0,
2892
- created_at TEXT NOT NULL
2893
- );
2894
-
2895
- CREATE INDEX IF NOT EXISTS idx_agent_reflection_project_time
2896
- ON agent_reflection_checkpoints(project_name, window_end_at);
2897
-
2898
- CREATE INDEX IF NOT EXISTS idx_agent_reflection_session_time
2899
- ON agent_reflection_checkpoints(session_id, window_end_at);
2900
- `);
2901
- try {
2902
- await client.execute({
2903
- sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
2904
- args: []
2905
- });
2906
- } catch (e) {
2907
- logCatchDebug("migration", e);
2908
- }
2909
- try {
2910
- await client.execute(
2911
- `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
2912
- );
2913
- } catch (e) {
2914
- logCatchDebug("migration", e);
2915
- }
2916
- try {
2917
- await client.execute({
2918
- sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
2919
- args: []
2920
- });
2921
- await client.execute({
2922
- sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
2923
- args: []
2924
- });
2925
- } catch (e) {
2926
- logCatchDebug("migration", e);
2927
- }
2928
- try {
2929
- await client.execute({
2930
- sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
2931
- args: []
2932
- });
2933
- } catch (e) {
2934
- logCatchDebug("migration", e);
2935
- }
2936
- try {
2937
- await client.execute(
2938
- `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
2939
- );
2940
- } catch (e) {
2941
- logCatchDebug("migration", e);
2942
- }
2943
- for (const col of [
2944
- "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
2945
- "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
2946
- ]) {
2947
- try {
2948
- await client.execute(col);
2949
- } catch (e) {
2950
- logCatchDebug("migration", e);
2951
- }
2952
- }
2953
- try {
2954
- await client.execute({
2955
- sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
2956
- args: []
2957
- });
2958
- } catch (e) {
2959
- logCatchDebug("migration", e);
2960
- }
2961
- try {
2962
- await client.execute(
2963
- `CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
2964
- );
2965
- } catch (e) {
2966
- logCatchDebug("migration", e);
2967
- }
2968
- for (const col of [
2969
- "ALTER TABLE memories ADD COLUMN valid_from TEXT",
2970
- "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
2971
- ]) {
2972
- try {
2973
- await client.execute(col);
2974
- } catch (e) {
2975
- logCatchDebug("migration", e);
2976
- }
2977
- }
2978
- try {
2979
- await client.execute({
2980
- sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
2981
- args: []
2982
- });
2983
- } catch (e) {
2984
- logCatchDebug("migration", e);
2985
- }
2986
- try {
2987
- await client.execute({
2988
- sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
2989
- args: []
2990
- });
2991
- } catch (e) {
2992
- logCatchDebug("migration", e);
2993
- }
2994
- try {
2995
- await client.execute(
2996
- `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
2997
- );
2998
- } catch (e) {
2999
- logCatchDebug("migration", e);
3000
- }
3001
- try {
3002
- await client.execute({
3003
- sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
3004
- args: []
3005
- });
3006
- } catch (e) {
3007
- logCatchDebug("migration", e);
3008
- }
3009
- for (const col of [
3010
- "ALTER TABLE memories ADD COLUMN intent TEXT",
3011
- "ALTER TABLE memories ADD COLUMN outcome TEXT",
3012
- "ALTER TABLE memories ADD COLUMN domain TEXT",
3013
- "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
3014
- "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
3015
- "ALTER TABLE memories ADD COLUMN chain_position TEXT",
3016
- "ALTER TABLE memories ADD COLUMN review_status TEXT",
3017
- "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
3018
- "ALTER TABLE memories ADD COLUMN file_paths TEXT",
3019
- "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
3020
- "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
3021
- "ALTER TABLE memories ADD COLUMN token_cost REAL",
3022
- "ALTER TABLE memories ADD COLUMN audience TEXT",
3023
- "ALTER TABLE memories ADD COLUMN language_type TEXT",
3024
- "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
3025
- ]) {
3026
- try {
3027
- await client.execute(col);
3028
- } catch (e) {
3029
- logCatchDebug("migration", e);
3030
- }
3031
- }
3032
- try {
3033
- await client.execute({
3034
- sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3035
- args: []
3036
- });
3037
- } catch (e) {
3038
- logCatchDebug("migration", e);
3039
- }
3040
- try {
3041
- await client.execute({
3042
- sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
3043
- args: []
3044
- });
3045
- } catch (e) {
3046
- logCatchDebug("migration", e);
3047
- }
3048
- try {
3049
- await client.execute({
3050
- sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
3051
- args: []
3052
- });
3053
- } catch (e) {
3054
- logCatchDebug("migration", e);
3055
- }
3056
- try {
3057
- await client.execute({
3058
- sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
3059
- args: []
3060
- });
3061
- } catch (e) {
3062
- logCatchDebug("migration", e);
3063
- }
3064
- }
3065
- async function disposeDatabase() {
3066
- if (_walCheckpointTimer) {
3067
- clearInterval(_walCheckpointTimer);
3068
- _walCheckpointTimer = null;
3069
- }
3070
- if (_client) {
3071
- try {
3072
- await _client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3073
- } catch (e) {
3074
- logCatchDebug("WAL checkpoint", e);
3075
- }
3076
- }
3077
- if (_daemonClient) {
3078
- _daemonClient.close();
3079
- _daemonClient = null;
3080
- }
3081
- if (_adapterClient && _adapterClient !== _resilientClient) {
3082
- _adapterClient.close();
3083
- }
3084
- _adapterClient = null;
3085
- if (_client) {
3086
- _client.close();
3087
- _client = null;
3088
- _resilientClient = null;
3089
- }
3090
- releaseDbLock();
3091
- }
3092
- var _debugDb, _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, _lockFd, DB_LOCK_PATH, initTurso, SOFT_DELETE_RETENTION_DAYS, disposeTurso;
3093
- var init_database = __esm({
3094
- "src/lib/database.ts"() {
3095
- "use strict";
3096
- init_db_retry();
3097
- init_employees();
3098
- init_database_adapter();
3099
- init_memory();
3100
- _debugDb = process.env.EXE_DEBUG === "1";
3101
- _client = null;
3102
- _resilientClient = null;
3103
- _walCheckpointTimer = null;
3104
- _daemonClient = null;
3105
- _adapterClient = null;
3106
- _lockFd = null;
3107
- DB_LOCK_PATH = join(homedir(), ".exe-os", "db.lock");
3108
- initTurso = initDatabase;
3109
- SOFT_DELETE_RETENTION_DAYS = 7;
3110
- disposeTurso = disposeDatabase;
3111
- }
3112
- });
3113
-
3114
- // src/lib/shard-manager.ts
3115
- var shard_manager_exports = {};
3116
- __export(shard_manager_exports, {
3117
- auditShardHealth: () => auditShardHealth,
3118
- disposeShards: () => disposeShards,
3119
- ensureShardSchema: () => ensureShardSchema,
3120
- getOpenShardCount: () => getOpenShardCount,
3121
- getReadyShardClient: () => getReadyShardClient,
3122
- getShardClient: () => getShardClient,
3123
- getShardsDir: () => getShardsDir,
3124
- initShardManager: () => initShardManager,
3125
- isShardingEnabled: () => isShardingEnabled,
3126
- listShards: () => listShards,
3127
- shardExists: () => shardExists
3128
- });
3129
- import path7 from "path";
3130
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
3131
- import { createClient as createClient2 } from "@libsql/client";
3132
- function initShardManager(encryptionKey) {
3133
- _encryptionKey = encryptionKey;
3134
- _keyValidated = false;
3135
- _keyValidationPromise = null;
3136
- if (!existsSync8(SHARDS_DIR)) {
3137
- mkdirSync3(SHARDS_DIR, { recursive: true });
3138
- }
3139
- const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
3140
- if (existingShards.length === 0) {
3141
- _keyValidated = true;
3142
- }
3143
- _shardingEnabled = true;
3144
- if (_evictionTimer) clearInterval(_evictionTimer);
3145
- _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
3146
- _evictionTimer.unref();
3147
- }
3148
- async function validateEncryptionKey() {
3149
- if (_keyValidated) return true;
3150
- if (!_encryptionKey) return false;
3151
- const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
3152
- if (existingShards.length === 0) {
3153
- _keyValidated = true;
3154
- return true;
3155
- }
3156
- for (const shardFile of existingShards.slice(0, 3)) {
3157
- const dbPath = path7.join(SHARDS_DIR, shardFile);
3158
- const testClient = createClient2({ url: `file:${dbPath}`, encryptionKey: _encryptionKey });
3159
- try {
3160
- await testClient.execute("SELECT COUNT(*) FROM sqlite_schema");
3161
- testClient.close();
3162
- _keyValidated = true;
3163
- return true;
3164
- } catch {
3165
- try {
3166
- testClient.close();
3167
- } catch {
3168
- }
3169
- }
3170
- }
3171
- process.stderr.write(
3172
- `[shard-manager] WARNING: encryption key cannot read any existing shards (${existingShards.length} found). New shard creation disabled to prevent stranded files. Run /exe-doctor to audit.
3173
- `
3174
- );
3175
- _shardingEnabled = false;
3176
- return false;
3177
- }
3178
- function isShardingEnabled() {
3179
- return _shardingEnabled;
3180
- }
3181
- function getShardsDir() {
3182
- return SHARDS_DIR;
3183
- }
3184
- function getShardClient(projectName) {
3185
- if (!_encryptionKey) {
3186
- throw new Error("Shard manager not initialized. Call initShardManager() first.");
3187
- }
3188
- const safeName = safeShardName(projectName);
3189
- if (!safeName || safeName === "unknown") {
3190
- throw new Error(`Invalid project name for shard: "${projectName}" (resolved to "${safeName}")`);
3191
- }
3192
- const cached = _shards.get(safeName);
3193
- if (cached) {
3194
- _shardLastAccess.set(safeName, Date.now());
3195
- return cached;
3196
- }
3197
- while (_shards.size >= MAX_OPEN_SHARDS) {
3198
- evictLRU();
3199
- }
3200
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
3201
- const client = createClient2({
3202
- url: `file:${dbPath}`,
3203
- encryptionKey: _encryptionKey
3204
- });
3205
- _shards.set(safeName, client);
3206
- _shardLastAccess.set(safeName, Date.now());
3207
- return client;
3208
- }
3209
- function shardExists(projectName) {
3210
- const safeName = safeShardName(projectName);
3211
- return existsSync8(path7.join(SHARDS_DIR, `${safeName}.db`));
3212
- }
3213
- function safeShardName(projectName) {
3214
- return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
3215
- }
3216
- function listShards() {
3217
- if (!existsSync8(SHARDS_DIR)) return [];
3218
- return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3219
- }
3220
- async function auditShardHealth(options = {}) {
3221
- if (!_encryptionKey) {
3222
- throw new Error("Shard manager not initialized. Call initShardManager() first.");
3223
- }
3224
- const repair = options.repair === true;
3225
- const dryRun = options.dryRun === true;
3226
- const names = listShards();
3227
- const shards = [];
3228
- for (const name of names) {
3229
- const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3230
- const stat = statSync4(dbPath);
3231
- const item = {
3232
- name,
3233
- path: dbPath,
3234
- ok: false,
3235
- unreadable: false,
3236
- error: null,
3237
- size: stat.size,
3238
- mtime: stat.mtime.toISOString(),
3239
- memoryCount: null
3240
- };
3241
- const client = createClient2({
3242
- url: `file:${dbPath}`,
3243
- encryptionKey: _encryptionKey
3244
- });
3245
- try {
3246
- await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3247
- const hasMemories = await client.execute(
3248
- "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3249
- );
3250
- if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3251
- const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3252
- item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3253
- }
3254
- item.ok = true;
3255
- } catch (err) {
3256
- const message = err instanceof Error ? err.message : String(err);
3257
- item.error = message;
3258
- item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3259
- if (item.unreadable && repair && !dryRun) {
3260
- client.close();
3261
- _shards.delete(name);
3262
- _shardLastAccess.delete(name);
3263
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3264
- const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3265
- renameSync3(dbPath, archivedPath);
3266
- item.archivedPath = archivedPath;
3267
- }
3268
- } finally {
3269
- try {
3270
- client.close();
3271
- } catch {
3272
- }
3273
- }
3274
- shards.push(item);
3275
- }
3276
- return {
3277
- total: shards.length,
3278
- ok: shards.filter((s) => s.ok).length,
3279
- unreadable: shards.filter((s) => s.unreadable).length,
3280
- archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3281
- shards
3282
- };
3283
- }
3284
- async function ensureShardSchema(client) {
3285
- await client.execute("PRAGMA journal_mode = WAL");
3286
- await client.execute("PRAGMA busy_timeout = 30000");
3287
- try {
3288
- await client.execute("PRAGMA libsql_vector_search_ef = 128");
3289
- } catch {
3290
- }
3291
- await client.executeMultiple(`
3292
- CREATE TABLE IF NOT EXISTS memories (
3293
- id TEXT PRIMARY KEY,
3294
- agent_id TEXT NOT NULL,
3295
- agent_role TEXT NOT NULL,
3296
- session_id TEXT NOT NULL,
3297
- timestamp TEXT NOT NULL,
3298
- tool_name TEXT NOT NULL,
3299
- project_name TEXT NOT NULL,
3300
- has_error INTEGER NOT NULL DEFAULT 0,
3301
- raw_text TEXT NOT NULL,
3302
- vector F32_BLOB(1024),
3303
- version INTEGER NOT NULL DEFAULT 0
3304
- );
3305
-
3306
- CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
3307
- CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
3308
- CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
3309
- `);
3310
- await client.executeMultiple(`
3311
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
3312
- raw_text,
3313
- content='memories',
3314
- content_rowid='rowid'
3315
- );
3316
-
3317
- CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
3318
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
3319
- END;
3320
-
3321
- CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
3322
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
3323
- END;
3324
-
3325
- CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
3326
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
3327
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
3328
- END;
3329
- `);
3330
- for (const col of [
3331
- "ALTER TABLE memories ADD COLUMN task_id TEXT",
3332
- "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
3333
- "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
3334
- "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
3335
- "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
3336
- "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
3337
- "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
3338
- "ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
3339
- "ALTER TABLE memories ADD COLUMN content_hash TEXT",
3340
- "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
3341
- "ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
3342
- "ALTER TABLE memories ADD COLUMN last_accessed TEXT",
3343
- // Wiki linkage columns (must match database.ts)
3344
- "ALTER TABLE memories ADD COLUMN workspace_id TEXT",
3345
- "ALTER TABLE memories ADD COLUMN document_id TEXT",
3346
- "ALTER TABLE memories ADD COLUMN user_id TEXT",
3347
- "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
3348
- "ALTER TABLE memories ADD COLUMN page_number INTEGER",
3349
- // Source provenance columns (must match database.ts)
3350
- "ALTER TABLE memories ADD COLUMN source_path TEXT",
3351
- "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
3352
- "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
3353
- "ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
3354
- // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
3355
- "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
3356
- "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
3357
- "ALTER TABLE memories ADD COLUMN trajectory TEXT",
3358
- // Metadata enrichment columns (must match database.ts)
3359
- "ALTER TABLE memories ADD COLUMN intent TEXT",
3360
- "ALTER TABLE memories ADD COLUMN outcome TEXT",
3361
- "ALTER TABLE memories ADD COLUMN domain TEXT",
3362
- "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
3363
- "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
3364
- "ALTER TABLE memories ADD COLUMN chain_position TEXT",
3365
- "ALTER TABLE memories ADD COLUMN review_status TEXT",
3366
- "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
3367
- "ALTER TABLE memories ADD COLUMN file_paths TEXT",
3368
- "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
3369
- "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
3370
- "ALTER TABLE memories ADD COLUMN token_cost REAL",
3371
- "ALTER TABLE memories ADD COLUMN audience TEXT",
3372
- "ALTER TABLE memories ADD COLUMN language_type TEXT",
3373
- "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT",
3374
- "ALTER TABLE memories ADD COLUMN deleted_at TEXT",
3375
- // Temporal validity (must match database.ts)
3376
- "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3377
- "ALTER TABLE memories ADD COLUMN invalid_at TEXT",
3378
- // Multi-agent visibility (must match database.ts)
3379
- "ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'",
3380
- // Procedure binding (AFM-3, must match database.ts)
3381
- "ALTER TABLE memories ADD COLUMN procedure_for TEXT",
3382
- // Memory strength scoring — Ebbinghaus decay (must match database.ts)
3383
- "ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0"
3384
- ]) {
3385
- try {
3386
- await client.execute(col);
3387
- } catch {
3388
- }
3389
- }
3390
- for (const idx of [
3391
- "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3392
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3393
- "CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
3394
- ]) {
3395
- try {
3396
- await client.execute(idx);
3397
- } catch {
3398
- }
3399
- }
3400
- try {
3401
- await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
3402
- } catch {
3403
- }
3404
- for (const idx of [
3405
- "CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
3406
- "CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
3407
- "CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
3408
- ]) {
3409
- try {
3410
- await client.execute(idx);
3411
- } catch {
3412
- }
3413
- }
3414
- await client.executeMultiple(`
3415
- CREATE TABLE IF NOT EXISTS entities (
3416
- id TEXT PRIMARY KEY,
3417
- name TEXT NOT NULL,
3418
- type TEXT NOT NULL,
3419
- first_seen TEXT NOT NULL,
3420
- last_seen TEXT NOT NULL,
3421
- properties TEXT DEFAULT '{}',
3422
- UNIQUE(name, type)
3423
- );
3424
-
3425
- CREATE TABLE IF NOT EXISTS relationships (
3426
- id TEXT PRIMARY KEY,
3427
- source_entity_id TEXT NOT NULL,
3428
- target_entity_id TEXT NOT NULL,
3429
- type TEXT NOT NULL,
3430
- weight REAL DEFAULT 1.0,
3431
- timestamp TEXT NOT NULL,
3432
- properties TEXT DEFAULT '{}',
3433
- UNIQUE(source_entity_id, target_entity_id, type)
3434
- );
3435
-
3436
- CREATE TABLE IF NOT EXISTS entity_memories (
3437
- entity_id TEXT NOT NULL,
3438
- memory_id TEXT NOT NULL,
3439
- PRIMARY KEY (entity_id, memory_id)
3440
- );
3441
-
3442
- CREATE TABLE IF NOT EXISTS relationship_memories (
3443
- relationship_id TEXT NOT NULL,
3444
- memory_id TEXT NOT NULL,
3445
- PRIMARY KEY (relationship_id, memory_id)
3446
- );
3447
-
3448
- CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
3449
- CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
3450
- CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
3451
- CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
3452
- CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
3453
-
3454
- CREATE TABLE IF NOT EXISTS hyperedges (
3455
- id TEXT PRIMARY KEY,
3456
- label TEXT NOT NULL,
3457
- relation TEXT NOT NULL,
3458
- confidence REAL DEFAULT 1.0,
3459
- timestamp TEXT NOT NULL
3460
- );
3461
-
3462
- CREATE TABLE IF NOT EXISTS hyperedge_nodes (
3463
- hyperedge_id TEXT NOT NULL,
3464
- entity_id TEXT NOT NULL,
3465
- PRIMARY KEY (hyperedge_id, entity_id)
3466
- );
3467
- `);
3468
- for (const col of [
3469
- "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
3470
- "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
3471
- ]) {
3472
- try {
3473
- await client.execute(col);
3474
- } catch {
3475
- }
3476
- }
3477
- }
3478
- async function getReadyShardClient(projectName) {
3479
- if (!_keyValidated) {
3480
- if (!_keyValidationPromise) {
3481
- _keyValidationPromise = validateEncryptionKey();
3482
- }
3483
- const valid = await _keyValidationPromise;
3484
- if (!valid) {
3485
- throw new Error(
3486
- `Shard creation blocked: encryption key mismatch with existing shards. Run /exe-doctor to audit.`
3487
- );
3488
- }
3489
- }
3490
- const safeName = safeShardName(projectName);
3491
- let client = getShardClient(projectName);
3492
- try {
3493
- await ensureShardSchema(client);
3494
- return client;
3495
- } catch (err) {
3496
- const message = err instanceof Error ? err.message : String(err);
3497
- if (!/SQLITE_NOTADB|file is not a database/i.test(message)) throw err;
3498
- client.close();
3499
- _shards.delete(safeName);
3500
- _shardLastAccess.delete(safeName);
3501
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
3502
- if (existsSync8(dbPath)) {
3503
- const stat = statSync4(dbPath);
3504
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3505
- const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
3506
- renameSync3(dbPath, archivedPath);
3507
- process.stderr.write(
3508
- `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
3509
- `
3510
- );
3511
- }
3512
- client = getShardClient(projectName);
3513
- await ensureShardSchema(client);
3514
- return client;
3515
- }
3516
- }
3517
- function evictLRU() {
3518
- let oldest = null;
3519
- let oldestTime = Infinity;
3520
- for (const [name, time] of _shardLastAccess) {
3521
- if (time < oldestTime) {
3522
- oldestTime = time;
3523
- oldest = name;
3524
- }
3525
- }
3526
- if (oldest) {
3527
- const client = _shards.get(oldest);
3528
- if (client) {
3529
- client.close();
3530
- }
3531
- _shards.delete(oldest);
3532
- _shardLastAccess.delete(oldest);
3533
- }
3534
- }
3535
- function evictIdleShards() {
3536
- const now = Date.now();
3537
- const toEvict = [];
3538
- for (const [name, lastAccess] of _shardLastAccess) {
3539
- if (now - lastAccess > SHARD_IDLE_MS) {
3540
- toEvict.push(name);
3541
- }
3542
- }
3543
- for (const name of toEvict) {
3544
- const client = _shards.get(name);
3545
- if (client) {
3546
- client.close();
3547
- }
3548
- _shards.delete(name);
3549
- _shardLastAccess.delete(name);
3550
- }
3551
- }
3552
- function getOpenShardCount() {
3553
- return _shards.size;
3554
- }
3555
- function disposeShards() {
3556
- if (_evictionTimer) {
3557
- clearInterval(_evictionTimer);
3558
- _evictionTimer = null;
3559
- }
3560
- for (const [, client] of _shards) {
3561
- client.close();
3562
- }
3563
- _shards.clear();
3564
- _shardLastAccess.clear();
3565
- _shardingEnabled = false;
3566
- _encryptionKey = null;
3567
- }
3568
- var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled, _keyValidated, _keyValidationPromise;
3569
- var init_shard_manager = __esm({
3570
- "src/lib/shard-manager.ts"() {
3571
- "use strict";
3572
- init_config();
3573
- SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
3574
- SHARD_IDLE_MS = 5 * 60 * 1e3;
3575
- MAX_OPEN_SHARDS = 10;
3576
- EVICTION_INTERVAL_MS = 60 * 1e3;
3577
- _shards = /* @__PURE__ */ new Map();
3578
- _shardLastAccess = /* @__PURE__ */ new Map();
3579
- _evictionTimer = null;
3580
- _encryptionKey = null;
3581
- _shardingEnabled = false;
3582
- _keyValidated = false;
3583
- _keyValidationPromise = null;
3584
- }
3585
- });
3586
-
3587
- // src/lib/platform-procedures.ts
3588
- var PLATFORM_PROCEDURES, PLATFORM_PROCEDURE_TITLES;
3589
- var init_platform_procedures = __esm({
3590
- "src/lib/platform-procedures.ts"() {
3591
- "use strict";
3592
- PLATFORM_PROCEDURES = [
3593
- // --- Foundation: what is exe-os ---
3594
- {
3595
- title: "What is exe-os \u2014 the operating model every agent must understand",
3596
- domain: "architecture",
3597
- priority: "p0",
3598
- content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO, CTO, CMO, engineers, and content production specialists. Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
3599
- },
3600
- {
3601
- title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
3602
- domain: "architecture",
3603
- priority: "p0",
3604
- content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code, Codex, or OpenCode. The founder picks their default tool at setup. The COO manages employees in tmux sessions. Each coordinator session is a separate window/project. Employees run in their own tmux panes via create_task auto-spawn. The founder talks to the COO; the COO orchestrates the team. The tool is the shell, exe-os is the brain."
3605
- },
3606
- {
3607
- title: "Sessions explained \u2014 coordinator session names and projects",
3608
- domain: "architecture",
3609
- priority: "p0",
3610
- content: "Each coordinator session is an isolated project session. One might be exe-os development, another might be exe-wiki. Each session spawns its own employees using {employee}-{coordinatorSession}. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
3611
- },
3612
- {
3613
- title: "Runtime settings \u2014 COO can view and change tools per agent",
3614
- domain: "workflow",
3615
- priority: "p1",
3616
- content: "exe-os supports three tools: Claude Code (Anthropic), Codex (OpenAI), and OpenCode (open source, 75+ providers). Each agent can use a different tool and model. COO uses set_agent_config MCP tool to view or change settings. Call with no args to show all agents. Call with agent_id + runtime + model to change. Users can also run `exe-os settings` from terminal for interactive arrow-key selection."
3617
- },
3618
- // --- Updates and deployment ---
3619
- {
3620
- title: "How to update exe-os \u2014 CLI update + stack update (two steps)",
3621
- domain: "operations",
3622
- priority: "p0",
3623
- content: 'Updating exe-os is a two-step process. Step 1 \u2014 update the CLI: `npm install -g @askexenow/exe-os@latest` (gets bug fixes, new platform procedures, new tools). Step 2 \u2014 update the VPS stack (if applicable): `exe-os stack-update --target <version> --yes` (pulls new Docker images for exed, wiki, CRM, gateway). Step 1 MUST happen before Step 2 \u2014 the stack-update command itself may have bug fixes. After updating, restart the daemon: the deploy script handles this automatically. Check update availability: `exe-os update --check` or config(action="check_update"). Agents should NEVER run npm install or stack-update autonomously \u2014 always confirm with the user first.'
3624
- },
3625
- {
3626
- title: "First install \u2014 setup wizard and license activation",
3627
- domain: "operations",
3628
- priority: "p1",
3629
- content: "Fresh install: `npm install -g @askexenow/exe-os` then run `exe` to start the setup wizard. The wizard prompts for: encryption passphrase (creates master key), license key (exe_sk_* from AskExe team), COO name, and optional team members. No license key = free tier (1 employee, 5K memories). After setup: hooks install automatically, MCP server registers in ~/.claude.json, daemon starts. Verify health: run `exe-os healthcheck` or use mcp_ping() tool."
3630
- },
3631
- // --- Hierarchy and dispatch ---
3632
- {
3633
- title: "Chain of command \u2014 who talks to whom",
3634
- domain: "workflow",
3635
- priority: "p0",
3636
- content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
3637
- },
3638
- {
3639
- title: "Orchestration phase guidance \u2014 recommend, never trap",
3640
- domain: "workflow",
3641
- priority: "p1",
3642
- content: "New customers start best in Phase 1: founder \u2194 coordinator/Chief of Staff, building company context. Suggest Phase 2 executives when domain work repeats; suggest Phase 3 parallel execution only when review/permission gates are ready. This is guidance, not a blocker: users may jump phases anytime. Never overwrite their phase, role titles, identities, or custom org design."
3643
- },
3644
- {
3645
- title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
3646
- domain: "identity",
3647
- priority: "p0",
3648
- content: "These procedures reference 'COO' as a shorthand for the coordinator role. This is an INTERNAL routing slot used by exe-os code (chain-of-command checks, dispatch logic, session detection). It is NOT your display title. Your actual title comes from your identity file's `title:` field \u2014 that is what you use externally: introductions, sign-offs, team comms, and any user-facing text. If your identity says `title: AI Chief of Staff`, you are the AI Chief of Staff. The internal routing slot stays unchanged for code compatibility \u2014 never rename it, but also never introduce yourself as 'COO' unless your identity file explicitly says so. The founder chose your title; respect it."
3649
- },
3650
- {
3651
- title: "Single dispatch path \u2014 create_task only",
3652
- domain: "workflow",
3653
- priority: "p0",
3654
- content: "create_task is the ONLY way to dispatch work to another agent. No direct ensureEmployee calls, no manual tmux spawns, no send_message for actionable work. create_task \u2192 system auto-spawns \u2192 session correctly named. ONE PATH. No backdoors. No exceptions."
3655
- },
3656
- // --- Session isolation ---
3657
- {
3658
- title: "Session scoping \u2014 stay in your coordinator boundary",
3659
- domain: "security",
3660
- priority: "p0",
3661
- content: "Session scoping is mandatory. Managers dispatch to workers within their own coordinator session ONLY. Employee sessions use {employee}-{coordinatorSession}. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating coordinator session."
3662
- },
3663
- {
3664
- title: "Session isolation \u2014 never touch another session's work",
3665
- domain: "workflow",
3666
- priority: "p0",
3667
- content: "Sessions are isolated. A coordinator session owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another coordinator session. (2) Never review work from a different session \u2014 report that it belongs to another session and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: employee sessions work ONLY on their parent coordinator session's tasks. Cross-session work is a system violation."
3668
- },
3669
- // --- Engineering: session scoping in code ---
3670
- {
3671
- title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
3672
- domain: "architecture",
3673
- priority: "p0",
3674
- content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching the current coordinator session. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ coordinator sessions simultaneously."
3675
- },
3676
- // --- Hard constraints ---
3677
- {
3678
- title: "What you CANNOT do in exe-os \u2014 hard constraints",
3679
- domain: "security",
3680
- priority: "p0",
3681
- content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
3682
- },
3683
- {
3684
- title: "Destructive operations \u2014 mandatory reviewer gate",
3685
- domain: "security",
3686
- priority: "p0",
3687
- content: "Before ANY destructive operation (delete, remove, overwrite, drop, reset, force-push, truncate), you MUST: (1) Have your full task spec accessible \u2014 if you cannot read it, STOP and report to your reviewer. Never improvise destructive actions. (2) Confirm with your reviewer (assigned_by or COO) before executing. (3) If the task spec explicitly authorizes the operation, proceed \u2014 but log it. Violation = immediate task failure. This applies to ALL agents regardless of role."
3688
- },
3689
- {
3690
- title: "Customer patch triage \u2014 upstream bug vs customization",
3691
- domain: "support",
3692
- priority: "p0",
3693
- content: "When an agent encounters a suspected Exe OS bug, update breakage, MCP/tool failure, installer issue, memory/orchestration defect, or customer-local patch need, it MUST use create_bug_report. Do this before or alongside any local workaround so the report reaches AskExe support directly via the customer's license. Do NOT ask the founder for permission to file a required bug report. If create_bug_report is deferred/lazy-loaded, load it and call it. If it is unavailable in the live MCP surface, report 'create_bug_report unavailable in this session' and save a local report in exe/output \u2014 never claim the tool does not exist unless the live MCP surface was checked. If upstream delivery fails, call support_test (MCP) and include its result in the local report so AskExe can distinguish customer setup, license provisioning, and server intake issues; only ask the founder to run `exe-os support test` if MCP is disconnected/unavailable. Classify first: upstream_bug = reproducible exe-os/platform defect; customer_customization = identity, behavior, procedure, config, branding, workflow preference that belongs in customer-owned layers; emergency_hotfix = temporary local patch. For upstream bugs/emergency hotfixes include version, repro steps, expected/actual, files changed, workaround, and local diff summary. Avoid permanent platform-code patches unless founder approves; if a hotfix is unavoidable, document it in the bug report and re-check after npm update."
3694
- },
3695
- {
3696
- title: "Bug report status check \u2014 surface available fixes on boot",
3697
- domain: "support",
3698
- priority: "p1",
3699
- content: "Once per session (COO boot only, never repeat), call support(action='list_my_bugs') to check if any previously filed bug reports have been fixed by AskExe. If any report has status 'closed' with a fixed_version, surface it to the founder immediately: '\u{1F527} N bug fix(es) available \u2014 run exe-os update to get version X.Y.Z'. You can also file new bugs with support(action='create_bug') and check status anytime with support(action='list_my_bugs'). This boot check is one-time, not a recurring poll. If the tool is unavailable, skip silently."
3700
- },
3701
- {
3702
- title: "Feature request triage \u2014 upstream feature vs local customization",
3703
- domain: "support",
3704
- priority: "p0",
3705
- content: "When an agent or founder identifies a desired capability that exe-os does not yet provide, the COO (or equivalent coordinator) must decide: is this a local customization (identity, behavior, procedure, config, branding, workflow preference that can be configured in customer-owned layers) or an upstream feature request (a platform capability that requires changes to exe-os code, shipped via npm update)? Local customizations: implement immediately using store_behavior, update_identity, company_procedure, or config changes. Upstream features: use create_feature_request to submit to AskExe. Include use case, business impact, and current workaround. Do NOT ask the founder for permission to file a feature request \u2014 file it proactively when the need is clear."
3706
- },
3707
- {
3708
- title: "Feature request status check \u2014 surface shipped features on boot",
3709
- domain: "support",
3710
- priority: "p1",
3711
- content: "Once per session (COO boot only, never repeat), call support(action='list_my_features') to check if any previously filed feature requests have been shipped by AskExe. If any request has status 'shipped' with a shipped_version, surface it to the founder immediately: '\u{1F680} N feature(s) shipped \u2014 run exe-os update to get version X.Y.Z'. You can also file new requests with support(action='create_feature') and check status anytime with support(action='list_my_features'). This boot check is one-time, not a recurring poll. If the tool is unavailable, skip silently."
3712
- },
3713
- // --- Tool guidance ---
3714
- {
3715
- title: "How to use company_actions \u2014 execute business actions through gateway connectors",
3716
- domain: "tools",
3717
- priority: "p2",
3718
- content: "The company_actions tool executes business actions through gateway connectors (e.g. send WhatsApp, trigger workflows, update CRM). It routes through the exe-gateway on the VPS. Actions are defined by the customer's gateway configuration \u2014 each connector (WhatsApp, Shopify, email, etc.) exposes specific actions. Use query_company_brain to find available data first, then company_actions to act on it. Requires gateway auth token. Read-only founders should NOT use this \u2014 it mutates external state."
3719
- },
3720
- // --- Release awareness ---
3721
- {
3722
- title: "What's New check \u2014 surface new features after update",
3723
- domain: "support",
3724
- priority: "p1",
3725
- content: "Once per session (COO boot only, never repeat), check if the installed exe-os version is newer than the last session. If it is, read the bundled release-notes.json (at the package root) and surface a brief summary to the founder: 'Updated to exe-os vX.Y.Z \u2014 N new features, M fixes.' List the top 3 features by name. This helps the founder know what they got from the update. If release-notes.json doesn't exist or the version hasn't changed, skip silently. Never repeat this check in the same session."
3726
- },
3727
- // --- Platform vs Customer ownership ---
3728
- {
3729
- title: "What the platform provides vs what you customize",
3730
- domain: "architecture",
3731
- priority: "p0",
3732
- content: "Exe OS has two layers. PLATFORM layer (shipped in code, updated via npm): platform procedures, default identity templates, MCP tools, tool gating, schema migrations, daemon behavior. You get improvements automatically on update. CUSTOMER layer (yours, stored locally): agent identities (exe.md files), behaviors, company procedures, config.json, wiki content, CRM data, memory. These are NEVER overwritten by updates. Identity templates are stamped once at /exe-new-employee \u2014 after that the file is yours. If the platform ships a better template, you can compare yours against the default with getTemplate() and merge what you want. Company procedures (company_procedure tool) layer ON TOP of platform procedures \u2014 both are injected, platform first. FIVE LAYERS \u2014 know when to use each: (1) Platform procedures: how exe-os works, shipped to ALL customers, updated via npm. Never put org-specific rules here. (2) Company procedures: your org's workflow rules, stored in DB, injected after platform. Use for internal gates, review checklists, org-specific policies. (3) Identity (exe.md): an agent's permanent role definition \u2014 who they are, what they own, non-negotiable rules. Use for rules that must NEVER be forgotten across sessions. (4) Behaviors: corrections and learned patterns (Layer 2 expertise). Use for 'from now on do X' or 'never do Y again' \u2014 scoped per agent per project, deactivatable. (5) Memory: facts, decisions, context (Layer 3 experience). Use for what happened, what was decided, project state. Searchable, consolidatable. Rule of thumb: platform procedures for product behavior, company procedures for org workflow, identity for permanent role rules, behaviors for per-agent per-project corrections, memory for facts. Behaviors are always yours. Config is always yours. The platform will never modify your local data."
3733
- },
3734
- // --- Updates ---
3735
- {
3736
- title: "How to update exe-os \u2014 CLI first, then stack",
3737
- domain: "operations",
3738
- priority: "p0",
3739
- content: "When bug fixes or features are available (surfaced at boot via list_my_bug_reports/list_my_feature_requests), update in two steps: (1) CLI: `npm install -g @askexenow/exe-os@latest` \u2014 this updates the local tools, MCP server, and bundled stack manifest. Must happen first so the stack-update tool itself has the latest fixes. (2) Stack (VPS only): `exe-os stack-update --target <version> --yes` \u2014 pulls new Docker images and restarts services. The target version comes from the stack manifest bundled with the CLI. Always update CLI before stack. Use `exe-os update --check` to see if a CLI update is available. Use `exe-os stack-update --check` to see stack status. For non-interactive/SSH: use `--yes` or `-y` flag. Never run `npm install -g` inside a tmux agent session \u2014 have the founder or COO do it from the host shell. If stack-update fails with EACCES on /opt/exe-stack, run: `sudo mkdir -p /opt/exe-stack && sudo chown $(whoami) /opt/exe-stack` then retry."
3740
- },
3741
- {
3742
- title: "CLI version vs stack version \u2014 two tracks, both normal",
3743
- domain: "operations",
3744
- priority: "p0",
3745
- content: "exe-os has TWO version numbers that move independently. This is normal \u2014 do not treat a mismatch as an error. (1) CLI version (e.g. 0.9.89, 0.9.90) \u2014 the npm package installed locally. Updates frequently: bug fixes, new MCP tools, platform procedures, search improvements, client-side changes. Update with `npm install -g @askexenow/exe-os@latest`. (2) Stack version (e.g. 0.9.7, 0.9.8) \u2014 the Docker images on the VPS. Updates less frequently: only when server-side daemon, gateway, wiki, or CRM images need rebuilding. Update with `exe-os stack-update --target <version> --yes`. The CLI version will almost always be higher than the stack version. A CLI at 0.9.90 with a stack at 0.9.8 is perfectly normal \u2014 it means the CLI got 12 patches since the last Docker image rebuild. Only update the stack when: (a) the boot brief surfaces a fix that mentions 'stack update required', (b) a new stack manifest version is bundled in the CLI (`exe-os stack-update --check` shows pending changes), or (c) AskExe support explicitly tells you to. Do NOT attempt to make the numbers match \u2014 they are separate tracks."
3746
- },
3747
- {
3748
- title: "Update lifecycle \u2014 what each command does and what picks up new code",
3749
- domain: "operations",
3750
- priority: "p0",
3751
- content: "Three update paths exist \u2014 know which does what. (1) `npm install -g @askexenow/exe-os@latest` \u2014 customer update. Replaces all dist/ files. postinstall copies slash commands AND restarts the daemon automatically. MCP server picks up new code on next `/mcp` reconnect or new Claude Code session. Hooks pick up new code on next session (they spawn fresh processes). (2) `exe-os update` \u2014 interactive update command. Runs `npm install -g` then `install --global` which restarts daemon, regenerates session wrappers, normalizes roster, registers Codex hooks. This is the recommended customer path. (3) `npm run deploy` \u2014 dev-only (COO/CTO on main branch). Builds, installs globally, runs `install --global`, restarts daemon. Never run from a worktree. NEVER confuse `exe-os setup` (first-time setup wizard for new installs) with `exe-os update` (update existing install). Setup creates encryption keys, configures cloud, runs first sync. Update just replaces code and restarts services. They are completely different commands."
3752
- },
3753
- {
3754
- title: "First install \u2014 setup wizard and license activation",
3755
- domain: "operations",
3756
- priority: "p1",
3757
- content: "Fresh install: `npm install -g @askexenow/exe-os` then run `exe` to start the setup wizard. The wizard prompts for: encryption passphrase (creates master key), license key (exe_sk_* from AskExe team), COO name, and optional team members. No license key = free tier (1 employee, 5K memories). After setup: hooks install automatically, MCP server registers in ~/.claude.json, daemon starts. Verify health: run `exe-os healthcheck` or use mcp_ping() tool."
3758
- },
3759
- // --- Operations ---
3760
- {
3761
- title: "Managers must supervise deployed workers",
3762
- domain: "workflow",
3763
- priority: "p0",
3764
- content: `Every manager (COO/CTO/CMO) who dispatches work to a worker MUST actively monitor them. Check tmux capture-pane every 10 minutes. Verify they're working, not stuck. If idle at prompt with in_progress task \u2192 send intercom. If stuck \u2192 unblock or escalate. "Standing by" without checking is negligence.`
3765
- },
3766
- {
3767
- title: "COO boot health check \u2014 memory, cloud sync, daemon on every launch",
3768
- domain: "workflow",
3769
- priority: "p0",
3770
- content: "On every /exe boot, COO MUST check system health BEFORE other work: (1) daemon \u2014 is exed PID alive, (2) cloud sync \u2014 grep workers.log for recent cloud-sync errors, (3) memory count \u2014 total in DB, (4) sync delta \u2014 local vs cloud storage_bytes. Report as 4-line status table. If ANY check fails, surface to founder immediately. Do not proceed to tasks until health confirmed."
3771
- },
3772
- {
3773
- title: "exe-build-adv mandatory for 3+ files",
3774
- domain: "workflow",
3775
- priority: "p0",
3776
- content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
3777
- },
3778
- {
3779
- title: "Code context first for repository orientation",
3780
- domain: "workflow",
3781
- priority: "p1",
3782
- content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
3783
- },
3784
- {
3785
- title: "Commit discipline \u2014 never leave verified work floating",
3786
- domain: "workflow",
3787
- priority: "p1",
3788
- content: "After any code-change batch passes typecheck/tests/build, run git status, summarize changed files, and commit with a clear message before ending the session. If work must remain uncommitted for review/dogfood, explicitly say so, list the files, and state the blocker. Never imply work is complete while verified changes are still floating locally."
3789
- },
3790
- {
3791
- title: "Desktop and TUI are the same product",
3792
- domain: "architecture",
3793
- priority: "p0",
3794
- content: "Desktop and TUI are the SAME product in different renderers. Same data contracts, same interactions, same acceptance criteria. Desktop tab specs in ARCHITECTURE.md ARE the TUI specs. When building TUI, cross-reference Desktop spec. Different tab names, identical behavior. Never treat them as separate products."
3795
- },
3796
- // --- Orchestration golden path ---
3797
- {
3798
- title: "Task lifecycle \u2014 the golden path every agent follows",
3799
- domain: "workflow",
3800
- priority: "p0",
3801
- content: "create_task is dispatch + delivery. Task lifecycle: open \u2192 in_progress (you start) \u2192 done (update_task when finished) \u2192 needs_review (reviewer nudged) \u2192 closed (COO only via close_task). DB is the reliable delivery \u2014 intercom is just a speedup nudge. If you finish a task, self-chain: check for next task immediately (step 7). Never wait for a nudge. Never say 'standing by.'"
3802
- },
3803
- {
3804
- title: "Review chain \u2014 managers must actively pull completed work, never wait for nudges",
3805
- domain: "workflow",
3806
- priority: "p0",
3807
- content: "When you dispatch work, you OWN the review. Check list_tasks(status='needs_review') on EVERY prompt \u2014 don't wait for intercom nudges (they're unreliable). When a task shows needs_review: (1) read the deliverable (git diff in worktree, exe/output/ files, or task result summary), (2) verify it works (tsc, build, run), (3) close_task if good or create a fix task if not. Reviews sitting >30 minutes is a pipeline stall. The whole chain: worker calls update_task(done) \u2192 system flags needs_review \u2192 manager pulls and verifies \u2192 close_task \u2192 COO reviews manager's work \u2192 merge to main. Every level actively pulls \u2014 nobody waits."
3808
- },
3809
- {
3810
- title: "Intercom is a speedup, not delivery \u2014 DB is the source of truth",
3811
- domain: "architecture",
3812
- priority: "p0",
3813
- content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
3814
- },
3815
- // --- Encryption key + cloud sync ---
3816
- {
3817
- title: "Encryption key lives in Keychain, not on disk \u2014 never expose the recovery phrase",
3818
- domain: "security",
3819
- priority: "p0",
3820
- content: "The master encryption key is stored in macOS Keychain (Secure Enclave) or Linux secret-tool \u2014 NOT as a file. There is no ~/.exe-os/master.key on modern installs. If an older install had one, it was auto-migrated to Keychain and the file deleted. Device linking uses a 24-word BIP39 recovery phrase: Device 1 runs `exe-os cloud link --show-full` in their local Terminal to reveal it, Device 2 runs `exe-os cloud` and pastes the phrase to import the key into its own Keychain, then cloud sync pulls encrypted memories. NEVER display, log, or return the recovery phrase in agent output. MCP tools are hardened \u2014 they cannot reveal it. If the user needs the phrase, tell them: 'Run exe-os cloud link --show-full in your Terminal.' If searching for master.key returns nothing, that is CORRECT \u2014 the key is in Keychain."
3821
- },
3822
- {
3823
- title: "Cloud endpoint is cloud.askexe.com \u2014 not askexe.com/cloud",
3824
- domain: "architecture",
3825
- priority: "p1",
3826
- content: "All cloud API calls (auth, sync, licensing, device registry, WebSocket) go to https://cloud.askexe.com, NOT https://askexe.com/cloud. This is a Cloudflare Workers Custom Domain that bypasses the zone-level managed challenge on askexe.com. Datacenter IPs (Hetzner, AWS, etc.) get HTTP 403 on askexe.com due to Bot Fight Mode, but cloud.askexe.com routes directly to the Worker before WAF rules evaluate. If a customer reports 403/challenge errors on cloud sync: verify they are on the latest exe-os version (cloud.askexe.com endpoint). Fix: `npm install -g @askexenow/exe-os@latest`. The EXE_CLOUD_ENDPOINT env var can override the endpoint if needed."
3827
- },
3828
- // --- MCP is the ONLY data interface ---
3829
- {
3830
- title: "MCP disconnect \u2014 ask the user, never work around it",
3831
- domain: "workflow",
3832
- priority: "p0",
3833
- content: "If MCP tools are unavailable, disconnected, or returning connection errors: STOP. Tell the user clearly: 'MCP server is disconnected. Please run /mcp to reconnect.' Do NOT attempt workarounds \u2014 no raw Node imports, no direct DB access, no CLI hacks, no daemon socket calls. MCP is the ONLY data interface. Working around it wastes time, hits bundling issues, and bypasses the contract boundary. Ask once, wait, proceed when reconnected."
3834
- },
3835
- // --- MCP Tool Catalog (Layer 0 — every agent knows what tools exist) ---
3836
- {
3837
- title: "MCP tool dispatch \u2014 all tools use action parameter",
3838
- domain: "tool-use",
3839
- priority: "p0",
3840
- content: 'exe-os MCP tools use consolidated action-based dispatch by default (19 tools). Call domain tools with an action parameter: memory(action="recall"), task(action="create"), config(action="list_employees"), etc. Legacy mode (108 separate tools like recall_my_memory, create_task) is still available via EXE_MCP_TOOL_SURFACE=legacy but will be removed in a future version. If you see specific tool names, call them directly \u2014 both surfaces are identical. Consolidated is the default and recommended surface.'
3841
- },
3842
- {
3843
- title: "MCP tools \u2014 memory, decision, and search",
3844
- domain: "tool-use",
3845
- priority: "p1",
3846
- content: `memory(action="recall") / recall_my_memory: search memories (semantic + FTS). Params: as_of (bi-temporal \u2014 what did I know at time X?), kind (decision|procedure|observation|raw|conversation|behavior), retrieval_mode (all|decisions_only|procedures_only|operational|recent_high_value). memory(action="ask_team") / ask_team_memory: search a colleague's memories. memory(action="store") / store_memory: persist a memory. Params: kind, procedure_for (domain tag for procedures). memory(action="commit") / commit_memory: high-importance, survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal window. Requires session_id + target_timestamp. memory(action="get_by_id"): fetch one memory by UUID with full untruncated text. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. memory(action="supersede"): replace an old memory with a new version (old_id + new text). decision(action="store") / store_decision: record an architectural decision (domain, decision, rationale). decision(action="get") / get_decision: retrieve a past decision by domain or query.`
3847
- },
3848
- {
3849
- title: "MCP tools \u2014 task orchestration",
3850
- domain: "tool-use",
3851
- priority: "p1",
3852
- content: 'task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. Params: blocked_by (task ID for dependency), parent_task_id (subtask hierarchy), reviewer, complexity (routine|standard|complex|critical), budget_tokens (max token cap), budget_fallback_model, spawn_runtime (override runtime: claude|codex|opencode), spawn_model (override model). task(action="list") / list_tasks: query tasks by status, assignee, project. task(action="get") / get_task: fetch full task details by task_id. task(action="update") / update_task: change status (in_progress, done, blocked, cancelled) + result summary. task(action="close") / close_task: finalize a reviewed task (COO only). task(action="checkpoint") / checkpoint_task: save progress state for crash recovery. task(action="resume") / resume_employee: re-spawn an employee session for an existing task.'
3853
- },
3854
- {
3855
- title: "MCP tools \u2014 knowledge graph (GraphRAG)",
3856
- domain: "tool-use",
3857
- priority: "p1",
3858
- content: 'graph(action="query_relationships") / query_relationships: find connections between entities. graph(action="entity_neighbors") / get_entity_neighbors: explore direct connections. graph(action="hot_entities") / get_hot_entities: most-referenced entities. graph(action="stats") / get_graph_stats: entity/relationship counts. graph(action="export") / export_graph: export for visualization (output_path, format). graph(action="merge_entities") / merge_entities: deduplicate entities (source_id, target_id). graph(action="similar_trajectories") / find_similar_trajectories: match patterns to past solutions.'
3859
- },
3860
- {
3861
- title: "MCP tools \u2014 identity, behavior, and support",
3862
- domain: "tool-use",
3863
- priority: "p1",
3864
- content: `identity(action="get") / get_identity: read an agent's exe.md (Layer 1 identity). identity(action="update") / update_identity: write an agent's exe.md. Identity > behavior. behavior(action="store") / store_behavior: record a correction or pattern (Layer 2 expertise). behavior(action="list") / list_behaviors: view active behaviors. behavior(action="deactivate") / deactivate_behavior: soft-delete a stale behavior. support(action="create_bug"): file a bug report. Required: title, description, severity (p0-p3). Optional: steps_to_reproduce, expected_behavior, actual_behavior. Auto-delivers to AskExe support. support(action="create_feature"): file a feature request. Required: title, description. Optional: use_case (why you need it), proposed_solution (how it could work). Include business impact if possible \u2014 it helps prioritization. Both create_bug and create_feature accept a 'product' param to file for any AskExe product: exe-os (default), exe-create, exe-wiki, exe-gateway, exe-crm, exe-build. support(action="list_my_bugs"): check status of YOUR filed bug reports \u2014 see which are open, triaged, or fixed with version number. support(action="list_my_features"): check status of YOUR filed feature requests \u2014 see which are open, planned, shipped, or closed with response notes from AskExe. support(action="health"): verify support server is reachable. When a bug shows status='closed' with a fixed_version, tell the founder: 'Bug X fixed in vY \u2014 run exe-os update to get it.' When a feature shows status='shipped' with a shipped_version, tell the founder: 'Feature X shipped in vY \u2014 run exe-os update.' support(action="triage_bug"): AskExe-internal only. CRITICAL: triage uses triage_notes NOT notes.`
3865
- },
3866
- {
3867
- title: "MCP tools \u2014 communication and messaging",
3868
- domain: "tool-use",
3869
- priority: "p1",
3870
- content: 'message(action="send") / send_message: send context to another agent (NOT for actionable work \u2014 use task). message(action="acknowledge") / acknowledge_messages: mark messages as read. reminder(action="create") / create_reminder: set a reminder (text, due_date). Shown in boot brief. reminder(action="list") / list_reminders: view pending reminders. reminder(action="complete") / complete_reminder: mark done (reminder_id). session(action="events") / get_session_events: view session event log (session_id). session(action="last_response") / get_last_assistant_response: get most recent response.'
3871
- },
3872
- {
3873
- title: "MCP tools \u2014 wiki, documents, CRM, and data",
3874
- domain: "tool-use",
3875
- priority: "p1",
3876
- content: 'wiki(action="list") / list_wiki_pages: list wiki pages (workspace). wiki(action="get") / get_wiki_page: read a wiki page (workspace, title or document_id). document(action="ingest") / ingest_document: import a file as memory chunks (workspace_id, filename, chunks). document(action="list") / list_documents: browse documents (workspace_id). document(action="purge") / purge_document: remove a document (document_id). document(action="set_importance") / set_document_importance: adjust chunk scores. document(action="rerank") / rerank_documents: re-score relevance (query, candidates). crm(action="list_people|get_person|list_tables|describe_table"): CRM records from exe-db. raw_data(action="list_sources|query|get"): read raw landing-pad events. gateway(action="send_whatsapp") / send_whatsapp: send WhatsApp message (recipients, message). gateway(action="query_conversations") / query_conversations: search conversations across channels. gateway(action="query_company_brain") / query_company_brain: unified RAG across company knowledge.'
3877
- },
3878
- {
3879
- title: "MCP tools \u2014 admin, config, and operations",
3880
- domain: "tool-use",
3881
- priority: "p1",
3882
- content: 'config(action="list_employees"): view roster. config(action="set_agent_config"): view or change per-agent runtime + model. Call with no args to show all agents. config(action="agent_spend"): token usage per agent. config(action="daemon_health"): check exed status. config(action="license_status"): check license. config(action="cloud_sync"): force sync. Supports cloud_action param: status|sync|reupload. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="worker_gate"): check spawn slot availability \u2014 alive/stale/reserved counts vs max. Use before dispatching. config(action="auto_wake_status"): orphaned tasks, blocked tasks, auto-wake retry status. config(action="orchestration_phase"): view/change org phase (phase_1_coo|phase_2_executives|phase_3_parallel_org). config(action="company_procedure", subaction="store|list|deactivate"): manage company procedures. config(action="global_procedure"): list all procedures (platform + company). config(action="create_trigger|list_triggers"): scheduled agent jobs. config(action="export_orchestration|import_orchestration"): portable org state. diagnostics(action="healthcheck|doctor|status_brief|check_update|cloud_status"): system diagnostics. diagnostics(action="pending_work_summary"): pending reviews + messages + notifications in one call. diagnostics(action="rename_employee"): rename an agent across all systems (roster, identity, DB, symlinks). diagnostics(action="tool_search"): semantic tool discovery \u2014 find relevant MCP tools by natural language query. diagnostics(action="drift"): identity drift detection \u2014 score how far an agent has drifted from its role. mcp_ping(): daemon health + license status + tool usage stats.'
3883
- }
3884
- ];
3885
- PLATFORM_PROCEDURE_TITLES = new Set(
3886
- PLATFORM_PROCEDURES.map((p) => p.title)
3887
- );
3888
- }
3889
- });
3890
-
3891
- // src/lib/global-procedures.ts
3892
- var global_procedures_exports = {};
3893
- __export(global_procedures_exports, {
3894
- deactivateGlobalProcedure: () => deactivateGlobalProcedure,
3895
- getGlobalProceduresBlock: () => getGlobalProceduresBlock,
3896
- loadGlobalProcedures: () => loadGlobalProcedures,
3897
- storeGlobalProcedure: () => storeGlobalProcedure
3898
- });
3899
- import { randomUUID as randomUUID2 } from "crypto";
3900
- async function loadGlobalProcedures() {
3901
- const client = getClient();
3902
- const result = await client.execute({
3903
- sql: "SELECT * FROM company_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
3904
- args: []
3905
- });
3906
- const allRows = result.rows;
3907
- const customerOnly = allRows.filter((p) => !PLATFORM_PROCEDURE_TITLES.has(p.title));
3908
- if (customerOnly.length > 0) {
3909
- _customerCache = customerOnly.map((p) => `### ${p.title}
3910
- ${p.content}`).join("\n\n");
3911
- } else {
3912
- _customerCache = "";
3913
- }
3914
- _cacheLoaded = true;
3915
- return customerOnly;
3916
- }
3917
- function getGlobalProceduresBlock() {
3918
- const sections = [];
3919
- if (_platformCache) sections.push(_platformCache);
3920
- if (_cacheLoaded && _customerCache) sections.push(_customerCache);
3921
- if (sections.length === 0) return "";
3922
- return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
3923
-
3924
- ${sections.join("\n\n")}
3925
- `;
3926
- }
3927
- async function storeGlobalProcedure(input) {
3928
- const id = randomUUID2();
3929
- const now = (/* @__PURE__ */ new Date()).toISOString();
3930
- const client = getClient();
3931
- await client.execute({
3932
- sql: `INSERT INTO company_procedures (id, title, content, priority, domain, active, created_at, updated_at)
3933
- VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
3934
- args: [id, input.title, input.content, input.priority ?? "p0", input.domain ?? null, now, now]
3935
- });
3936
- await loadGlobalProcedures();
3937
- return id;
3938
- }
3939
- async function deactivateGlobalProcedure(id) {
3940
- const now = (/* @__PURE__ */ new Date()).toISOString();
3941
- const client = getClient();
3942
- const result = await client.execute({
3943
- sql: "UPDATE company_procedures SET active = 0, updated_at = ? WHERE id = ?",
3944
- args: [now, id]
3945
- });
3946
- await loadGlobalProcedures();
3947
- return result.rowsAffected > 0;
3948
- }
3949
- var _customerCache, _cacheLoaded, _platformCache;
3950
- var init_global_procedures = __esm({
3951
- "src/lib/global-procedures.ts"() {
3952
- "use strict";
3953
- init_database();
3954
- init_platform_procedures();
3955
- _customerCache = "";
3956
- _cacheLoaded = false;
3957
- _platformCache = PLATFORM_PROCEDURES.map((p) => `### ${p.title}
3958
- ${p.content}`).join("\n\n");
3959
- }
3960
- });
3961
-
3962
- // src/lib/schedules.ts
3963
- init_database();
3964
- import crypto2 from "crypto";
3965
- import { execSync as execSync4 } from "child_process";
3966
-
3967
- // src/lib/store.ts
3968
- init_memory();
3969
- init_database();
3970
-
3971
- // src/lib/keychain.ts
3972
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
3973
- import { existsSync as existsSync7, statSync as statSync3 } from "fs";
3974
- import { execSync as execSync3 } from "child_process";
3975
- import path6 from "path";
3976
- import os5 from "os";
3977
- var SERVICE = "exe-os";
3978
- var LEGACY_SERVICE = "exe-mem";
3979
- var ACCOUNT = "master-key";
3980
- function getKeyDir() {
3981
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
3982
- }
3983
- function getKeyPath() {
3984
- return path6.join(getKeyDir(), "master.key");
3985
- }
3986
- function nativeKeychainAllowed() {
3987
- return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
3988
- }
3989
- var linuxSecretAvailability = null;
3990
- function linuxSecretAvailable() {
3991
- if (!nativeKeychainAllowed()) return false;
3992
- if (process.platform !== "linux") return false;
3993
- if (linuxSecretAvailability !== null) return linuxSecretAvailability;
3994
- try {
3995
- execSync3("command -v secret-tool >/dev/null 2>&1", { timeout: 1e3 });
3996
- } catch {
3997
- linuxSecretAvailability = false;
3998
- return false;
3999
- }
4000
- try {
4001
- execSync3("secret-tool search --all exe-os probe >/dev/null 2>&1", { timeout: 1e3 });
4002
- linuxSecretAvailability = true;
4003
- } catch {
4004
- linuxSecretAvailability = false;
4005
- }
4006
- return linuxSecretAvailability;
4007
- }
4008
- function isRootOnlyTrustedServerKeyFile(keyPath) {
4009
- if (process.platform !== "linux") return false;
4010
- try {
4011
- const st = statSync3(keyPath);
4012
- if (!st.isFile() || (st.mode & 63) !== 0) return false;
4013
- const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
4014
- if (uid === 0) return true;
4015
- const exeOsDir = process.env.EXE_OS_DIR;
4016
- if (exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep)) return true;
4017
- if (!linuxSecretAvailable()) return true;
4018
- return false;
4019
- } catch {
4020
- return false;
4021
- }
4022
- }
4023
- function macKeychainGet(service = SERVICE) {
4024
- if (!nativeKeychainAllowed()) return null;
4025
- if (process.platform !== "darwin") return null;
4026
- try {
4027
- return execSync3(
4028
- `security find-generic-password -s "${service}" -a "${ACCOUNT}" -w 2>/dev/null`,
4029
- { encoding: "utf-8", timeout: 5e3 }
4030
- ).trim();
4031
- } catch {
4032
- return null;
4033
- }
4034
- }
4035
- function macKeychainSet(value, service = SERVICE) {
4036
- if (!nativeKeychainAllowed()) return false;
4037
- if (process.platform !== "darwin") return false;
4038
- try {
4039
- try {
4040
- execSync3(
4041
- `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
4042
- { timeout: 5e3 }
4043
- );
4044
- } catch {
4045
- }
4046
- execSync3(
4047
- `security add-generic-password -s "${service}" -a "${ACCOUNT}" -w "${value}"`,
4048
- { timeout: 5e3 }
4049
- );
4050
- return true;
4051
- } catch {
4052
- return false;
4053
- }
4054
- }
4055
- function macKeychainDelete(service = SERVICE) {
4056
- if (!nativeKeychainAllowed()) return false;
4057
- if (process.platform !== "darwin") return false;
4058
- try {
4059
- execSync3(
4060
- `security delete-generic-password -s "${service}" -a "${ACCOUNT}" 2>/dev/null`,
4061
- { timeout: 5e3 }
4062
- );
4063
- return true;
4064
- } catch {
4065
- return false;
4066
- }
4067
- }
4068
- function linuxSecretGet(service = SERVICE) {
4069
- if (!linuxSecretAvailable()) return null;
4070
- try {
4071
- return execSync3(
4072
- `secret-tool lookup service "${service}" account "${ACCOUNT}" 2>/dev/null`,
4073
- { encoding: "utf-8", timeout: 5e3 }
4074
- ).trim();
4075
- } catch {
4076
- return null;
4077
- }
4078
- }
4079
- function linuxSecretSet(value, service = SERVICE) {
4080
- if (!linuxSecretAvailable()) return false;
4081
- try {
4082
- execSync3(
4083
- `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${service}" account "${ACCOUNT}" 2>/dev/null`,
4084
- { timeout: 5e3 }
4085
- );
4086
- return true;
4087
- } catch {
4088
- return false;
4089
- }
4090
- }
4091
- function linuxSecretDelete(service = SERVICE) {
4092
- if (!nativeKeychainAllowed()) return false;
4093
- if (process.platform !== "linux") return false;
4094
- try {
4095
- execSync3(
4096
- `secret-tool clear service "${service}" account "${ACCOUNT}" 2>/dev/null`,
4097
- { timeout: 5e3 }
4098
- );
4099
- return true;
4100
- } catch {
4101
- return false;
4102
- }
4103
- }
4104
- async function tryKeytar() {
4105
- if (!nativeKeychainAllowed()) return null;
4106
- try {
4107
- return await import("keytar");
4108
- } catch {
4109
- return null;
4110
- }
4111
- }
4112
- var ENCRYPTED_PREFIX = "enc:";
4113
- function deriveMachineKey() {
4114
- try {
4115
- const crypto3 = __require("crypto");
4116
- const material = [
4117
- os5.hostname(),
4118
- os5.userInfo().username,
4119
- os5.arch(),
4120
- os5.platform(),
4121
- // Machine ID on Linux (stable across reboots)
4122
- process.platform === "linux" ? readMachineId() : ""
4123
- ].join("|");
4124
- return crypto3.createHash("sha256").update(material).digest();
4125
- } catch {
4126
- return null;
4127
- }
4128
- }
4129
- function readMachineId() {
4130
- try {
4131
- const { readFileSync: readFileSync5 } = __require("fs");
4132
- return readFileSync5("/etc/machine-id", "utf-8").trim();
4133
- } catch {
4134
- return "";
4135
- }
4136
- }
4137
- function encryptWithMachineKey(plaintext, machineKey) {
4138
- const crypto3 = __require("crypto");
4139
- const iv = crypto3.randomBytes(12);
4140
- const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
4141
- let encrypted = cipher.update(plaintext, "utf-8", "base64");
4142
- encrypted += cipher.final("base64");
4143
- const authTag = cipher.getAuthTag().toString("base64");
4144
- return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
4145
- }
4146
- function decryptWithMachineKey(encrypted, machineKey) {
4147
- if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
4148
- try {
4149
- const crypto3 = __require("crypto");
4150
- const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
4151
- if (parts.length !== 3) return null;
4152
- const [ivB64, tagB64, cipherB64] = parts;
4153
- const iv = Buffer.from(ivB64, "base64");
4154
- const authTag = Buffer.from(tagB64, "base64");
4155
- const decipher = crypto3.createDecipheriv("aes-256-gcm", machineKey, iv);
4156
- decipher.setAuthTag(authTag);
4157
- let decrypted = decipher.update(cipherB64, "base64", "utf-8");
4158
- decrypted += decipher.final("utf-8");
4159
- return decrypted;
4160
- } catch {
4161
- return null;
4162
- }
4163
- }
4164
- async function writeMachineBoundFileFallback(b64) {
4165
- const dir = getKeyDir();
4166
- await mkdir3(dir, { recursive: true });
4167
- const keyPath = getKeyPath();
4168
- const machineKey = deriveMachineKey();
4169
- const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
4170
- const result = machineKey ? "encrypted" : "plaintext";
4171
- const tmpPath = keyPath + ".tmp";
4172
- try {
4173
- if (existsSync7(keyPath)) {
4174
- await copyFile(keyPath, keyPath + ".bak").catch(() => {
4175
- });
4176
- }
4177
- await writeFile3(tmpPath, content, "utf-8");
4178
- await chmod2(tmpPath, 384);
4179
- await rename(tmpPath, keyPath);
4180
- } catch (err) {
4181
- try {
4182
- await unlink(tmpPath);
4183
- } catch {
4184
- }
4185
- throw err;
4186
- }
4187
- return result;
4188
- }
4189
- async function getMasterKey() {
4190
- let nativeValue = macKeychainGet() ?? linuxSecretGet();
4191
- if (!nativeValue) {
4192
- const legacyValue = macKeychainGet(LEGACY_SERVICE) ?? linuxSecretGet(LEGACY_SERVICE);
4193
- if (legacyValue) {
4194
- const migrated = macKeychainSet(legacyValue) || linuxSecretSet(legacyValue);
4195
- if (migrated) {
4196
- macKeychainDelete(LEGACY_SERVICE);
4197
- linuxSecretDelete(LEGACY_SERVICE);
4198
- process.stderr.write("[keychain] Migrated keychain service from exe-mem to exe-os.\n");
4199
- }
4200
- nativeValue = legacyValue;
4201
- }
4202
- }
4203
- if (nativeValue) {
4204
- return Buffer.from(nativeValue, "base64");
4205
- }
4206
- const keytar = await tryKeytar();
4207
- if (keytar) {
4208
- try {
4209
- const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
4210
- const legacyKeytarValue = keytarValue ?? await keytar.getPassword(LEGACY_SERVICE, ACCOUNT);
4211
- if (legacyKeytarValue) {
4212
- const migrated = macKeychainSet(legacyKeytarValue) || linuxSecretSet(legacyKeytarValue);
4213
- if (migrated) {
4214
- process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
4215
- try {
4216
- await keytar.deletePassword(LEGACY_SERVICE, ACCOUNT);
4217
- } catch {
4218
- }
4219
- }
4220
- return Buffer.from(legacyKeytarValue, "base64");
4221
- }
4222
- } catch {
4223
- }
4224
- }
4225
- const keyPath = getKeyPath();
4226
- if (!existsSync7(keyPath)) {
4227
- process.stderr.write(
4228
- `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
4229
- `
4230
- );
4231
- return null;
4232
- }
4233
- try {
4234
- const content = (await readFile3(keyPath, "utf-8")).trim();
4235
- let b64Value;
4236
- if (content.startsWith(ENCRYPTED_PREFIX)) {
4237
- const machineKey = deriveMachineKey();
4238
- if (!machineKey) {
4239
- process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
4240
- return null;
4241
- }
4242
- const decrypted = decryptWithMachineKey(content, machineKey);
4243
- if (!decrypted) {
4244
- process.stderr.write(
4245
- "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase during setup: exe-os setup\n"
4246
- );
4247
- return null;
4248
- }
4249
- b64Value = decrypted;
4250
- } else {
4251
- b64Value = content;
4252
- }
4253
- const key = Buffer.from(b64Value, "base64");
4254
- if (isRootOnlyTrustedServerKeyFile(keyPath)) {
4255
- return key;
4256
- }
4257
- const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
4258
- if (migrated) {
4259
- process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
4260
- try {
4261
- await unlink(keyPath);
4262
- process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
4263
- } catch {
4264
- }
4265
- } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
4266
- const fallback = await writeMachineBoundFileFallback(b64Value);
4267
- if (fallback === "encrypted") {
4268
- process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
4269
- } else {
4270
- process.stderr.write(
4271
- "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
4272
- );
4273
- }
4274
- }
4275
- return key;
4276
- } catch (err) {
4277
- process.stderr.write(
4278
- `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
4279
- `
4280
- );
4281
- return null;
4282
- }
4283
- }
4284
-
4285
- // src/lib/store.ts
4286
- init_config();
4287
-
4288
- // src/lib/state-bus.ts
4289
- var StateBus = class {
4290
- handlers = /* @__PURE__ */ new Map();
4291
- globalHandlers = /* @__PURE__ */ new Set();
4292
- /** Emit an event to all subscribers */
4293
- emit(event) {
4294
- const typeHandlers = this.handlers.get(event.type);
4295
- if (typeHandlers) {
4296
- for (const handler of typeHandlers) {
4297
- try {
4298
- handler(event);
4299
- } catch {
4300
- }
4301
- }
4302
- }
4303
- for (const handler of this.globalHandlers) {
4304
- try {
4305
- handler(event);
4306
- } catch {
4307
- }
4308
- }
4309
- }
4310
- /** Subscribe to a specific event type */
4311
- on(type, handler) {
4312
- if (!this.handlers.has(type)) {
4313
- this.handlers.set(type, /* @__PURE__ */ new Set());
4314
- }
4315
- this.handlers.get(type).add(handler);
4316
- }
4317
- /** Subscribe to ALL events */
4318
- onAny(handler) {
4319
- this.globalHandlers.add(handler);
4320
- }
4321
- /** Unsubscribe from a specific event type */
4322
- off(type, handler) {
4323
- this.handlers.get(type)?.delete(handler);
4324
- }
4325
- /** Unsubscribe from ALL events */
4326
- offAny(handler) {
4327
- this.globalHandlers.delete(handler);
4328
- }
4329
- /** Remove all listeners */
4330
- clear() {
4331
- this.handlers.clear();
4332
- this.globalHandlers.clear();
4333
- }
4334
- };
4335
- var orgBus = new StateBus();
4336
-
4337
- // src/lib/memory-write-governor.ts
4338
- import { createHash } from "crypto";
4339
-
4340
- // src/lib/store.ts
4341
- var _debugStore = process.env.EXE_DEBUG === "1";
4342
- function logStoreWarn(context, err) {
4343
- process.stderr.write(
4344
- `[store] WARN ${context}: ${err instanceof Error ? err.message : String(err)}
4345
- `
4346
- );
4347
- }
4348
- var INIT_MAX_RETRIES = 3;
4349
- var INIT_RETRY_DELAY_MS = 1e3;
4350
- function isBusyError2(err) {
4351
- if (err instanceof Error) {
4352
- const msg = err.message.toLowerCase();
4353
- return msg.includes("sqlite_busy") || msg.includes("database is locked");
4354
- }
4355
- return false;
4356
- }
4357
- async function retryOnBusy2(fn, label) {
4358
- for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
4359
- try {
4360
- return await fn();
4361
- } catch (err) {
4362
- if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
4363
- process.stderr.write(
4364
- `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
4365
- `
4366
- );
4367
- await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
4368
- }
4369
- }
4370
- throw new Error("unreachable");
4371
- }
4372
- var _pendingRecords = [];
4373
- var _batchSize = 20;
4374
- var _flushIntervalMs = 1e4;
4375
- var _flushTimer = null;
4376
- var _flushing = false;
4377
- var _nextVersion = 1;
4378
- async function initStore(options) {
4379
- if (_flushTimer !== null) {
4380
- clearInterval(_flushTimer);
4381
- _flushTimer = null;
4382
- }
4383
- _pendingRecords = [];
4384
- _flushing = false;
4385
- _batchSize = options?.batchSize ?? 20;
4386
- _flushIntervalMs = options?.flushIntervalMs ?? 1e4;
4387
- let dbPath = options?.dbPath;
4388
- if (!dbPath) {
4389
- const config = await loadConfig();
4390
- dbPath = config.dbPath;
4391
- }
4392
- let masterKey = options?.masterKey ?? null;
4393
- if (!masterKey) {
4394
- masterKey = await getMasterKey();
4395
- if (!masterKey) {
4396
- throw new Error(
4397
- "No encryption key found. Run /exe-setup to generate one."
4398
- );
4399
- }
4400
- }
4401
- const hexKey = masterKey.toString("hex");
4402
- await initTurso({
4403
- dbPath,
4404
- encryptionKey: hexKey
4405
- });
4406
- await retryOnBusy2(() => ensureSchema(), "ensureSchema");
4407
- try {
4408
- const { initDaemonClient: initDaemonClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4409
- await initDaemonClient2();
4410
- } catch (e) {
4411
- logStoreWarn("catch", e);
4412
- }
4413
- if (!options?.lightweight) {
4414
- try {
4415
- const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4416
- initShardManager2(hexKey);
4417
- } catch (e) {
4418
- logStoreWarn("catch", e);
4419
- }
4420
- const client = getClient();
4421
- const vResult = await retryOnBusy2(
4422
- () => client.execute("SELECT MAX(version) as max_v FROM memories"),
4423
- "version-query"
4424
- );
4425
- _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
4426
- try {
4427
- const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
4428
- await loadGlobalProcedures2();
4429
- } catch (e) {
4430
- logStoreWarn("catch", e);
4431
- }
4432
- }
4433
- }
4434
-
4435
- // src/lib/schedules.ts
4436
- var CRON_FIELD = /^[\d*/,\-]+$/;
4437
- function isValidCron(cron) {
4438
- const fields = cron.trim().split(/\s+/);
4439
- if (fields.length !== 5) return false;
4440
- return fields.every((f) => CRON_FIELD.test(f));
4441
- }
4442
- var SAFE_ID = /^[a-zA-Z0-9_\-]+$/;
4443
- function isValidScheduleId(id) {
4444
- return SAFE_ID.test(id) && id.length <= 128;
4445
- }
4446
- async function ensureDb() {
4447
- if (!isInitialized()) {
4448
- await initStore();
4449
- }
4450
- }
4451
- function parseHumanCron(input) {
4452
- const s = input.toLowerCase().trim();
4453
- if (/^[\d*\/,-]+\s+[\d*\/,-]+\s+[\d*\/,-]+\s+[\d*\/,-]+\s+[\d*\/,-]+$/.test(s)) {
4454
- return s;
4455
- }
4456
- const dayMap = {
4457
- sunday: "0",
4458
- sun: "0",
4459
- monday: "1",
4460
- mon: "1",
4461
- tuesday: "2",
4462
- tue: "2",
4463
- wednesday: "3",
4464
- wed: "3",
4465
- thursday: "4",
4466
- thu: "4",
4467
- friday: "5",
4468
- fri: "5",
4469
- saturday: "6",
4470
- sat: "6"
4471
- };
4472
- const everyMatch = s.match(/every\s+(\d+)\s*([mh])/);
4473
- if (everyMatch) {
4474
- const n = everyMatch[1];
4475
- const unit = everyMatch[2];
4476
- if (unit === "m") return `*/${n} * * * *`;
4477
- if (unit === "h") return `0 */${n} * * *`;
4478
- }
4479
- let hour = -1;
4480
- let minute = 0;
4481
- if (/midnight/.test(s)) {
4482
- hour = 0;
4483
- } else if (/noon/.test(s)) {
4484
- hour = 12;
4485
- } else {
4486
- const timeMatch = s.match(/(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/);
4487
- if (timeMatch) {
4488
- hour = parseInt(timeMatch[1], 10);
4489
- minute = timeMatch[2] ? parseInt(timeMatch[2], 10) : 0;
4490
- if (timeMatch[3] === "pm" && hour < 12) hour += 12;
4491
- if (timeMatch[3] === "am" && hour === 12) hour = 0;
4492
- }
4493
- }
4494
- if (hour === -1) hour = 0;
4495
- let dow = "*";
4496
- if (/weekday|weekdays/.test(s)) {
4497
- dow = "1-5";
4498
- } else if (/weekend|weekends/.test(s)) {
4499
- dow = "0,6";
4500
- } else {
4501
- for (const [name, val] of Object.entries(dayMap)) {
4502
- if (s.includes(name)) {
4503
- dow = val;
4504
- break;
4505
- }
4506
- }
4507
- }
4508
- return `${minute} ${hour} * * ${dow}`;
4509
- }
4510
- async function createSchedule(input) {
4511
- if (!isValidCron(input.cron)) {
4512
- throw new Error(`Invalid cron expression: ${input.cron}. Must be 5 fields with only digits, *, /, -, comma.`);
4513
- }
4514
- await ensureDb();
4515
- const client = getClient();
4516
- const id = crypto2.randomUUID().slice(0, 8);
4517
- const now = (/* @__PURE__ */ new Date()).toISOString();
4518
- const prompt = input.prompt ?? input.description;
4519
- await client.execute({
4520
- sql: `INSERT INTO schedules (id, cron, description, job_type, prompt, assigned_to, project_name, active, use_crontab, created_at)
4521
- VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?)`,
4522
- args: [
4523
- id,
4524
- input.cron,
4525
- input.description,
4526
- input.jobType ?? "report",
4527
- prompt,
4528
- input.assignedTo ?? null,
4529
- input.projectName ?? null,
4530
- input.useCrontab ? 1 : 0,
4531
- now
4532
- ]
4533
- });
4534
- if (input.useCrontab) {
4535
- addToCrontab(id, input.cron, prompt, input.projectName);
4536
- }
4537
- return {
4538
- id,
4539
- cron: input.cron,
4540
- description: input.description,
4541
- jobType: input.jobType ?? "report",
4542
- prompt,
4543
- assignedTo: input.assignedTo,
4544
- projectName: input.projectName,
4545
- active: true,
4546
- useCrontab: input.useCrontab ?? false,
4547
- createdAt: now
4548
- };
4549
- }
4550
- async function listSchedules(activeOnly = true) {
4551
- await ensureDb();
4552
- const client = getClient();
4553
- const sql = activeOnly ? "SELECT * FROM schedules WHERE active = 1 ORDER BY created_at ASC" : "SELECT * FROM schedules ORDER BY created_at ASC";
4554
- const result = await client.execute({ sql, args: [] });
4555
- return result.rows.map((row) => ({
4556
- id: String(row.id),
4557
- cron: String(row.cron),
4558
- description: String(row.description),
4559
- jobType: String(row.job_type),
4560
- prompt: row.prompt ? String(row.prompt) : void 0,
4561
- assignedTo: row.assigned_to ? String(row.assigned_to) : void 0,
4562
- projectName: row.project_name ? String(row.project_name) : void 0,
4563
- active: Number(row.active) === 1,
4564
- useCrontab: Number(row.use_crontab) === 1,
4565
- createdAt: String(row.created_at)
4566
- }));
4567
- }
4568
- async function deleteSchedule(id) {
4569
- await ensureDb();
4570
- const client = getClient();
4571
- const existing = await client.execute({
4572
- sql: "SELECT use_crontab FROM schedules WHERE id = ?",
4573
- args: [id]
4574
- });
4575
- if (existing.rows.length === 0) return false;
4576
- const usesCrontab = Number(existing.rows[0].use_crontab) === 1;
4577
- await client.execute({
4578
- sql: "DELETE FROM schedules WHERE id = ?",
4579
- args: [id]
4580
- });
4581
- if (usesCrontab) {
4582
- removeFromCrontab(id);
4583
- }
4584
- return true;
4585
- }
4586
- function addToCrontab(id, cron, prompt, projectDir) {
4587
- if (!isValidCron(cron)) {
4588
- throw new Error(`Invalid cron expression: ${cron}`);
4589
- }
4590
- if (!isValidScheduleId(id)) {
4591
- throw new Error(`Invalid schedule ID: ${id}`);
4592
- }
4593
- try {
4594
- const cwd = projectDir ? `cd ${JSON.stringify(projectDir)} && ` : "";
4595
- const escapedPrompt = prompt.replace(/"/g, '\\"');
4596
- const entry = `${cron} ${cwd}claude -p --dangerously-skip-permissions "${escapedPrompt}" # exe-schedule:${id}`;
4597
- execSync4(
4598
- `(crontab -l 2>/dev/null; echo ${JSON.stringify(entry)}) | crontab -`,
4599
- { timeout: 5e3, stdio: "ignore" }
4600
- );
4601
- } catch (err) {
4602
- if (err instanceof Error && err.message.startsWith("Invalid")) throw err;
4603
- }
4604
- }
4605
- function removeFromCrontab(id) {
4606
- if (!isValidScheduleId(id)) return;
4607
- try {
4608
- execSync4(
4609
- `crontab -l 2>/dev/null | grep -v "exe-schedule:${id}" | crontab -`,
4610
- { timeout: 5e3, stdio: "ignore" }
4611
- );
4612
- } catch {
4613
- }
4614
- }
1
+ import {
2
+ createSchedule,
3
+ deleteSchedule,
4
+ listSchedules,
5
+ parseHumanCron
6
+ } from "../chunk-VQ4K2MLM.js";
7
+ import "../chunk-HSRM6PNM.js";
8
+ import "../chunk-NHK5KW6Y.js";
9
+ import "../chunk-WZQ4CPRG.js";
10
+ import "../chunk-A2NZP64U.js";
11
+ import "../chunk-4NYQAS33.js";
12
+ import "../chunk-RUG3N6P4.js";
13
+ import "../chunk-XUHFQHGZ.js";
14
+ import "../chunk-KVPG5UT6.js";
15
+ import "../chunk-X2IMCCM5.js";
16
+ import "../chunk-KFQGP6VL.js";
4615
17
  export {
4616
18
  createSchedule,
4617
19
  deleteSchedule,