@askexenow/exe-os 0.9.295 → 0.9.296

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 (279) hide show
  1. package/deploy/compose/cloudflared/config.yml.example +14 -9
  2. package/deploy/compose/docker-compose.yml +86 -8
  3. package/deploy/compose/sso-edge/default.conf.template +87 -0
  4. package/deploy/compose/sso-edge/entrypoint.sh +23 -0
  5. package/deploy/compose/sso-edge/sso-redirect.conf +63 -0
  6. package/deploy/stack-manifests/v0.9.json +1 -1
  7. package/dist/active-agent-AFX2FODG.js +28 -0
  8. package/dist/active-agent-E2IJA7YX.js +27 -0
  9. package/dist/agentic-ontology-A2YUZK5O.js +25 -0
  10. package/dist/assets/com.askexe.exed.plist +4 -1
  11. package/dist/backfill-metadata-OC7EOD5U.js +600 -0
  12. package/dist/behaviors-H5ZOVHDH.js +46 -0
  13. package/dist/bin/agentic-ontology-backfill.js +5 -5
  14. package/dist/bin/agentic-reflection-backfill.js +6 -6
  15. package/dist/bin/agentic-semantic-label.js +5 -5
  16. package/dist/bin/backfill-conversations.js +6 -6
  17. package/dist/bin/backfill-responses.js +6 -6
  18. package/dist/bin/backfill-vectors.js +8 -8
  19. package/dist/bin/bulk-sync-postgres.js +7 -7
  20. package/dist/bin/cc-doctor.js +4 -4
  21. package/dist/bin/cleanup-stale-review-tasks.js +11 -11
  22. package/dist/bin/cli.js +16 -16
  23. package/dist/bin/deferred-daemon-restart.js +1 -1
  24. package/dist/bin/exe-agent-config.js +2 -2
  25. package/dist/bin/exe-agent.js +4 -4
  26. package/dist/bin/exe-assign.js +8 -8
  27. package/dist/bin/exe-boot.js +21 -18
  28. package/dist/bin/exe-call.js +4 -4
  29. package/dist/bin/exe-cloud.js +7 -7
  30. package/dist/bin/exe-dispatch.js +11 -11
  31. package/dist/bin/exe-doctor.js +3 -2
  32. package/dist/bin/exe-export-behaviors.js +7 -7
  33. package/dist/bin/exe-forget.js +6 -6
  34. package/dist/bin/exe-gateway.js +7 -7
  35. package/dist/bin/exe-healthcheck.js +6 -4
  36. package/dist/bin/exe-heartbeat.js +11 -11
  37. package/dist/bin/exe-kill.js +14 -14
  38. package/dist/bin/exe-launch-agent.js +18 -18
  39. package/dist/bin/exe-new-employee.js +6 -6
  40. package/dist/bin/exe-pending-messages.js +12 -12
  41. package/dist/bin/exe-pending-notifications.js +11 -11
  42. package/dist/bin/exe-pending-reviews.js +11 -11
  43. package/dist/bin/exe-rename.js +4 -4
  44. package/dist/bin/exe-review.js +13 -13
  45. package/dist/bin/exe-search.js +5 -5
  46. package/dist/bin/exe-session-cleanup.js +16 -16
  47. package/dist/bin/exe-settings.js +39 -9
  48. package/dist/bin/exe-start-codex.js +11 -11
  49. package/dist/bin/exe-start-opencode.js +8 -8
  50. package/dist/bin/exe-status.js +12 -12
  51. package/dist/bin/exe-team.js +3 -3
  52. package/dist/bin/git-sweep.js +12 -12
  53. package/dist/bin/graph-backfill.js +4 -4
  54. package/dist/bin/graph-export.js +5 -5
  55. package/dist/bin/import-history.js +7 -7
  56. package/dist/bin/install-launchd.js +13 -6
  57. package/dist/bin/install.js +26 -14
  58. package/dist/bin/intercom-check.js +4 -4
  59. package/dist/bin/mcp-sessions.js +2 -2
  60. package/dist/bin/orchestration-metrics.js +4 -4
  61. package/dist/bin/postgres-agentic-reflection-backfill.js +2 -2
  62. package/dist/bin/postgres-agentic-semantic-backfill.js +1 -1
  63. package/dist/bin/scan-tasks.js +11 -11
  64. package/dist/bin/setup.js +1 -1
  65. package/dist/bin/shard-migrate.js +4 -4
  66. package/dist/bin/stack-update.js +2 -2
  67. package/dist/bin/vps-health-gate.js +1 -1
  68. package/dist/capability-cards-4USI7CUW.js +89 -0
  69. package/dist/capacity-monitor-WLCBTEYR.js +51 -0
  70. package/dist/catchup-brief-ZR3NX6LZ.js +175 -0
  71. package/dist/chunk-22TVSRQQ.js +226 -0
  72. package/dist/chunk-2E43UXRH.js +395 -0
  73. package/dist/chunk-2PIGT6UJ.js +460 -0
  74. package/dist/chunk-3XTMW2MZ.js +535 -0
  75. package/dist/chunk-465PQFTH.js +262 -0
  76. package/dist/chunk-5CCXU2AW.js +129 -0
  77. package/dist/chunk-5D6MPWR7.js +1094 -0
  78. package/dist/chunk-5Q4MR6SL.js +123 -0
  79. package/dist/chunk-6327RBWR.js +345 -0
  80. package/dist/chunk-6MZZREZY.js +199 -0
  81. package/dist/chunk-7DI2Q4O5.js +1186 -0
  82. package/dist/chunk-7PW5VNIY.js +122 -0
  83. package/dist/chunk-7T7Y56HW.js +43 -0
  84. package/dist/chunk-7UHCWCLT.js +128 -0
  85. package/dist/chunk-A2ZUMF6L.js +1350 -0
  86. package/dist/chunk-AKV44JEH.js +185 -0
  87. package/dist/chunk-ANHWGX5N.js +735 -0
  88. package/dist/chunk-BQ3P4TKD.js +97 -0
  89. package/dist/chunk-BUZMT3KZ.js +604 -0
  90. package/dist/chunk-C2SBESBO.js +210 -0
  91. package/dist/chunk-CLSXZUZW.js +51 -0
  92. package/dist/chunk-CONHLVAR.js +1079 -0
  93. package/dist/chunk-D3WTZPFX.js +456 -0
  94. package/dist/chunk-DE6SOIYL.js +197 -0
  95. package/dist/chunk-EIVNMA3Q.js +284 -0
  96. package/dist/chunk-EJIF4FNT.js +12 -0
  97. package/dist/chunk-FDFOW564.js +171 -0
  98. package/dist/chunk-GZUBJ5EC.js +127 -0
  99. package/dist/chunk-HGZITN22.js +105 -0
  100. package/dist/chunk-HSRKDU6X.js +362 -0
  101. package/dist/chunk-IIEN2PHV.js +85 -0
  102. package/dist/chunk-JQ56VLMM.js +567 -0
  103. package/dist/chunk-JVHHXRFY.js +280 -0
  104. package/dist/chunk-JXCXGZ3S.js +55 -0
  105. package/dist/chunk-K5ZO532Q.js +4388 -0
  106. package/dist/chunk-K6CAAMXF.js +97 -0
  107. package/dist/chunk-KA26YTNU.js +81 -0
  108. package/dist/chunk-KMUW5C3R.js +381 -0
  109. package/dist/chunk-KOO3J5PV.js +20 -0
  110. package/dist/chunk-LSV7OFIH.js +290 -0
  111. package/dist/chunk-LSVFDVNY.js +1158 -0
  112. package/dist/chunk-LXDQTW32.js +230 -0
  113. package/dist/chunk-MEP7OUVZ.js +181 -0
  114. package/dist/chunk-MN2B2LKS.js +240 -0
  115. package/dist/chunk-N2EAYPYQ.js +1352 -0
  116. package/dist/chunk-N7I2A667.js +70 -0
  117. package/dist/chunk-NLZHVIOP.js +630 -0
  118. package/dist/chunk-NUH5TRZL.js +227 -0
  119. package/dist/chunk-OAHEIH3G.js +167 -0
  120. package/dist/chunk-OBHRQGCK.js +58 -0
  121. package/dist/chunk-ODFA7B2V.js +54 -0
  122. package/dist/chunk-OSNUP45F.js +731 -0
  123. package/dist/chunk-OTPRHBTO.js +33 -0
  124. package/dist/chunk-P6MUA4QU.js +157 -0
  125. package/dist/chunk-PGIOFKSK.js +2093 -0
  126. package/dist/chunk-PSE7VHWK.js +50 -0
  127. package/dist/chunk-QIFUVZFW.js +331 -0
  128. package/dist/chunk-RDPXKTVK.js +221 -0
  129. package/dist/chunk-RKYTYJGB.js +76 -0
  130. package/dist/chunk-RXLR6EFM.js +348 -0
  131. package/dist/chunk-SDB67PQJ.js +159 -0
  132. package/dist/chunk-SF2T7MP3.js +402 -0
  133. package/dist/chunk-SLU3FRFQ.js +2133 -0
  134. package/dist/chunk-SNDZJ5IV.js +214 -0
  135. package/dist/chunk-STEEAABW.js +448 -0
  136. package/dist/chunk-TUTWNHIQ.js +244 -0
  137. package/dist/chunk-UDP35QBR.js +30 -0
  138. package/dist/chunk-UKFHNJBI.js +85 -0
  139. package/dist/chunk-VC2DTK2X.js +382 -0
  140. package/dist/chunk-VRRAE5JX.js +836 -0
  141. package/dist/chunk-VVJTBQPR.js +38 -0
  142. package/dist/chunk-W3EQ362K.js +581 -0
  143. package/dist/chunk-WHIXIFHC.js +2242 -0
  144. package/dist/chunk-WRNGJJNR.js +377 -0
  145. package/dist/chunk-WUKHLCBE.js +3313 -0
  146. package/dist/chunk-WVPLHGDG.js +150 -0
  147. package/dist/chunk-XJZBSTL5.js +204 -0
  148. package/dist/chunk-Y3PMNUM5.js +304 -0
  149. package/dist/chunk-YHVS4QOV.js +14597 -0
  150. package/dist/chunk-YJ2OYAOC.js +668 -0
  151. package/dist/chunk-YYAD2GXX.js +128 -0
  152. package/dist/chunk-ZQML7EWE.js +333 -0
  153. package/dist/co-activation-XJLH46OX.js +74 -0
  154. package/dist/co-occurrence-GNN2X526.js +95 -0
  155. package/dist/code-context-index-OCPRLFG5.js +30 -0
  156. package/dist/core-memory-J4W2IYOF.js +110 -0
  157. package/dist/crdt-sync-QCBTSHIH.js +33 -0
  158. package/dist/crm-webhook-EM442VUW.js +10 -0
  159. package/dist/cto-delegation-gate-MLJMVHBK.js +280 -0
  160. package/dist/daemon-orchestration-2VNLZVTW.js +139 -0
  161. package/dist/db-backup-VUGFTPJ4.js +43 -0
  162. package/dist/doc-graph-extractor-PNRSFPSS.js +133 -0
  163. package/dist/dreaming-SK5VEQRF.js +34 -0
  164. package/dist/entity-boost-TQWWJUC2.js +375 -0
  165. package/dist/exe-drift-N34UPO7S.js +70 -0
  166. package/dist/exe-export-KACBKGVV.js +77 -0
  167. package/dist/exe-import-GXGDWACG.js +80 -0
  168. package/dist/exe-key-XPDOZBWW.js +673 -0
  169. package/dist/exe-snapshot-32GQKGQ5.js +338 -0
  170. package/dist/fast-db-init-F3TDD5VV.js +7 -0
  171. package/dist/gateway/index.js +8 -8
  172. package/dist/git-staleness-J45WNYRF.js +112 -0
  173. package/dist/git-task-sweep-BTGVQPFB.js +42 -0
  174. package/dist/global-procedures-6JCQWU4D.js +22 -0
  175. package/dist/graph-auto-extract-3ZQNXTPC.js +183 -0
  176. package/dist/hooks/bug-report-worker.js +13 -13
  177. package/dist/hooks/codex-stop-task-finalizer.js +13 -13
  178. package/dist/hooks/commit-complete.js +13 -13
  179. package/dist/hooks/error-recall.js +6 -6
  180. package/dist/hooks/exe-heartbeat-hook.js +3 -3
  181. package/dist/hooks/ingest-worker.js +3 -3
  182. package/dist/hooks/ingest.js +6 -6
  183. package/dist/hooks/instructions-loaded.js +4 -4
  184. package/dist/hooks/manifest.json +20 -20
  185. package/dist/hooks/notification.js +4 -4
  186. package/dist/hooks/post-compact.js +12 -12
  187. package/dist/hooks/post-tool-combined.js +6 -6
  188. package/dist/hooks/pre-compact.js +16 -16
  189. package/dist/hooks/pre-tool-use.js +16 -16
  190. package/dist/hooks/prompt-submit.js +24 -24
  191. package/dist/hooks/session-end.js +21 -21
  192. package/dist/hooks/session-start.js +12 -12
  193. package/dist/hooks/stop.js +19 -19
  194. package/dist/hooks/subagent-stop.js +12 -12
  195. package/dist/hooks/summary-worker.js +19 -19
  196. package/dist/index.js +19 -19
  197. package/dist/installer-5VPFY7SB.js +298 -0
  198. package/dist/installer-OENFPMA2.js +344 -0
  199. package/dist/installer-OIX4QOG5.js +40 -0
  200. package/dist/lib/cloud-sync.js +7 -7
  201. package/dist/lib/consolidation.js +6 -5
  202. package/dist/lib/database.js +2 -2
  203. package/dist/lib/db-daemon-client.js +2 -2
  204. package/dist/lib/db.js +2 -2
  205. package/dist/lib/embed-worker.js +1 -0
  206. package/dist/lib/embedder.js +7 -3
  207. package/dist/lib/employee-templates.js +4 -4
  208. package/dist/lib/employees.js +2 -2
  209. package/dist/lib/exe-daemon-client.js +2 -2
  210. package/dist/lib/exe-daemon.js +160 -79
  211. package/dist/lib/hybrid-search.js +5 -5
  212. package/dist/lib/identity.js +2 -2
  213. package/dist/lib/messaging.js +11 -11
  214. package/dist/lib/reminders.js +3 -3
  215. package/dist/lib/schedules.js +5 -5
  216. package/dist/lib/session-registry.js +4 -4
  217. package/dist/lib/skill-learning.js +6 -6
  218. package/dist/lib/store.js +4 -4
  219. package/dist/lib/task-router.js +3 -3
  220. package/dist/lib/tasks.js +12 -12
  221. package/dist/lib/tmux-routing.js +12 -10
  222. package/dist/lib/tmux-transport.js +1 -1
  223. package/dist/lib/token-spend.js +3 -3
  224. package/dist/lib/transport.js +2 -2
  225. package/dist/mcp/register-tools.js +62 -61
  226. package/dist/mcp/server.js +63 -62
  227. package/dist/mcp/tools/complete-reminder.js +4 -4
  228. package/dist/mcp/tools/create-reminder.js +4 -4
  229. package/dist/mcp/tools/create-task.js +14 -14
  230. package/dist/mcp/tools/deactivate-behavior.js +7 -7
  231. package/dist/mcp/tools/list-reminders.js +4 -4
  232. package/dist/mcp/tools/list-tasks.js +14 -14
  233. package/dist/mcp/tools/send-message.js +13 -13
  234. package/dist/mcp/tools/update-task.js +13 -13
  235. package/dist/mcp-http-config-PQTOLCTP.js +29 -0
  236. package/dist/memory-cards-4RVDZIY2.js +180 -0
  237. package/dist/memory-graph-extractor-L6YC7G4M.js +22 -0
  238. package/dist/memory-poisoning-defense-4YVJYH4G.js +224 -0
  239. package/dist/memory-queue-client-MVAUOZNJ.js +16 -0
  240. package/dist/memory-reflection-SHHDQNOH.js +244 -0
  241. package/dist/message-queue-client-DCKZT6X2.js +92 -0
  242. package/dist/notifications-JFR3G42W.js +47 -0
  243. package/dist/orchestration-events-MGCGPTDN.js +27 -0
  244. package/dist/orchestrator-DAFL2YZB.js +35 -0
  245. package/dist/pipeline-router-WWSZVPCH.js +15 -0
  246. package/dist/plan-limits-C7XCSDZC.js +28 -0
  247. package/dist/project-boot-N3NTBVLE.js +299 -0
  248. package/dist/projection-worker-MTPAPCWX.js +1084 -0
  249. package/dist/prospective-memory-BTIVUJSB.js +232 -0
  250. package/dist/reranker-UA6WVESJ.js +19 -0
  251. package/dist/retrieval-health-7XNZJEBF.js +12 -0
  252. package/dist/review-polling-4ALGMXC3.js +126 -0
  253. package/dist/runtime/index.js +13 -13
  254. package/dist/self-query-router-MROFQLQB.js +192 -0
  255. package/dist/session-events-CK44XOU4.js +38 -0
  256. package/dist/session-kill-telemetry-MT6ITDOG.js +31 -0
  257. package/dist/session-scope-3XDBWV65.js +88 -0
  258. package/dist/setup-wizard-X6DOD7MC.js +12 -0
  259. package/dist/skill-refinement-G2CCY3GM.js +159 -0
  260. package/dist/stack-update-JF7F56AS.js +84 -0
  261. package/dist/steward-gate-YF2CYXE7.js +15 -0
  262. package/dist/task-enforcement-YN6HK7NE.js +506 -0
  263. package/dist/task-scope-CVK6ISCZ.js +37 -0
  264. package/dist/tasks-crud-NTNET4JE.js +79 -0
  265. package/dist/tasks-notify-4LJVFPCV.js +40 -0
  266. package/dist/tasks-review-3V4WOIRG.js +49 -0
  267. package/dist/telemetry-upload-5PNUKGTM.js +741 -0
  268. package/dist/token-budget-E46G7ZAQ.js +86 -0
  269. package/dist/tool-capability-index-JDSMKJER.js +10 -0
  270. package/dist/tool-telemetry-J3NLS3LJ.js +17 -0
  271. package/dist/tui/App.js +18 -18
  272. package/dist/tui-data-6DOMUUCM.js +260 -0
  273. package/dist/wiki-acl-5UK37LKF.js +111 -0
  274. package/dist/worker-gate-FM7AEC7G.js +21 -0
  275. package/dist/workflow-engine-2EDUHUIY.js +28 -0
  276. package/dist/worktree-7YKKJIYR.js +28 -0
  277. package/dist/worktree-sweep-C3ELFGDN.js +21 -0
  278. package/package.json +1 -1
  279. package/release-notes.json +23 -23
@@ -0,0 +1,3313 @@
1
+ import {
2
+ assertValidDbPath
3
+ } from "./chunk-2I23RPSI.js";
4
+ import {
5
+ listBackups
6
+ } from "./chunk-2E43UXRH.js";
7
+ import {
8
+ getEmotionalBaseline,
9
+ renderEmotionalBaselineSection
10
+ } from "./chunk-PNQDP3OA.js";
11
+ import {
12
+ isDaemonAlive
13
+ } from "./chunk-7HLWBYH7.js";
14
+ import {
15
+ EMBEDDING_DIM
16
+ } from "./chunk-FXU7JOXK.js";
17
+ import {
18
+ EXE_AI_DIR,
19
+ loadConfigSync
20
+ } from "./chunk-R36FAN53.js";
21
+ import {
22
+ atomicWriteJson
23
+ } from "./chunk-LYH5HE24.js";
24
+
25
+ // src/lib/database.ts
26
+ import { chmodSync, existsSync as existsSync3, statSync, copyFileSync, unlinkSync as unlinkSync2, openSync, closeSync, mkdirSync as mkdirSync2, renameSync as renameSync2 } from "fs";
27
+ import { createClient } from "@libsql/client";
28
+ import { homedir, totalmem } from "os";
29
+ import { join } from "path";
30
+
31
+ // src/lib/db-retry.ts
32
+ var MAX_RETRIES = 5;
33
+ var BASE_DELAY_MS = 250;
34
+ var MAX_JITTER_MS = 400;
35
+ function isBusyError(err) {
36
+ if (err instanceof Error) {
37
+ const msg = err.message.toLowerCase();
38
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
39
+ }
40
+ return false;
41
+ }
42
+ function delay(ms) {
43
+ return new Promise((resolve) => setTimeout(resolve, ms));
44
+ }
45
+ async function retryOnBusy(fn, label) {
46
+ let lastError;
47
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
48
+ try {
49
+ return await fn();
50
+ } catch (err) {
51
+ lastError = err;
52
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
53
+ throw err;
54
+ }
55
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
56
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
57
+ process.stderr.write(
58
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
59
+ `
60
+ );
61
+ await delay(backoff + jitter);
62
+ }
63
+ }
64
+ throw lastError;
65
+ }
66
+ function wrapWithRetry(client) {
67
+ return new Proxy(client, {
68
+ get(target, prop, receiver) {
69
+ if (prop === "execute") {
70
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
71
+ }
72
+ if (prop === "batch") {
73
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
74
+ }
75
+ return Reflect.get(target, prop, receiver);
76
+ }
77
+ });
78
+ }
79
+
80
+ // src/lib/employees.ts
81
+ import { readFile, mkdir } from "fs/promises";
82
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
83
+ import { execSync } from "child_process";
84
+ import path2 from "path";
85
+ import os from "os";
86
+
87
+ // src/lib/identity.ts
88
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
89
+ import { readdirSync } from "fs";
90
+ import path from "path";
91
+ import { createHash } from "crypto";
92
+ var IDENTITY_DIR = path.join(EXE_AI_DIR, "identity");
93
+ function ensureDir() {
94
+ if (!existsSync(IDENTITY_DIR)) {
95
+ mkdirSync(IDENTITY_DIR, { recursive: true });
96
+ }
97
+ }
98
+ function identityPath(agentId) {
99
+ return path.join(IDENTITY_DIR, `${agentId}.md`);
100
+ }
101
+ function sanitizeIdentityBody(body) {
102
+ return body.replace(/<!--[\s\S]*?-->/g, "").trim();
103
+ }
104
+ function parseFrontmatter(raw) {
105
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
106
+ if (!match) {
107
+ return {
108
+ frontmatter: {
109
+ role: "unknown",
110
+ title: "Unknown",
111
+ agent_id: "unknown",
112
+ org_level: "specialist",
113
+ created_by: "system",
114
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
115
+ },
116
+ body: sanitizeIdentityBody(raw)
117
+ };
118
+ }
119
+ const yamlStr = match[1];
120
+ const body = sanitizeIdentityBody(match[2]);
121
+ const fm = {};
122
+ for (const line of yamlStr.split("\n")) {
123
+ const kv = line.match(/^(\w+):\s*(.+)$/);
124
+ if (kv) fm[kv[1]] = kv[2].trim();
125
+ }
126
+ return {
127
+ frontmatter: {
128
+ role: fm.role ?? "unknown",
129
+ title: fm.title ?? "Unknown",
130
+ agent_id: fm.agent_id ?? "unknown",
131
+ org_level: fm.org_level ?? "specialist",
132
+ created_by: fm.created_by ?? "system",
133
+ updated_at: fm.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
134
+ tone: fm.tone,
135
+ vocabulary: fm.vocabulary,
136
+ response_style: fm.response_style,
137
+ communication_patterns: fm.communication_patterns
138
+ },
139
+ body
140
+ };
141
+ }
142
+ function contentHash(content) {
143
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
144
+ }
145
+ function getIdentity(agentId) {
146
+ const filePath = identityPath(agentId);
147
+ if (!existsSync(filePath)) return null;
148
+ const raw = readFileSync(filePath, "utf-8");
149
+ const { frontmatter, body } = parseFrontmatter(raw);
150
+ return {
151
+ agentId,
152
+ frontmatter,
153
+ body,
154
+ raw,
155
+ contentHash: contentHash(raw)
156
+ };
157
+ }
158
+ async function updateIdentity(agentId, content, updatedBy) {
159
+ ensureDir();
160
+ const filePath = identityPath(agentId);
161
+ const hash = contentHash(content);
162
+ writeFileSync(filePath, content, "utf-8");
163
+ try {
164
+ const client = getClient();
165
+ await client.execute({
166
+ sql: `INSERT INTO identity (agent_id, content_hash, updated_at, updated_by)
167
+ VALUES (?, ?, ?, ?)
168
+ ON CONFLICT(agent_id) DO UPDATE SET
169
+ content_hash = excluded.content_hash,
170
+ updated_at = excluded.updated_at,
171
+ updated_by = excluded.updated_by`,
172
+ args: [agentId, hash, (/* @__PURE__ */ new Date()).toISOString(), updatedBy]
173
+ });
174
+ } catch {
175
+ }
176
+ }
177
+ function listIdentities() {
178
+ ensureDir();
179
+ const files = readdirSync(IDENTITY_DIR).filter((f) => f.endsWith(".md"));
180
+ const results = [];
181
+ for (const file of files) {
182
+ const agentId = file.replace(".md", "");
183
+ const identity = getIdentity(agentId);
184
+ if (!identity) continue;
185
+ const lines = identity.body.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
186
+ const summary = lines[0]?.trim().slice(0, 120) ?? identity.frontmatter.title;
187
+ results.push({
188
+ agentId,
189
+ // User-facing/team-facing title only. `frontmatter.role` is internal
190
+ // routing metadata and must not leak as an external title.
191
+ title: identity.frontmatter.title,
192
+ summary
193
+ });
194
+ }
195
+ return results;
196
+ }
197
+ function getIdentityInjection(agentId) {
198
+ const own = getIdentity(agentId);
199
+ const all = listIdentities();
200
+ const parts = [];
201
+ if (own) {
202
+ parts.push(`## Your Identity (exe.md)
203
+ These define WHO YOU ARE. Non-negotiable. Permanent.
204
+
205
+ ${own.body}`);
206
+ const personaLines = [];
207
+ if (own.frontmatter.tone) personaLines.push(`- **Tone:** ${own.frontmatter.tone}`);
208
+ if (own.frontmatter.vocabulary) personaLines.push(`- **Vocabulary:** ${own.frontmatter.vocabulary}`);
209
+ if (own.frontmatter.response_style) personaLines.push(`- **Response style:** ${own.frontmatter.response_style}`);
210
+ if (own.frontmatter.communication_patterns) personaLines.push(`- **Communication patterns:** ${own.frontmatter.communication_patterns}`);
211
+ if (personaLines.length > 0) {
212
+ parts.push(`## Persona
213
+ How you communicate \u2014 these shape every response.
214
+
215
+ ${personaLines.join("\n")}`);
216
+ }
217
+ const emotionalBaseline = getEmotionalBaseline(own.frontmatter.role);
218
+ if (emotionalBaseline) {
219
+ parts.push(renderEmotionalBaselineSection(emotionalBaseline));
220
+ }
221
+ }
222
+ const teamLines = all.filter((a) => a.agentId !== agentId).map((a) => `- ${a.agentId} (${a.title}): ${a.summary}`);
223
+ if (teamLines.length > 0) {
224
+ parts.push(`## Team Identities
225
+ ${teamLines.join("\n")}`);
226
+ }
227
+ return parts.join("\n\n");
228
+ }
229
+
230
+ // src/lib/employees.ts
231
+ var EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
232
+ var DEFAULT_BOOT_TIMEOUT_MS = 45e3;
233
+ var DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
234
+ var COORDINATOR_ROLE = "COO";
235
+ function normalizeRole(role) {
236
+ return (role ?? "").trim().toLowerCase();
237
+ }
238
+ function isCoordinatorRole(role) {
239
+ return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
240
+ }
241
+ function getCoordinatorEmployee(employees) {
242
+ return employees.find((e) => isCoordinatorRole(e.role));
243
+ }
244
+ function getCoordinatorName(employees = loadEmployeesSync()) {
245
+ return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
246
+ }
247
+ function getCoordinatorDisplayTitle(employees = loadEmployeesSync()) {
248
+ const coordinator = getCoordinatorEmployee(employees);
249
+ if (coordinator) {
250
+ try {
251
+ const identity = getIdentity(coordinator.name);
252
+ if (identity?.frontmatter?.title) return identity.frontmatter.title;
253
+ } catch {
254
+ }
255
+ }
256
+ return "Coordinator";
257
+ }
258
+ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
259
+ if (!agentName) return false;
260
+ return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
261
+ }
262
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
263
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
264
+ }
265
+ var MANAGER_ROLES = /* @__PURE__ */ new Map([
266
+ ["coo", /* @__PURE__ */ new Set(["cto", "cmo", "gm", "principal engineer", "content production specialist", "staff code reviewer", "ai product lead", "specialist"])],
267
+ ["cto", /* @__PURE__ */ new Set(["principal engineer", "staff code reviewer", "specialist"])],
268
+ ["cmo", /* @__PURE__ */ new Set(["content production specialist", "specialist"])],
269
+ ["gm", /* @__PURE__ */ new Set(["specialist"])]
270
+ ]);
271
+ function canManage(callerName, callerRole, targetName, employees = loadEmployeesSync()) {
272
+ if (canCoordinate(callerName, callerRole, employees)) return true;
273
+ const targetBase = baseAgentName(targetName, employees);
274
+ const targetEmp = getEmployee(employees, targetBase);
275
+ if (!targetEmp) return false;
276
+ const normalizedCallerRole = normalizeRole(callerRole);
277
+ const allowedSubordinates = MANAGER_ROLES.get(normalizedCallerRole);
278
+ if (!allowedSubordinates) return false;
279
+ return allowedSubordinates.has(normalizeRole(targetEmp.role));
280
+ }
281
+ var VALID_AGENT_NAME = /^[a-z][a-z0-9]*$/;
282
+ var VALID_AGENT_NAME_DESCRIPTION = "Name must start with a letter and contain only lowercase letters and digits (no hyphens, underscores, or spaces).";
283
+ function validateEmployeeName(name) {
284
+ if (!name) {
285
+ return { valid: false, error: "Name is required" };
286
+ }
287
+ if (name.length > 32) {
288
+ return { valid: false, error: "Name must be 32 characters or fewer" };
289
+ }
290
+ if (!VALID_AGENT_NAME.test(name)) {
291
+ return {
292
+ valid: false,
293
+ error: VALID_AGENT_NAME_DESCRIPTION
294
+ };
295
+ }
296
+ return { valid: true };
297
+ }
298
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
299
+ if (!existsSync2(employeesPath)) {
300
+ return [];
301
+ }
302
+ const raw = await readFile(employeesPath, "utf-8");
303
+ try {
304
+ return JSON.parse(raw);
305
+ } catch {
306
+ return [];
307
+ }
308
+ }
309
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
310
+ await mkdir(path2.dirname(employeesPath), { recursive: true });
311
+ await atomicWriteJson(employeesPath, employees);
312
+ }
313
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
314
+ if (!existsSync2(employeesPath)) return [];
315
+ try {
316
+ return JSON.parse(readFileSync2(employeesPath, "utf-8"));
317
+ } catch {
318
+ return [];
319
+ }
320
+ }
321
+ function getEmployee(employees, name) {
322
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
323
+ }
324
+ function getEmployeeByRole(employees, role) {
325
+ const lower = role.toLowerCase();
326
+ return employees.find((e) => e.role.toLowerCase() === lower);
327
+ }
328
+ function getEmployeeNamesByRole(employees, role) {
329
+ const lower = role.toLowerCase();
330
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
331
+ }
332
+ function hasRole(agentName, role) {
333
+ const employees = loadEmployeesSync();
334
+ const emp = getEmployee(employees, agentName);
335
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
336
+ }
337
+ function baseAgentName(name, employees) {
338
+ const match = name.match(/^([a-zA-Z]+)\d+$/);
339
+ if (!match) return name;
340
+ const base = match[1];
341
+ const roster = employees ?? loadEmployeesSync();
342
+ if (getEmployee(roster, base)) return base;
343
+ return name;
344
+ }
345
+ var MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set([
346
+ "principal engineer",
347
+ "content production specialist",
348
+ "staff code reviewer"
349
+ ]);
350
+ function isMultiInstance(agentName, employees) {
351
+ const roster = employees ?? loadEmployeesSync();
352
+ const emp = getEmployee(roster, agentName);
353
+ if (!emp) return false;
354
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
355
+ }
356
+ function shouldAutoInstance(agentName, employees) {
357
+ const multi = isMultiInstance(agentName, employees);
358
+ if (!multi) return { autoInstance: false, maxAutoInstances: void 0 };
359
+ let max = 10;
360
+ try {
361
+ const config = loadConfigSync();
362
+ max = config.sessionLifecycle?.maxAutoInstances ?? 10;
363
+ } catch {
364
+ }
365
+ return { autoInstance: true, maxAutoInstances: max };
366
+ }
367
+ function addEmployee(employees, employee) {
368
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
369
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
370
+ if (!VALID_AGENT_NAME.test(normalized.name)) {
371
+ throw new Error(
372
+ `Invalid agent name '${normalized.name}' \u2014 must be lowercase letters and digits only (no hyphens, underscores, or spaces). Example: 'davinci', not 'da-vinci'.`
373
+ );
374
+ }
375
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
376
+ throw new Error(`Employee '${normalized.name}' already exists`);
377
+ }
378
+ return [...employees, normalized];
379
+ }
380
+ var IDENTITY_DIR2 = path2.join(EXE_AI_DIR, "identity");
381
+ var TEAM_SECTION_RE = /^## Team\b.*$/m;
382
+ function appendToCoordinatorTeam(employee) {
383
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
384
+ if (!coordinator) return;
385
+ const idPath = path2.join(IDENTITY_DIR2, `${coordinator.name}.md`);
386
+ if (!existsSync2(idPath)) return;
387
+ const content = readFileSync2(idPath, "utf-8");
388
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
389
+ const teamMatch = content.match(TEAM_SECTION_RE);
390
+ if (!teamMatch || teamMatch.index === void 0) return;
391
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
392
+ const nextHeading = afterTeam.match(/\n## /);
393
+ const entry = `
394
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
395
+ `;
396
+ let updated;
397
+ if (nextHeading && nextHeading.index !== void 0) {
398
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
399
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
400
+ } else {
401
+ updated = content.trimEnd() + "\n" + entry;
402
+ }
403
+ writeFileSync2(idPath, updated, "utf-8");
404
+ }
405
+ function capitalize(s) {
406
+ return s.charAt(0).toUpperCase() + s.slice(1);
407
+ }
408
+ async function hireEmployee(employee) {
409
+ const employees = await loadEmployees();
410
+ const updated = addEmployee(employees, employee);
411
+ await saveEmployees(updated);
412
+ try {
413
+ appendToCoordinatorTeam(employee);
414
+ } catch {
415
+ }
416
+ try {
417
+ const { loadAgentConfig, saveAgentConfig } = await import("./lib/agent-config.js");
418
+ const config = loadAgentConfig();
419
+ const name = employee.name.toLowerCase();
420
+ if (!config[name] && config["default"]) {
421
+ config[name] = { ...config["default"] };
422
+ saveAgentConfig(config);
423
+ }
424
+ } catch {
425
+ }
426
+ return updated;
427
+ }
428
+ async function normalizeRosterCase(rosterPath) {
429
+ const employees = await loadEmployees(rosterPath);
430
+ let changed = false;
431
+ for (const emp of employees) {
432
+ if (emp.name !== emp.name.toLowerCase()) {
433
+ const oldName = emp.name;
434
+ emp.name = emp.name.toLowerCase();
435
+ changed = true;
436
+ try {
437
+ const identityDir = path2.join(os.homedir(), ".exe-os", "identity");
438
+ const oldPath = path2.join(identityDir, `${oldName}.md`);
439
+ const newPath = path2.join(identityDir, `${emp.name}.md`);
440
+ if (existsSync2(oldPath) && !existsSync2(newPath)) {
441
+ renameSync(oldPath, newPath);
442
+ } else if (existsSync2(oldPath) && oldPath !== newPath) {
443
+ const content = readFileSync2(oldPath, "utf-8");
444
+ writeFileSync2(newPath, content, "utf-8");
445
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
446
+ unlinkSync(oldPath);
447
+ }
448
+ }
449
+ } catch {
450
+ }
451
+ }
452
+ }
453
+ if (changed) {
454
+ await saveEmployees(employees, rosterPath);
455
+ }
456
+ return changed;
457
+ }
458
+ function findExeBin() {
459
+ try {
460
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
461
+ } catch {
462
+ return null;
463
+ }
464
+ }
465
+ function registerBinSymlinks(name) {
466
+ const created = [];
467
+ const skipped = [];
468
+ const errors = [];
469
+ const exeBinPath = findExeBin();
470
+ if (!exeBinPath) {
471
+ errors.push("Could not find 'exe-os' in PATH");
472
+ return { created, skipped, errors };
473
+ }
474
+ const binDir = path2.dirname(exeBinPath);
475
+ let target;
476
+ try {
477
+ target = readlinkSync(exeBinPath);
478
+ } catch {
479
+ errors.push("Could not read 'exe' symlink");
480
+ return { created, skipped, errors };
481
+ }
482
+ for (const suffix of ["", "-opencode"]) {
483
+ const linkName = `${name}${suffix}`;
484
+ const linkPath = path2.join(binDir, linkName);
485
+ if (existsSync2(linkPath)) {
486
+ skipped.push(linkName);
487
+ continue;
488
+ }
489
+ try {
490
+ symlinkSync(target, linkPath);
491
+ created.push(linkName);
492
+ } catch (err) {
493
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
494
+ }
495
+ }
496
+ return { created, skipped, errors };
497
+ }
498
+
499
+ // src/lib/database-adapter.ts
500
+ import os2 from "os";
501
+ import path3 from "path";
502
+ import { createRequire } from "module";
503
+ import { pathToFileURL } from "url";
504
+ var VIEW_MAPPINGS = [
505
+ { view: "memories", source: "graph.memory_records" },
506
+ { view: "tasks", source: "graph.tasks" },
507
+ { view: "behaviors", source: "graph.behaviors" },
508
+ { view: "entities", source: "graph.entities" },
509
+ { view: "relationships", source: "graph.relationships" },
510
+ { view: "entity_memories", source: "graph.entity_memories" },
511
+ { view: "entity_aliases", source: "graph.entity_aliases" },
512
+ { view: "notifications", source: "graph.notifications" },
513
+ { view: "messages", source: "graph.messages" },
514
+ { view: "users", source: "wiki.users" },
515
+ { view: "workspaces", source: "wiki.workspaces" },
516
+ { view: "workspace_users", source: "wiki.workspace_users" },
517
+ { view: "documents", source: "wiki.workspace_documents" },
518
+ { view: "chats", source: "wiki.workspace_chats" }
519
+ ];
520
+ var UPSERT_KEYS = {
521
+ memories: ["id"],
522
+ tasks: ["id"],
523
+ behaviors: ["id"],
524
+ entities: ["id"],
525
+ relationships: ["id"],
526
+ entity_aliases: ["alias"],
527
+ notifications: ["id"],
528
+ messages: ["id"],
529
+ users: ["id"],
530
+ workspaces: ["id"],
531
+ workspace_users: ["id"],
532
+ documents: ["id"],
533
+ chats: ["id"]
534
+ };
535
+ var BOOLEAN_COLUMNS_BY_TABLE = {
536
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
537
+ behaviors: /* @__PURE__ */ new Set(["active"]),
538
+ notifications: /* @__PURE__ */ new Set(["read"]),
539
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
540
+ };
541
+ var BOOLEAN_COLUMN_NAMES = new Set(
542
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
543
+ );
544
+ var IMMEDIATE_FALLBACK_PATTERNS = [
545
+ /\bPRAGMA\b/i,
546
+ /\bsqlite_master\b/i,
547
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
548
+ /\bMATCH\b/i,
549
+ /\bvector_distance_cos\s*\(/i,
550
+ /\bjson_extract\s*\(/i,
551
+ /\bjulianday\s*\(/i,
552
+ /\bstrftime\s*\(/i,
553
+ /\blast_insert_rowid\s*\(/i
554
+ ];
555
+ var prismaClientPromise = null;
556
+ var compatibilityBootstrapPromise = null;
557
+ function quotedIdentifier(identifier) {
558
+ return `"${identifier.replace(/"/g, '""')}"`;
559
+ }
560
+ function unqualifiedTableName(name) {
561
+ const raw = name.trim().replace(/^"|"$/g, "");
562
+ const parts = raw.split(".");
563
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
564
+ }
565
+ function stripTrailingSemicolon(sql) {
566
+ return sql.trim().replace(/;+\s*$/u, "");
567
+ }
568
+ function appendClause(sql, clause) {
569
+ const trimmed = stripTrailingSemicolon(sql);
570
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
571
+ if (!returningMatch) {
572
+ return `${trimmed}${clause}`;
573
+ }
574
+ const idx = returningMatch.index;
575
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
576
+ }
577
+ function normalizeStatement(stmt) {
578
+ if (typeof stmt === "string") {
579
+ return { kind: "positional", sql: stmt, args: [] };
580
+ }
581
+ const sql = stmt.sql;
582
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
583
+ return { kind: "positional", sql, args: stmt.args ?? [] };
584
+ }
585
+ return { kind: "named", sql, args: stmt.args };
586
+ }
587
+ function rewriteBooleanLiterals(sql) {
588
+ let out = sql;
589
+ for (const column of BOOLEAN_COLUMN_NAMES) {
590
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
591
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
592
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
593
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
594
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
595
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
596
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
597
+ }
598
+ return out;
599
+ }
600
+ function rewriteInsertOrIgnore(sql) {
601
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
602
+ return sql;
603
+ }
604
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
605
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
606
+ }
607
+ function rewriteInsertOrReplace(sql) {
608
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
609
+ if (!match) {
610
+ return sql;
611
+ }
612
+ const rawTable = match[1];
613
+ const rawColumns = match[2];
614
+ const remainder = match[3];
615
+ const tableName = unqualifiedTableName(rawTable);
616
+ const conflictKeys = UPSERT_KEYS[tableName];
617
+ if (!conflictKeys?.length) {
618
+ return sql;
619
+ }
620
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
621
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
622
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
623
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
624
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
625
+ }
626
+ function rewriteSql(sql) {
627
+ let out = sql;
628
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
629
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
630
+ out = rewriteBooleanLiterals(out);
631
+ out = rewriteInsertOrReplace(out);
632
+ out = rewriteInsertOrIgnore(out);
633
+ return stripTrailingSemicolon(out);
634
+ }
635
+ function toBoolean(value) {
636
+ if (value === null || value === void 0) return value;
637
+ if (typeof value === "boolean") return value;
638
+ if (typeof value === "number") return value !== 0;
639
+ if (typeof value === "bigint") return value !== 0n;
640
+ if (typeof value === "string") {
641
+ const normalized = value.trim().toLowerCase();
642
+ if (normalized === "0" || normalized === "false") return false;
643
+ if (normalized === "1" || normalized === "true") return true;
644
+ }
645
+ return Boolean(value);
646
+ }
647
+ function countQuestionMarks(sql, end) {
648
+ let count = 0;
649
+ let inSingle = false;
650
+ let inDouble = false;
651
+ let inLineComment = false;
652
+ let inBlockComment = false;
653
+ for (let i = 0; i < end; i++) {
654
+ const ch = sql[i];
655
+ const next = sql[i + 1];
656
+ if (inLineComment) {
657
+ if (ch === "\n") inLineComment = false;
658
+ continue;
659
+ }
660
+ if (inBlockComment) {
661
+ if (ch === "*" && next === "/") {
662
+ inBlockComment = false;
663
+ i += 1;
664
+ }
665
+ continue;
666
+ }
667
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
668
+ inLineComment = true;
669
+ i += 1;
670
+ continue;
671
+ }
672
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
673
+ inBlockComment = true;
674
+ i += 1;
675
+ continue;
676
+ }
677
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
678
+ inSingle = !inSingle;
679
+ continue;
680
+ }
681
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
682
+ inDouble = !inDouble;
683
+ continue;
684
+ }
685
+ if (!inSingle && !inDouble && ch === "?") {
686
+ count += 1;
687
+ }
688
+ }
689
+ return count;
690
+ }
691
+ function findBooleanPlaceholderIndexes(sql) {
692
+ const indexes = /* @__PURE__ */ new Set();
693
+ for (const column of BOOLEAN_COLUMN_NAMES) {
694
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
695
+ for (const match of sql.matchAll(pattern)) {
696
+ const matchText = match[0];
697
+ const qIndex = match.index + matchText.lastIndexOf("?");
698
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
699
+ }
700
+ }
701
+ return indexes;
702
+ }
703
+ function coerceInsertBooleanArgs(sql, args) {
704
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
705
+ if (!match) return;
706
+ const rawTable = match[1];
707
+ const rawColumns = match[2];
708
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
709
+ if (!boolColumns?.size) return;
710
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
711
+ for (const [index, column] of columns.entries()) {
712
+ if (boolColumns.has(column) && index < args.length) {
713
+ args[index] = toBoolean(args[index]);
714
+ }
715
+ }
716
+ }
717
+ function coerceUpdateBooleanArgs(sql, args) {
718
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
719
+ if (!match) return;
720
+ const rawTable = match[1];
721
+ const setClause = match[2];
722
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
723
+ if (!boolColumns?.size) return;
724
+ const assignments = setClause.split(",");
725
+ let placeholderIndex = 0;
726
+ for (const assignment of assignments) {
727
+ if (!assignment.includes("?")) continue;
728
+ placeholderIndex += 1;
729
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
730
+ if (colMatch && boolColumns.has(colMatch[1])) {
731
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
732
+ }
733
+ }
734
+ }
735
+ function coerceBooleanArgs(sql, args) {
736
+ const nextArgs = [...args];
737
+ coerceInsertBooleanArgs(sql, nextArgs);
738
+ coerceUpdateBooleanArgs(sql, nextArgs);
739
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
740
+ for (const index of placeholderIndexes) {
741
+ if (index > 0 && index <= nextArgs.length) {
742
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
743
+ }
744
+ }
745
+ return nextArgs;
746
+ }
747
+ function convertQuestionMarksToDollarParams(sql) {
748
+ let out = "";
749
+ let placeholder = 0;
750
+ let inSingle = false;
751
+ let inDouble = false;
752
+ let inLineComment = false;
753
+ let inBlockComment = false;
754
+ for (let i = 0; i < sql.length; i++) {
755
+ const ch = sql[i];
756
+ const next = sql[i + 1];
757
+ if (inLineComment) {
758
+ out += ch;
759
+ if (ch === "\n") inLineComment = false;
760
+ continue;
761
+ }
762
+ if (inBlockComment) {
763
+ out += ch;
764
+ if (ch === "*" && next === "/") {
765
+ out += next;
766
+ inBlockComment = false;
767
+ i += 1;
768
+ }
769
+ continue;
770
+ }
771
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
772
+ out += ch + next;
773
+ inLineComment = true;
774
+ i += 1;
775
+ continue;
776
+ }
777
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
778
+ out += ch + next;
779
+ inBlockComment = true;
780
+ i += 1;
781
+ continue;
782
+ }
783
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
784
+ inSingle = !inSingle;
785
+ out += ch;
786
+ continue;
787
+ }
788
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
789
+ inDouble = !inDouble;
790
+ out += ch;
791
+ continue;
792
+ }
793
+ if (!inSingle && !inDouble && ch === "?") {
794
+ placeholder += 1;
795
+ out += `$${placeholder}`;
796
+ continue;
797
+ }
798
+ out += ch;
799
+ }
800
+ return out;
801
+ }
802
+ function translateStatementForPostgres(stmt) {
803
+ const normalized = normalizeStatement(stmt);
804
+ if (normalized.kind === "named") {
805
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
806
+ }
807
+ const rewrittenSql = rewriteSql(normalized.sql);
808
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
809
+ return {
810
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
811
+ args: coercedArgs
812
+ };
813
+ }
814
+ function shouldBypassPostgres(stmt) {
815
+ const normalized = normalizeStatement(stmt);
816
+ if (normalized.kind === "named") {
817
+ return true;
818
+ }
819
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
820
+ }
821
+ function shouldFallbackOnError(error) {
822
+ const message = error instanceof Error ? error.message : String(error);
823
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
824
+ }
825
+ function isReadQuery(sql) {
826
+ const trimmed = sql.trimStart();
827
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
828
+ }
829
+ function buildRow(row, columns) {
830
+ const values = columns.map((column) => row[column]);
831
+ return Object.assign(values, row);
832
+ }
833
+ function buildResultSet(rows, rowsAffected = 0) {
834
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
835
+ const resultRows = rows.map((row) => buildRow(row, columns));
836
+ return {
837
+ columns,
838
+ columnTypes: columns.map(() => ""),
839
+ rows: resultRows,
840
+ rowsAffected,
841
+ lastInsertRowid: void 0,
842
+ toJSON() {
843
+ return {
844
+ columns,
845
+ columnTypes: columns.map(() => ""),
846
+ rows,
847
+ rowsAffected,
848
+ lastInsertRowid: void 0
849
+ };
850
+ }
851
+ };
852
+ }
853
+ async function loadPrismaClient() {
854
+ if (!prismaClientPromise) {
855
+ prismaClientPromise = (async () => {
856
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
857
+ if (explicitPath) {
858
+ const module2 = await import(pathToFileURL(explicitPath).href);
859
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
860
+ if (!PrismaClient2) {
861
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
862
+ }
863
+ return new PrismaClient2();
864
+ }
865
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os2.homedir(), "exe-db");
866
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
867
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
868
+ const module = await import(pathToFileURL(prismaEntry).href);
869
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
870
+ if (!PrismaClient) {
871
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
872
+ }
873
+ return new PrismaClient();
874
+ })();
875
+ }
876
+ return prismaClientPromise;
877
+ }
878
+ async function ensureCompatibilityViews(prisma) {
879
+ if (!compatibilityBootstrapPromise) {
880
+ compatibilityBootstrapPromise = (async () => {
881
+ for (const mapping of VIEW_MAPPINGS) {
882
+ const relation = mapping.source.replace(/"/g, "");
883
+ const rows = await prisma.$queryRawUnsafe(
884
+ "SELECT to_regclass($1)::text AS regclass",
885
+ relation
886
+ );
887
+ if (!rows[0]?.regclass) {
888
+ continue;
889
+ }
890
+ await prisma.$executeRawUnsafe(
891
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
892
+ );
893
+ }
894
+ })();
895
+ }
896
+ return compatibilityBootstrapPromise;
897
+ }
898
+ async function executeOnPrisma(executor, stmt) {
899
+ const translated = translateStatementForPostgres(stmt);
900
+ if (isReadQuery(translated.sql)) {
901
+ const rows = await executor.$queryRawUnsafe(
902
+ translated.sql,
903
+ ...translated.args
904
+ );
905
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
906
+ }
907
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
908
+ return buildResultSet([], rowsAffected);
909
+ }
910
+ function splitSqlStatements(sql) {
911
+ const parts = [];
912
+ let current = "";
913
+ let inSingle = false;
914
+ let inDouble = false;
915
+ let inLineComment = false;
916
+ let inBlockComment = false;
917
+ for (let i = 0; i < sql.length; i++) {
918
+ const ch = sql[i];
919
+ const next = sql[i + 1];
920
+ if (inLineComment) {
921
+ current += ch;
922
+ if (ch === "\n") inLineComment = false;
923
+ continue;
924
+ }
925
+ if (inBlockComment) {
926
+ current += ch;
927
+ if (ch === "*" && next === "/") {
928
+ current += next;
929
+ inBlockComment = false;
930
+ i += 1;
931
+ }
932
+ continue;
933
+ }
934
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
935
+ current += ch + next;
936
+ inLineComment = true;
937
+ i += 1;
938
+ continue;
939
+ }
940
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
941
+ current += ch + next;
942
+ inBlockComment = true;
943
+ i += 1;
944
+ continue;
945
+ }
946
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
947
+ inSingle = !inSingle;
948
+ current += ch;
949
+ continue;
950
+ }
951
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
952
+ inDouble = !inDouble;
953
+ current += ch;
954
+ continue;
955
+ }
956
+ if (!inSingle && !inDouble && ch === ";") {
957
+ if (current.trim()) {
958
+ parts.push(current.trim());
959
+ }
960
+ current = "";
961
+ continue;
962
+ }
963
+ current += ch;
964
+ }
965
+ if (current.trim()) {
966
+ parts.push(current.trim());
967
+ }
968
+ return parts;
969
+ }
970
+ async function createPrismaDbAdapter(fallbackClient) {
971
+ const prisma = await loadPrismaClient();
972
+ await ensureCompatibilityViews(prisma);
973
+ let closed = false;
974
+ const fallbackExecute = async (stmt, error) => {
975
+ if (!fallbackClient) {
976
+ if (error) throw error;
977
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
978
+ }
979
+ if (error) {
980
+ process.stderr.write(
981
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
982
+ `
983
+ );
984
+ }
985
+ return fallbackClient.execute(stmt);
986
+ };
987
+ const adapter = {
988
+ async execute(stmt) {
989
+ if (shouldBypassPostgres(stmt)) {
990
+ return fallbackExecute(stmt);
991
+ }
992
+ try {
993
+ return await executeOnPrisma(prisma, stmt);
994
+ } catch (error) {
995
+ if (shouldFallbackOnError(error)) {
996
+ return fallbackExecute(stmt, error);
997
+ }
998
+ throw error;
999
+ }
1000
+ },
1001
+ async batch(stmts, mode) {
1002
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1003
+ if (!fallbackClient) {
1004
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1005
+ }
1006
+ return fallbackClient.batch(stmts, mode);
1007
+ }
1008
+ try {
1009
+ if (prisma.$transaction) {
1010
+ return await prisma.$transaction(async (tx) => {
1011
+ const results2 = [];
1012
+ for (const stmt of stmts) {
1013
+ results2.push(await executeOnPrisma(tx, stmt));
1014
+ }
1015
+ return results2;
1016
+ });
1017
+ }
1018
+ const results = [];
1019
+ for (const stmt of stmts) {
1020
+ results.push(await executeOnPrisma(prisma, stmt));
1021
+ }
1022
+ return results;
1023
+ } catch (error) {
1024
+ if (fallbackClient && shouldFallbackOnError(error)) {
1025
+ process.stderr.write(
1026
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1027
+ `
1028
+ );
1029
+ return fallbackClient.batch(stmts, mode);
1030
+ }
1031
+ throw error;
1032
+ }
1033
+ },
1034
+ async migrate(stmts) {
1035
+ if (fallbackClient) {
1036
+ return fallbackClient.migrate(stmts);
1037
+ }
1038
+ return adapter.batch(stmts, "deferred");
1039
+ },
1040
+ async transaction(mode) {
1041
+ if (!fallbackClient) {
1042
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1043
+ }
1044
+ return fallbackClient.transaction(mode);
1045
+ },
1046
+ async executeMultiple(sql) {
1047
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1048
+ return fallbackClient.executeMultiple(sql);
1049
+ }
1050
+ for (const statement of splitSqlStatements(sql)) {
1051
+ await adapter.execute(statement);
1052
+ }
1053
+ },
1054
+ async sync() {
1055
+ if (fallbackClient) {
1056
+ return fallbackClient.sync();
1057
+ }
1058
+ return { frame_no: 0, frames_synced: 0 };
1059
+ },
1060
+ close() {
1061
+ closed = true;
1062
+ prismaClientPromise = null;
1063
+ compatibilityBootstrapPromise = null;
1064
+ void prisma.$disconnect?.();
1065
+ },
1066
+ get closed() {
1067
+ return closed;
1068
+ },
1069
+ get protocol() {
1070
+ return "prisma-postgres";
1071
+ }
1072
+ };
1073
+ return adapter;
1074
+ }
1075
+
1076
+ // src/lib/database.ts
1077
+ function _checkDaemonAliveSync() {
1078
+ return isDaemonAlive().alive;
1079
+ }
1080
+ var _debugDb = process.env.EXE_DEBUG === "1";
1081
+ function logCatchDebug(context, err) {
1082
+ if (_debugDb) {
1083
+ process.stderr.write(
1084
+ `[database] ${context}: ${err instanceof Error ? err.message : String(err)}
1085
+ `
1086
+ );
1087
+ }
1088
+ }
1089
+ function isAlreadyExistsSchemaError(err) {
1090
+ const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
1091
+ return msg.includes("duplicate column") || msg.includes("already exists");
1092
+ }
1093
+ function rethrowUnlessAlreadyExists(context, err) {
1094
+ if (isAlreadyExistsSchemaError(err)) {
1095
+ logCatchDebug(context, err);
1096
+ return;
1097
+ }
1098
+ process.stderr.write(
1099
+ `[database] ERROR: ${context} failed: ${err instanceof Error ? err.message : String(err)}
1100
+ `
1101
+ );
1102
+ throw err;
1103
+ }
1104
+ async function executeAddColumn(client, sql, context = "migration add column") {
1105
+ try {
1106
+ await client.execute(sql);
1107
+ return true;
1108
+ } catch (e) {
1109
+ rethrowUnlessAlreadyExists(context, e);
1110
+ return false;
1111
+ }
1112
+ }
1113
+ var _client = null;
1114
+ var _resilientClient = null;
1115
+ var _walCheckpointTimer = null;
1116
+ var _daemonClient = null;
1117
+ var _adapterClient = null;
1118
+ var _lockFd = null;
1119
+ var DB_LOCK_PATH = join(homedir(), ".exe-os", "db.lock");
1120
+ function acquireDbLock() {
1121
+ mkdirSync2(join(homedir(), ".exe-os"), { recursive: true });
1122
+ try {
1123
+ _lockFd = openSync(DB_LOCK_PATH, "wx");
1124
+ } catch (err) {
1125
+ if (err && typeof err === "object" && "code" in err && err.code === "EEXIST") {
1126
+ try {
1127
+ const lockStat = statSync(DB_LOCK_PATH);
1128
+ if (Date.now() - lockStat.mtimeMs > 6e4) {
1129
+ unlinkSync2(DB_LOCK_PATH);
1130
+ _lockFd = openSync(DB_LOCK_PATH, "wx");
1131
+ return;
1132
+ }
1133
+ } catch (e) {
1134
+ logCatchDebug("stale lock check", e);
1135
+ }
1136
+ process.stderr.write(
1137
+ "[database] WARN: Another process holds db.lock \u2014 waiting briefly then proceeding.\n"
1138
+ );
1139
+ return;
1140
+ }
1141
+ throw err;
1142
+ }
1143
+ }
1144
+ function releaseDbLock() {
1145
+ if (_lockFd !== null) {
1146
+ try {
1147
+ closeSync(_lockFd);
1148
+ } catch (e) {
1149
+ logCatchDebug("lock close", e);
1150
+ }
1151
+ _lockFd = null;
1152
+ }
1153
+ try {
1154
+ unlinkSync2(DB_LOCK_PATH);
1155
+ } catch (e) {
1156
+ logCatchDebug("lock unlink", e);
1157
+ }
1158
+ }
1159
+ var CORRUPTION_ERROR_RE = /malformed|disk image|sqlite_corrupt|database is corrupt|corrupt(ed)? (database|page|index)|btree/i;
1160
+ var NON_CORRUPTION_OPEN_ERROR_RE = /not a database|file is encrypted|is encrypted|encryption|cipher|hmac|locked|database is busy|\bbusy\b|unable to open|no such file|permission denied|readonly|read-only/i;
1161
+ async function _checkDbIntegrity(client) {
1162
+ try {
1163
+ const intCheck = await client.execute("PRAGMA quick_check");
1164
+ const result = String(intCheck.rows[0]?.[0] ?? "");
1165
+ if (result !== "ok") {
1166
+ process.stderr.write(
1167
+ `[database] CRITICAL: quick_check failed: ${result}. DB is corrupt.
1168
+ `
1169
+ );
1170
+ return true;
1171
+ }
1172
+ return false;
1173
+ } catch (e) {
1174
+ const msg = e instanceof Error ? e.message : String(e);
1175
+ if (CORRUPTION_ERROR_RE.test(msg) && !NON_CORRUPTION_OPEN_ERROR_RE.test(msg)) {
1176
+ process.stderr.write(
1177
+ `[database] CRITICAL: quick_check reported corruption (${msg}). Will attempt backup restore.
1178
+ `
1179
+ );
1180
+ return true;
1181
+ }
1182
+ process.stderr.write(
1183
+ `[database] CRITICAL: quick_check could not run (${msg}). Refusing to start without touching the database \u2014 likely a wrong SQLCipher key, locked/busy database, or open failure. Resolve the key/lock issue and retry; the data is intact.
1184
+ `
1185
+ );
1186
+ throw e;
1187
+ }
1188
+ }
1189
+ async function _attemptBackupRestore(config) {
1190
+ assertValidDbPath(config.dbPath, "database backup restore");
1191
+ const backups = listBackups();
1192
+ if (backups.length === 0) {
1193
+ process.stderr.write(
1194
+ `[database] CRITICAL: No backups found in ~/.exe-os/backups/ \u2014 cannot auto-restore.
1195
+ `
1196
+ );
1197
+ return null;
1198
+ }
1199
+ const timestamp = Date.now();
1200
+ const corruptPath = `${config.dbPath}.corrupt-${timestamp}`;
1201
+ try {
1202
+ renameSync2(config.dbPath, corruptPath);
1203
+ for (const suffix of ["-wal", "-shm"]) {
1204
+ const sidePath = config.dbPath + suffix;
1205
+ if (existsSync3(sidePath)) {
1206
+ try {
1207
+ renameSync2(sidePath, corruptPath + suffix);
1208
+ } catch {
1209
+ }
1210
+ }
1211
+ }
1212
+ process.stderr.write(
1213
+ `[database] Moved corrupt DB to ${corruptPath}
1214
+ `
1215
+ );
1216
+ } catch (moveErr) {
1217
+ process.stderr.write(
1218
+ `[database] ERROR: Failed to move corrupt DB: ${moveErr instanceof Error ? moveErr.message : String(moveErr)}
1219
+ `
1220
+ );
1221
+ return null;
1222
+ }
1223
+ for (const backup of backups) {
1224
+ process.stderr.write(
1225
+ `[database] Trying backup: ${backup.name} (${backup.date.toISOString()})
1226
+ `
1227
+ );
1228
+ try {
1229
+ copyFileSync(backup.path, config.dbPath);
1230
+ const backupWal = backup.path + "-wal";
1231
+ if (existsSync3(backupWal)) {
1232
+ copyFileSync(backupWal, config.dbPath + "-wal");
1233
+ }
1234
+ const opts = {
1235
+ url: `file:${config.dbPath}`
1236
+ };
1237
+ if (config.encryptionKey) {
1238
+ opts.encryptionKey = config.encryptionKey;
1239
+ }
1240
+ const testClient = createClient(opts);
1241
+ await testClient.execute("PRAGMA busy_timeout = 30000");
1242
+ await testClient.execute("PRAGMA journal_mode = WAL");
1243
+ const isCorrupt = await _checkDbIntegrity(testClient);
1244
+ if (isCorrupt) {
1245
+ testClient.close();
1246
+ try {
1247
+ unlinkSync2(config.dbPath);
1248
+ } catch {
1249
+ }
1250
+ try {
1251
+ unlinkSync2(config.dbPath + "-wal");
1252
+ } catch {
1253
+ }
1254
+ try {
1255
+ unlinkSync2(config.dbPath + "-shm");
1256
+ } catch {
1257
+ }
1258
+ process.stderr.write(
1259
+ `[database] Backup ${backup.name} is also corrupt, trying next...
1260
+ `
1261
+ );
1262
+ continue;
1263
+ }
1264
+ const corruptStat = statSync(corruptPath);
1265
+ const backupAge = Date.now() - backup.date.getTime();
1266
+ const hoursLost = Math.round(backupAge / (1e3 * 60 * 60) * 10) / 10;
1267
+ process.stderr.write(
1268
+ `[database] RESTORED from backup: ${backup.name}
1269
+ [database] Backup date: ${backup.date.toISOString()}
1270
+ [database] Corrupt DB date: ${new Date(corruptStat.mtimeMs).toISOString()}
1271
+ [database] Estimated data loss window: ~${hoursLost} hours
1272
+ [database] Corrupt DB preserved at: ${corruptPath}
1273
+ `
1274
+ );
1275
+ try {
1276
+ const { recordDbRestoreEvent } = await import("./db-restore-events-GNZS42YO.js");
1277
+ recordDbRestoreEvent({
1278
+ kind: "auto-restore",
1279
+ restoredFrom: backup.name,
1280
+ backupDate: backup.date.toISOString(),
1281
+ hoursLost,
1282
+ quarantinePath: corruptPath,
1283
+ detail: `DB corruption detected at startup; restored from ${backup.name} (~${hoursLost}h data-loss window)`
1284
+ });
1285
+ } catch {
1286
+ }
1287
+ try {
1288
+ const { pruneCorruptQuarantine } = await import("./db-backup-VUGFTPJ4.js");
1289
+ pruneCorruptQuarantine();
1290
+ } catch {
1291
+ }
1292
+ return { client: testClient };
1293
+ } catch (restoreErr) {
1294
+ process.stderr.write(
1295
+ `[database] Failed to restore from ${backup.name}: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}
1296
+ `
1297
+ );
1298
+ try {
1299
+ unlinkSync2(config.dbPath);
1300
+ } catch {
1301
+ }
1302
+ try {
1303
+ unlinkSync2(config.dbPath + "-wal");
1304
+ } catch {
1305
+ }
1306
+ try {
1307
+ unlinkSync2(config.dbPath + "-shm");
1308
+ } catch {
1309
+ }
1310
+ continue;
1311
+ }
1312
+ }
1313
+ process.stderr.write(
1314
+ `[database] CRITICAL: All ${backups.length} backups are corrupt or unreadable. Refusing to create an empty DB.
1315
+ [database] Corrupt DB preserved at: ${corruptPath}
1316
+ `
1317
+ );
1318
+ try {
1319
+ const { recordDbRestoreEvent } = await import("./db-restore-events-GNZS42YO.js");
1320
+ recordDbRestoreEvent({
1321
+ kind: "restore-failed",
1322
+ quarantinePath: corruptPath,
1323
+ detail: `DB corruption detected at startup; all ${backups.length} backups were also corrupt or unreadable`
1324
+ });
1325
+ } catch {
1326
+ }
1327
+ return null;
1328
+ }
1329
+ var WAL_ESCALATION_THRESHOLD_BYTES = 256 * 1024 * 1024;
1330
+ async function checkpointWalIfLarge(dbPath, client = getRawClient(), thresholdBytes = WAL_ESCALATION_THRESHOLD_BYTES) {
1331
+ const walPath = `${dbPath}-wal`;
1332
+ let walSize = 0;
1333
+ try {
1334
+ walSize = statSync(walPath).size;
1335
+ } catch {
1336
+ return "skipped";
1337
+ }
1338
+ if (walSize < thresholdBytes) return "skipped";
1339
+ await client.execute("PRAGMA wal_checkpoint(RESTART)");
1340
+ try {
1341
+ walSize = statSync(walPath).size;
1342
+ } catch {
1343
+ return "restart";
1344
+ }
1345
+ if (walSize < thresholdBytes) return "restart";
1346
+ await client.execute("PRAGMA wal_checkpoint(TRUNCATE)");
1347
+ return "truncate";
1348
+ }
1349
+ var initTurso = initDatabase;
1350
+ async function initDatabase(config) {
1351
+ assertValidDbPath(config?.dbPath, "initDatabase");
1352
+ acquireDbLock();
1353
+ if (existsSync3(config.dbPath)) {
1354
+ const dbStat = statSync(config.dbPath);
1355
+ if (dbStat.size === 0) {
1356
+ const walPath = config.dbPath + "-wal";
1357
+ if (existsSync3(walPath) && statSync(walPath).size > 0) {
1358
+ const backupPath = config.dbPath + ".zeroed-" + Date.now();
1359
+ copyFileSync(config.dbPath, backupPath);
1360
+ unlinkSync2(config.dbPath);
1361
+ process.stderr.write(
1362
+ `[database] CRITICAL: DB was 0 bytes. Moved to ${backupPath}, attempting WAL recovery.
1363
+ `
1364
+ );
1365
+ try {
1366
+ const { recordDbRestoreEvent } = await import("./db-restore-events-GNZS42YO.js");
1367
+ recordDbRestoreEvent({
1368
+ kind: "wal-recovery",
1369
+ quarantinePath: backupPath,
1370
+ detail: "DB was 0 bytes at startup; reconstructing from WAL"
1371
+ });
1372
+ } catch {
1373
+ }
1374
+ } else {
1375
+ process.stderr.write(
1376
+ `[database] CRITICAL: DB is 0 bytes and no WAL available for recovery. Data may be lost. Check backups at ${config.dbPath}.bak
1377
+ `
1378
+ );
1379
+ try {
1380
+ const { recordDbRestoreEvent } = await import("./db-restore-events-GNZS42YO.js");
1381
+ recordDbRestoreEvent({
1382
+ kind: "zeroed-no-wal",
1383
+ detail: "DB was 0 bytes at startup with no WAL available \u2014 data may be lost"
1384
+ });
1385
+ } catch {
1386
+ }
1387
+ }
1388
+ }
1389
+ }
1390
+ if (_walCheckpointTimer) {
1391
+ clearInterval(_walCheckpointTimer);
1392
+ _walCheckpointTimer = null;
1393
+ }
1394
+ if (_daemonClient) {
1395
+ _daemonClient.close();
1396
+ _daemonClient = null;
1397
+ }
1398
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1399
+ _adapterClient.close();
1400
+ }
1401
+ _adapterClient = null;
1402
+ if (_client) {
1403
+ _client.close();
1404
+ _client = null;
1405
+ _resilientClient = null;
1406
+ }
1407
+ const opts = {
1408
+ url: `file:${config.dbPath}`
1409
+ };
1410
+ if (config.encryptionKey) {
1411
+ opts.encryptionKey = config.encryptionKey;
1412
+ }
1413
+ _client = createClient(opts);
1414
+ _resilientClient = wrapWithRetry(_client);
1415
+ _adapterClient = _resilientClient;
1416
+ await _client.execute("PRAGMA busy_timeout = 30000");
1417
+ await _client.execute("PRAGMA journal_mode = WAL");
1418
+ await _client.execute("PRAGMA synchronous = NORMAL");
1419
+ await _client.execute("PRAGMA cache_size = -65536");
1420
+ const _totalMem = totalmem();
1421
+ const _mmapSize = Math.min(536870912, Math.floor(_totalMem / 4));
1422
+ await _client.execute(`PRAGMA mmap_size = ${_mmapSize}`);
1423
+ await _client.execute("PRAGMA temp_store = MEMORY");
1424
+ if (process.env.EXE_IS_DAEMON === "1") {
1425
+ await _client.execute("PRAGMA wal_autocheckpoint = 0");
1426
+ }
1427
+ let dbIsCorrupt = false;
1428
+ try {
1429
+ dbIsCorrupt = await _checkDbIntegrity(_client);
1430
+ } catch (e) {
1431
+ releaseDbLock();
1432
+ throw e;
1433
+ }
1434
+ if (dbIsCorrupt) {
1435
+ const restored = await _attemptBackupRestore(config);
1436
+ if (restored) {
1437
+ _client = restored.client;
1438
+ _resilientClient = wrapWithRetry(_client);
1439
+ _adapterClient = _resilientClient;
1440
+ } else {
1441
+ releaseDbLock();
1442
+ throw new Error("Database quick_check reported corruption and no valid backup could be restored; refusing to create an empty DB.");
1443
+ }
1444
+ }
1445
+ if (_walCheckpointTimer) clearInterval(_walCheckpointTimer);
1446
+ _walCheckpointTimer = setInterval(() => {
1447
+ _client?.execute("PRAGMA wal_checkpoint(PASSIVE)").then(() => {
1448
+ if (process.env.EXE_IS_DAEMON === "1" && _client) {
1449
+ return checkpointWalIfLarge(config.dbPath, _client).then(() => void 0);
1450
+ }
1451
+ return void 0;
1452
+ }).catch(() => {
1453
+ });
1454
+ }, 3e4);
1455
+ _walCheckpointTimer.unref();
1456
+ if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") {
1457
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1458
+ }
1459
+ try {
1460
+ chmodSync(config.dbPath, 384);
1461
+ for (const suffix of ["-wal", "-shm"]) {
1462
+ try {
1463
+ chmodSync(config.dbPath + suffix, 384);
1464
+ } catch (chmodErr) {
1465
+ process.stderr.write(`[database] chmod ${suffix} failed: ${chmodErr instanceof Error ? chmodErr.message : String(chmodErr)}
1466
+ `);
1467
+ }
1468
+ }
1469
+ } catch (chmodErr) {
1470
+ process.stderr.write(`[database] chmod db failed: ${chmodErr instanceof Error ? chmodErr.message : String(chmodErr)}
1471
+ `);
1472
+ }
1473
+ releaseDbLock();
1474
+ }
1475
+ function isInitialized() {
1476
+ return _adapterClient !== null || _client !== null;
1477
+ }
1478
+ function setExternalClient(client) {
1479
+ _adapterClient = client;
1480
+ }
1481
+ var SOFT_DELETE_RETENTION_DAYS = 7;
1482
+ function getClient() {
1483
+ if (!_adapterClient) {
1484
+ throw new Error("Database client not initialized. Call initDatabase() first.");
1485
+ }
1486
+ if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") {
1487
+ return _adapterClient;
1488
+ }
1489
+ if (process.env.EXE_IS_DAEMON === "1") {
1490
+ return _resilientClient;
1491
+ }
1492
+ if (_daemonClient && (_daemonClient._isDaemonActive() || process.env.EXE_MCP_MODE === "1")) {
1493
+ return _daemonClient;
1494
+ }
1495
+ if (!_resilientClient) {
1496
+ return _adapterClient;
1497
+ }
1498
+ if (process.env.EXE_DB_READONLY === "1" || process.env.EXE_DB_DIRECT === "1") {
1499
+ return _resilientClient;
1500
+ }
1501
+ if (!process.env.EXE_MCP_MODE) {
1502
+ let daemonAlive = false;
1503
+ try {
1504
+ daemonAlive = _checkDaemonAliveSync();
1505
+ } catch {
1506
+ }
1507
+ if (daemonAlive) {
1508
+ process.stderr.write(
1509
+ "[database] INFO: daemon running \u2014 CLI taking direct SQLite access (single-writer CLI mode).\n"
1510
+ );
1511
+ } else {
1512
+ process.stderr.write(
1513
+ "[database] WARN: daemon unavailable \u2014 using direct SQLite (single-writer CLI mode).\n"
1514
+ );
1515
+ }
1516
+ return _resilientClient;
1517
+ }
1518
+ process.stderr.write(
1519
+ "[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"
1520
+ );
1521
+ throw new Error(
1522
+ "Daemon not running. Direct SQLite writes from MCP processes are blocked to prevent FTS5 corruption. Restart daemon to restore service."
1523
+ );
1524
+ }
1525
+ async function initDaemonClient() {
1526
+ if (process.env.DATABASE_URL && process.env.EXE_USE_POSTGRES === "1") return;
1527
+ if (process.env.EXE_IS_DAEMON === "1") return;
1528
+ if (process.env.EXE_DB_READONLY === "1" || process.env.EXE_DB_DIRECT === "1" || process.env.EXE_SKIP_DAEMON === "1") return;
1529
+ if (process.env.VITEST) return;
1530
+ if (!_resilientClient) return;
1531
+ if (_daemonClient) return;
1532
+ try {
1533
+ const { initDaemonDbClient } = await import("./lib/db-daemon-client.js");
1534
+ _daemonClient = await initDaemonDbClient(_resilientClient);
1535
+ } catch (err) {
1536
+ process.stderr.write(
1537
+ `[database] Daemon client init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
1538
+ `
1539
+ );
1540
+ }
1541
+ }
1542
+ function getRawClient() {
1543
+ if (!_client) {
1544
+ throw new Error("Database client not initialized. Call initDatabase() first.");
1545
+ }
1546
+ return _client;
1547
+ }
1548
+ async function ensureSchema() {
1549
+ const client = getRawClient();
1550
+ await client.execute("PRAGMA journal_mode = WAL");
1551
+ await client.execute("PRAGMA busy_timeout = 30000");
1552
+ await client.execute("PRAGMA synchronous = NORMAL");
1553
+ await client.execute("PRAGMA cache_size = -65536");
1554
+ await client.execute("PRAGMA mmap_size = 536870912");
1555
+ await client.execute("PRAGMA temp_store = MEMORY");
1556
+ if (process.env.EXE_IS_DAEMON === "1") {
1557
+ await client.execute("PRAGMA wal_autocheckpoint = 0");
1558
+ }
1559
+ try {
1560
+ await client.execute("PRAGMA libsql_vector_search_ef = 128");
1561
+ } catch (e) {
1562
+ logCatchDebug("migration", e);
1563
+ }
1564
+ await client.executeMultiple(`
1565
+ CREATE TABLE IF NOT EXISTS memories (
1566
+ id TEXT PRIMARY KEY,
1567
+ agent_id TEXT NOT NULL,
1568
+ agent_role TEXT NOT NULL,
1569
+ session_id TEXT NOT NULL,
1570
+ timestamp TEXT NOT NULL,
1571
+ tool_name TEXT NOT NULL,
1572
+ project_name TEXT NOT NULL,
1573
+ has_error INTEGER NOT NULL DEFAULT 0,
1574
+ raw_text TEXT NOT NULL,
1575
+ vector F32_BLOB(1024),
1576
+ version INTEGER NOT NULL DEFAULT 0
1577
+ );
1578
+
1579
+ CREATE INDEX IF NOT EXISTS idx_memories_agent
1580
+ ON memories(agent_id);
1581
+
1582
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp
1583
+ ON memories(timestamp);
1584
+
1585
+ CREATE INDEX IF NOT EXISTS idx_memories_session
1586
+ ON memories(session_id);
1587
+
1588
+ CREATE INDEX IF NOT EXISTS idx_memories_project
1589
+ ON memories(project_name);
1590
+
1591
+ CREATE INDEX IF NOT EXISTS idx_memories_tool
1592
+ ON memories(tool_name);
1593
+
1594
+ CREATE INDEX IF NOT EXISTS idx_memories_version
1595
+ ON memories(version);
1596
+
1597
+ CREATE INDEX IF NOT EXISTS idx_memories_agent_project
1598
+ ON memories(agent_id, project_name);
1599
+ `);
1600
+ try {
1601
+ await client.execute("ALTER TABLE memories ADD COLUMN keywords TEXT DEFAULT ''");
1602
+ } catch (e) {
1603
+ rethrowUnlessAlreadyExists("migration add memories.keywords", e);
1604
+ }
1605
+ try {
1606
+ await client.execute("ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0");
1607
+ } catch (e) {
1608
+ rethrowUnlessAlreadyExists("migration add memories.graph_extracted", e);
1609
+ }
1610
+ await executeAddColumn(client, "ALTER TABLE memories ADD COLUMN embedding_model TEXT", "migration add memories.embedding_model");
1611
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_embedding_model ON memories(embedding_model)");
1612
+ let needsFtsMigration = false;
1613
+ try {
1614
+ const ftsCheck = await client.execute(
1615
+ "SELECT sql FROM sqlite_master WHERE type='table' AND name='memories_fts'"
1616
+ );
1617
+ const ftsSql = ftsCheck.rows[0]?.sql ?? "";
1618
+ needsFtsMigration = ftsSql.length > 0 && !ftsSql.includes("keywords");
1619
+ if (ftsCheck.rows.length === 0) {
1620
+ needsFtsMigration = true;
1621
+ }
1622
+ } catch {
1623
+ needsFtsMigration = true;
1624
+ }
1625
+ if (needsFtsMigration) {
1626
+ try {
1627
+ await client.execute("DROP TRIGGER IF EXISTS memories_fts_ai");
1628
+ await client.execute("DROP TRIGGER IF EXISTS memories_fts_ad");
1629
+ await client.execute("DROP TRIGGER IF EXISTS memories_fts_au");
1630
+ await client.execute("DROP TRIGGER IF EXISTS memories_fts_soft_delete");
1631
+ await client.execute("DROP TABLE IF EXISTS memories_fts");
1632
+ } catch (e) {
1633
+ logCatchDebug("fts-migration-drop", e);
1634
+ }
1635
+ }
1636
+ await client.executeMultiple(`
1637
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
1638
+ raw_text,
1639
+ keywords,
1640
+ content='memories',
1641
+ content_rowid='rowid'
1642
+ );
1643
+
1644
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
1645
+ INSERT INTO memories_fts(rowid, raw_text, keywords) VALUES (new.rowid, new.raw_text, COALESCE(new.keywords, ''));
1646
+ END;
1647
+
1648
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories
1649
+ WHEN old.status IS NULL OR old.status != 'deleted' BEGIN
1650
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text, keywords) VALUES('delete', old.rowid, old.raw_text, COALESCE(old.keywords, ''));
1651
+ END;
1652
+
1653
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE OF raw_text, keywords ON memories
1654
+ WHEN new.status IS NULL OR new.status != 'deleted' BEGIN
1655
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text, keywords) VALUES('delete', old.rowid, old.raw_text, COALESCE(old.keywords, ''));
1656
+ INSERT INTO memories_fts(rowid, raw_text, keywords) VALUES (new.rowid, new.raw_text, COALESCE(new.keywords, ''));
1657
+ END;
1658
+
1659
+ -- Soft-delete trigger: remove from FTS when status changes to 'deleted'
1660
+ CREATE TRIGGER IF NOT EXISTS memories_fts_soft_delete AFTER UPDATE ON memories
1661
+ WHEN new.status = 'deleted' AND (old.status IS NULL OR old.status != 'deleted') BEGIN
1662
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text, keywords) VALUES('delete', old.rowid, old.raw_text, COALESCE(old.keywords, ''));
1663
+ END;
1664
+ `);
1665
+ if (needsFtsMigration) {
1666
+ try {
1667
+ await client.execute("INSERT INTO memories_fts(memories_fts) VALUES('rebuild')");
1668
+ } catch (e) {
1669
+ logCatchDebug("fts-rebuild", e);
1670
+ }
1671
+ }
1672
+ try {
1673
+ await client.execute("SELECT COUNT(*) FROM memories_fts LIMIT 1");
1674
+ } catch (ftsErr) {
1675
+ process.stderr.write(
1676
+ `[database] WARN: memories_fts corrupted (${ftsErr instanceof Error ? ftsErr.message : String(ftsErr)}) \u2014 rebuilding FTS index.
1677
+ `
1678
+ );
1679
+ try {
1680
+ await client.execute("INSERT INTO memories_fts(memories_fts) VALUES('rebuild')");
1681
+ process.stderr.write("[database] FTS index rebuilt successfully.\n");
1682
+ } catch (rebuildErr) {
1683
+ process.stderr.write(
1684
+ `[database] ERROR: FTS rebuild failed: ${rebuildErr instanceof Error ? rebuildErr.message : String(rebuildErr)}
1685
+ `
1686
+ );
1687
+ }
1688
+ }
1689
+ await client.executeMultiple(`
1690
+ CREATE TABLE IF NOT EXISTS sync_meta (
1691
+ key TEXT PRIMARY KEY,
1692
+ value TEXT NOT NULL
1693
+ );
1694
+ `);
1695
+ await client.executeMultiple(`
1696
+ CREATE TABLE IF NOT EXISTS tasks (
1697
+ id TEXT PRIMARY KEY,
1698
+ title TEXT NOT NULL,
1699
+ assigned_to TEXT NOT NULL,
1700
+ assigned_by TEXT NOT NULL,
1701
+ project_name TEXT NOT NULL,
1702
+ priority TEXT NOT NULL DEFAULT 'p1',
1703
+ status TEXT NOT NULL DEFAULT 'open',
1704
+ task_file TEXT,
1705
+ created_at TEXT NOT NULL,
1706
+ updated_at TEXT NOT NULL
1707
+ );
1708
+
1709
+ CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
1710
+ ON tasks(assigned_to, status);
1711
+ `);
1712
+ await client.executeMultiple(`
1713
+ CREATE TABLE IF NOT EXISTS orchestration_events (
1714
+ id TEXT PRIMARY KEY,
1715
+ ts TEXT NOT NULL,
1716
+ event_type TEXT NOT NULL,
1717
+ source TEXT NOT NULL,
1718
+ severity TEXT NOT NULL DEFAULT 'info',
1719
+ task_id TEXT,
1720
+ agent_id TEXT,
1721
+ reviewer TEXT,
1722
+ session_scope TEXT,
1723
+ project_name TEXT,
1724
+ instance_id TEXT,
1725
+ tmux_session TEXT,
1726
+ runtime TEXT,
1727
+ correlation_id TEXT,
1728
+ attempt INTEGER,
1729
+ duration_ms INTEGER,
1730
+ result TEXT,
1731
+ error_code TEXT,
1732
+ payload_json TEXT,
1733
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
1734
+ );
1735
+
1736
+ CREATE INDEX IF NOT EXISTS idx_orch_events_ts
1737
+ ON orchestration_events(ts);
1738
+
1739
+ CREATE INDEX IF NOT EXISTS idx_orch_events_task
1740
+ ON orchestration_events(task_id, ts);
1741
+
1742
+ CREATE INDEX IF NOT EXISTS idx_orch_events_agent
1743
+ ON orchestration_events(agent_id, session_scope, ts);
1744
+
1745
+ CREATE INDEX IF NOT EXISTS idx_orch_events_type
1746
+ ON orchestration_events(event_type, ts);
1747
+ `);
1748
+ await client.executeMultiple(`
1749
+ CREATE TABLE IF NOT EXISTS behaviors (
1750
+ id TEXT PRIMARY KEY,
1751
+ agent_id TEXT NOT NULL,
1752
+ project_name TEXT,
1753
+ domain TEXT,
1754
+ content TEXT NOT NULL,
1755
+ active INTEGER NOT NULL DEFAULT 1,
1756
+ created_at TEXT NOT NULL,
1757
+ updated_at TEXT NOT NULL
1758
+ );
1759
+
1760
+ CREATE INDEX IF NOT EXISTS idx_behaviors_agent
1761
+ ON behaviors(agent_id, active);
1762
+ `);
1763
+ try {
1764
+ const coordinatorName = getCoordinatorName();
1765
+ const existing = await client.execute({
1766
+ sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
1767
+ args: [coordinatorName]
1768
+ });
1769
+ if (Number(existing.rows[0]?.cnt) === 0) {
1770
+ const seededAt = "2026-03-25T00:00:00Z";
1771
+ for (const [domain, content] of [
1772
+ ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
1773
+ ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
1774
+ ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
1775
+ ]) {
1776
+ await client.execute({
1777
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
1778
+ VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
1779
+ args: [coordinatorName, domain, content, seededAt, seededAt]
1780
+ });
1781
+ }
1782
+ }
1783
+ } catch (seedErr) {
1784
+ logCatchDebug("behavior seed", seedErr);
1785
+ }
1786
+ await executeAddColumn(client, `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`);
1787
+ await executeAddColumn(client, `ALTER TABLE behaviors ADD COLUMN vector F32_BLOB(${EMBEDDING_DIM})`);
1788
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
1789
+ await executeAddColumn(client, `ALTER TABLE behaviors ADD COLUMN ${col}`);
1790
+ }
1791
+ try {
1792
+ await client.execute(
1793
+ `CREATE TRIGGER IF NOT EXISTS behaviors_reject_ghost_insert
1794
+ BEFORE INSERT ON behaviors
1795
+ WHEN new.id IS NULL
1796
+ OR trim(new.id) = ''
1797
+ OR trim(coalesce(new.content, '')) = ''
1798
+ OR (new.content = '(empty)' AND coalesce(new.agent_id, '') = 'unknown')
1799
+ BEGIN
1800
+ SELECT RAISE(ABORT, 'ghost behavior rejected: null/blank id or empty content (bug 26688a14)');
1801
+ END`
1802
+ );
1803
+ } catch (e) {
1804
+ logCatchDebug("behaviors ghost-guard migration", e);
1805
+ }
1806
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`);
1807
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`);
1808
+ try {
1809
+ await client.execute({
1810
+ sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
1811
+ ON tasks(parent_task_id)
1812
+ WHERE parent_task_id IS NOT NULL`,
1813
+ args: []
1814
+ });
1815
+ } catch (e) {
1816
+ logCatchDebug("migration", e);
1817
+ }
1818
+ try {
1819
+ await client.execute({
1820
+ sql: `UPDATE tasks SET status = 'closed' WHERE status IN ('completed', 'done') AND result IS NOT NULL`,
1821
+ args: []
1822
+ });
1823
+ await client.execute({
1824
+ sql: `UPDATE tasks SET status = 'needs_review' WHERE status IN ('completed', 'done') AND result IS NULL`,
1825
+ args: []
1826
+ });
1827
+ } catch (e) {
1828
+ logCatchDebug("migration", e);
1829
+ }
1830
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN reviewer TEXT`);
1831
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN context TEXT`);
1832
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN result TEXT`);
1833
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`);
1834
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`);
1835
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`);
1836
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`);
1837
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN session_scope TEXT`);
1838
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN instance_id TEXT`);
1839
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN task_id TEXT`);
1840
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`);
1841
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN author_device_id TEXT`);
1842
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`);
1843
+ await client.executeMultiple(`
1844
+ CREATE TABLE IF NOT EXISTS consolidations (
1845
+ id TEXT PRIMARY KEY,
1846
+ consolidated_memory_id TEXT NOT NULL,
1847
+ source_memory_id TEXT NOT NULL,
1848
+ created_at TEXT NOT NULL
1849
+ );
1850
+
1851
+ CREATE INDEX IF NOT EXISTS idx_consolidations_source
1852
+ ON consolidations(source_memory_id);
1853
+
1854
+ CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
1855
+ ON consolidations(consolidated_memory_id);
1856
+ `);
1857
+ await client.executeMultiple(`
1858
+ CREATE TABLE IF NOT EXISTS reminders (
1859
+ id TEXT PRIMARY KEY,
1860
+ text TEXT NOT NULL,
1861
+ created_at TEXT NOT NULL,
1862
+ due_date TEXT,
1863
+ completed_at TEXT
1864
+ );
1865
+ `);
1866
+ await client.executeMultiple(`
1867
+ CREATE TABLE IF NOT EXISTS notifications (
1868
+ id TEXT PRIMARY KEY,
1869
+ agent_id TEXT NOT NULL,
1870
+ agent_role TEXT NOT NULL,
1871
+ event TEXT NOT NULL,
1872
+ project TEXT NOT NULL,
1873
+ summary TEXT NOT NULL,
1874
+ task_file TEXT,
1875
+ session_scope TEXT,
1876
+ read INTEGER NOT NULL DEFAULT 0,
1877
+ created_at TEXT NOT NULL
1878
+ );
1879
+
1880
+ CREATE INDEX IF NOT EXISTS idx_notifications_read
1881
+ ON notifications(read);
1882
+
1883
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent
1884
+ ON notifications(agent_id, session_scope);
1885
+
1886
+ CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1887
+ ON notifications(task_file);
1888
+ `);
1889
+ await client.executeMultiple(`
1890
+ CREATE TABLE IF NOT EXISTS schedules (
1891
+ id TEXT PRIMARY KEY,
1892
+ cron TEXT NOT NULL,
1893
+ description TEXT NOT NULL,
1894
+ job_type TEXT NOT NULL DEFAULT 'report',
1895
+ prompt TEXT,
1896
+ assigned_to TEXT,
1897
+ project_name TEXT,
1898
+ active INTEGER NOT NULL DEFAULT 1,
1899
+ use_crontab INTEGER NOT NULL DEFAULT 0,
1900
+ created_at TEXT NOT NULL
1901
+ );
1902
+ `);
1903
+ await client.executeMultiple(`
1904
+ CREATE TABLE IF NOT EXISTS device_registry (
1905
+ device_id TEXT PRIMARY KEY,
1906
+ friendly_name TEXT NOT NULL,
1907
+ hostname TEXT NOT NULL,
1908
+ projects TEXT NOT NULL DEFAULT '[]',
1909
+ agents TEXT NOT NULL DEFAULT '[]',
1910
+ connected INTEGER DEFAULT 0,
1911
+ last_seen TEXT NOT NULL
1912
+ );
1913
+ `);
1914
+ await client.executeMultiple(`
1915
+ CREATE TABLE IF NOT EXISTS messages (
1916
+ id TEXT PRIMARY KEY,
1917
+ from_agent TEXT NOT NULL,
1918
+ from_device TEXT NOT NULL DEFAULT 'local',
1919
+ target_agent TEXT NOT NULL,
1920
+ target_project TEXT,
1921
+ target_device TEXT NOT NULL DEFAULT 'local',
1922
+ session_scope TEXT,
1923
+ content TEXT NOT NULL,
1924
+ priority TEXT DEFAULT 'normal',
1925
+ status TEXT DEFAULT 'pending',
1926
+ server_seq INTEGER,
1927
+ retry_count INTEGER DEFAULT 0,
1928
+ created_at TEXT NOT NULL,
1929
+ delivered_at TEXT,
1930
+ processed_at TEXT,
1931
+ failed_at TEXT,
1932
+ failure_reason TEXT
1933
+ );
1934
+
1935
+ CREATE INDEX IF NOT EXISTS idx_messages_target
1936
+ ON messages(target_agent, session_scope, status);
1937
+
1938
+ CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1939
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1940
+ `);
1941
+ await executeAddColumn(client, `ALTER TABLE notifications ADD COLUMN session_scope TEXT`);
1942
+ await executeAddColumn(client, `ALTER TABLE messages ADD COLUMN session_scope TEXT`);
1943
+ await client.executeMultiple(`
1944
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1945
+ ON notifications(agent_id, session_scope, read, created_at);
1946
+
1947
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1948
+ ON messages(target_agent, session_scope, status, created_at);
1949
+ `);
1950
+ try {
1951
+ await client.execute({
1952
+ sql: `UPDATE memories SET project_name = 'exe-create' WHERE project_name = 'web'`,
1953
+ args: []
1954
+ });
1955
+ await client.execute({
1956
+ sql: `UPDATE memories SET project_name = 'exe-os' WHERE project_name = 'worker'`,
1957
+ args: []
1958
+ });
1959
+ await client.execute({
1960
+ sql: `UPDATE tasks SET project_name = 'exe-create' WHERE project_name = 'web'`,
1961
+ args: []
1962
+ });
1963
+ await client.execute({
1964
+ sql: `UPDATE tasks SET project_name = 'exe-os' WHERE project_name = 'worker'`,
1965
+ args: []
1966
+ });
1967
+ } catch (e) {
1968
+ logCatchDebug("migration", e);
1969
+ }
1970
+ await client.executeMultiple(`
1971
+ CREATE TABLE IF NOT EXISTS trajectories (
1972
+ id TEXT PRIMARY KEY,
1973
+ task_id TEXT NOT NULL,
1974
+ agent_id TEXT NOT NULL,
1975
+ project_name TEXT NOT NULL,
1976
+ task_title TEXT NOT NULL,
1977
+ signature TEXT NOT NULL,
1978
+ signature_hash TEXT NOT NULL,
1979
+ tool_count INTEGER NOT NULL,
1980
+ skill_id TEXT,
1981
+ created_at TEXT NOT NULL
1982
+ );
1983
+
1984
+ CREATE INDEX IF NOT EXISTS idx_trajectories_hash
1985
+ ON trajectories(signature_hash);
1986
+
1987
+ CREATE INDEX IF NOT EXISTS idx_trajectories_agent
1988
+ ON trajectories(agent_id);
1989
+ `);
1990
+ await executeAddColumn(client, "ALTER TABLE trajectories ADD COLUMN skill_id TEXT");
1991
+ await client.executeMultiple(`
1992
+ CREATE TABLE IF NOT EXISTS consolidations (
1993
+ id TEXT PRIMARY KEY,
1994
+ consolidated_memory_id TEXT NOT NULL,
1995
+ source_memory_id TEXT NOT NULL,
1996
+ created_at TEXT NOT NULL
1997
+ );
1998
+
1999
+ CREATE INDEX IF NOT EXISTS idx_consolidations_source
2000
+ ON consolidations(source_memory_id);
2001
+ `);
2002
+ await client.executeMultiple(`
2003
+ CREATE TABLE IF NOT EXISTS audit_trail (
2004
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2005
+ timestamp TEXT NOT NULL,
2006
+ session_id TEXT NOT NULL,
2007
+ agent_id TEXT NOT NULL,
2008
+ tool TEXT NOT NULL,
2009
+ input TEXT,
2010
+ decision TEXT NOT NULL,
2011
+ reason TEXT,
2012
+ is_customer_facing INTEGER NOT NULL DEFAULT 0
2013
+ );
2014
+
2015
+ CREATE INDEX IF NOT EXISTS idx_audit_trail_agent
2016
+ ON audit_trail(agent_id, timestamp);
2017
+
2018
+ CREATE INDEX IF NOT EXISTS idx_audit_trail_session
2019
+ ON audit_trail(session_id);
2020
+ `);
2021
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`);
2022
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5`);
2023
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'`);
2024
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN deleted_at TEXT`);
2025
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7`);
2026
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN last_accessed TEXT`);
2027
+ try {
2028
+ await client.execute({
2029
+ sql: `UPDATE memories SET last_accessed = timestamp WHERE last_accessed IS NULL`,
2030
+ args: []
2031
+ });
2032
+ } catch (e) {
2033
+ logCatchDebug("migration", e);
2034
+ }
2035
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0`);
2036
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0`);
2037
+ for (const col of [
2038
+ "ALTER TABLE memories ADD COLUMN content_hash TEXT",
2039
+ "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT"
2040
+ ]) {
2041
+ await executeAddColumn(client, col);
2042
+ }
2043
+ try {
2044
+ await client.execute(
2045
+ `CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
2046
+ );
2047
+ } catch (e) {
2048
+ logCatchDebug("migration", e);
2049
+ }
2050
+ try {
2051
+ await client.execute(
2052
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2053
+ ON memories(content_hash, agent_id, project_name, memory_type)
2054
+ WHERE content_hash IS NOT NULL`
2055
+ );
2056
+ } catch (e) {
2057
+ logCatchDebug("migration", e);
2058
+ }
2059
+ try {
2060
+ await client.execute(
2061
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_unique_scoped_hash
2062
+ ON memories(content_hash, agent_id, project_name, memory_type)
2063
+ WHERE content_hash IS NOT NULL
2064
+ AND COALESCE(status, 'active') != 'deleted'`
2065
+ );
2066
+ } catch (e) {
2067
+ if (e instanceof Error && e.message.includes("UNIQUE constraint")) {
2068
+ process.stderr.write("[migration] Cleaning up existing duplicate memories before creating unique index...\n");
2069
+ try {
2070
+ await client.execute(`
2071
+ UPDATE memories SET status = 'deleted', outcome = 'dedup-migration'
2072
+ WHERE id IN (
2073
+ SELECT m.id FROM memories m
2074
+ JOIN (
2075
+ SELECT content_hash, agent_id, project_name, memory_type, MAX(timestamp) as max_ts
2076
+ FROM memories
2077
+ WHERE content_hash IS NOT NULL AND COALESCE(status, 'active') != 'deleted'
2078
+ GROUP BY content_hash, agent_id, project_name, memory_type
2079
+ HAVING COUNT(*) > 1
2080
+ ) d ON m.content_hash = d.content_hash
2081
+ AND m.agent_id = d.agent_id
2082
+ AND m.project_name = d.project_name
2083
+ AND COALESCE(m.memory_type, 'raw') = COALESCE(d.memory_type, 'raw')
2084
+ AND m.timestamp < d.max_ts
2085
+ AND COALESCE(m.status, 'active') != 'deleted'
2086
+ )
2087
+ `);
2088
+ await client.execute(
2089
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_unique_scoped_hash
2090
+ ON memories(content_hash, agent_id, project_name, memory_type)
2091
+ WHERE content_hash IS NOT NULL
2092
+ AND COALESCE(status, 'active') != 'deleted'`
2093
+ );
2094
+ process.stderr.write("[migration] Duplicate cleanup + unique index created successfully\n");
2095
+ } catch (retryErr) {
2096
+ logCatchDebug("migration-dedup-retry", retryErr);
2097
+ }
2098
+ } else {
2099
+ logCatchDebug("migration", e);
2100
+ }
2101
+ }
2102
+ await client.executeMultiple(`
2103
+ CREATE TABLE IF NOT EXISTS entities (
2104
+ id TEXT PRIMARY KEY,
2105
+ name TEXT NOT NULL,
2106
+ type TEXT NOT NULL,
2107
+ first_seen TEXT NOT NULL,
2108
+ last_seen TEXT NOT NULL,
2109
+ properties TEXT DEFAULT '{}',
2110
+ UNIQUE(name, type)
2111
+ );
2112
+
2113
+ CREATE TABLE IF NOT EXISTS relationships (
2114
+ id TEXT PRIMARY KEY,
2115
+ source_entity_id TEXT NOT NULL,
2116
+ target_entity_id TEXT NOT NULL,
2117
+ type TEXT NOT NULL,
2118
+ weight REAL DEFAULT 1.0,
2119
+ timestamp TEXT NOT NULL,
2120
+ properties TEXT DEFAULT '{}',
2121
+ UNIQUE(source_entity_id, target_entity_id, type)
2122
+ );
2123
+
2124
+ CREATE TABLE IF NOT EXISTS entity_memories (
2125
+ entity_id TEXT NOT NULL,
2126
+ memory_id TEXT NOT NULL,
2127
+ PRIMARY KEY (entity_id, memory_id)
2128
+ );
2129
+
2130
+ CREATE TABLE IF NOT EXISTS relationship_memories (
2131
+ relationship_id TEXT NOT NULL,
2132
+ memory_id TEXT NOT NULL,
2133
+ PRIMARY KEY (relationship_id, memory_id)
2134
+ );
2135
+
2136
+ CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
2137
+ CREATE INDEX IF NOT EXISTS idx_entities_name_nocase ON entities(name COLLATE NOCASE);
2138
+ CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
2139
+ CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
2140
+ CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
2141
+ CREATE INDEX IF NOT EXISTS idx_entity_memories_memory ON entity_memories(memory_id);
2142
+ CREATE INDEX IF NOT EXISTS idx_relationship_memories_memory ON relationship_memories(memory_id);
2143
+
2144
+ CREATE TABLE IF NOT EXISTS hyperedges (
2145
+ id TEXT PRIMARY KEY,
2146
+ label TEXT NOT NULL,
2147
+ relation TEXT NOT NULL,
2148
+ confidence REAL DEFAULT 1.0,
2149
+ timestamp TEXT NOT NULL
2150
+ );
2151
+
2152
+ CREATE TABLE IF NOT EXISTS hyperedge_nodes (
2153
+ hyperedge_id TEXT NOT NULL,
2154
+ entity_id TEXT NOT NULL,
2155
+ PRIMARY KEY (hyperedge_id, entity_id)
2156
+ );
2157
+
2158
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
2159
+ name,
2160
+ content=entities,
2161
+ content_rowid=rowid
2162
+ );
2163
+
2164
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
2165
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
2166
+ END;
2167
+
2168
+ CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
2169
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
2170
+ END;
2171
+
2172
+ CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
2173
+ INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
2174
+ INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
2175
+ END;
2176
+ `);
2177
+ try {
2178
+ await client.execute(
2179
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_entities_type_name_nocase_unique ON entities(type, name COLLATE NOCASE)"
2180
+ );
2181
+ } catch (e) {
2182
+ process.stderr.write(
2183
+ `[database] WARN: could not create NOCASE entity uniqueness index; existing case-only duplicates may need dedupe: ${e instanceof Error ? e.message : String(e)}
2184
+ `
2185
+ );
2186
+ }
2187
+ try {
2188
+ await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
2189
+ } catch (e) {
2190
+ logCatchDebug("migration", e);
2191
+ }
2192
+ await client.executeMultiple(`
2193
+ CREATE TABLE IF NOT EXISTS entity_aliases (
2194
+ alias TEXT NOT NULL PRIMARY KEY,
2195
+ canonical_entity_id TEXT NOT NULL
2196
+ );
2197
+ CREATE INDEX IF NOT EXISTS idx_entity_aliases_canonical ON entity_aliases(canonical_entity_id);
2198
+ `);
2199
+ for (const col of [
2200
+ "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
2201
+ "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
2202
+ ]) {
2203
+ await executeAddColumn(client, col);
2204
+ }
2205
+ await client.executeMultiple(`
2206
+ CREATE TABLE IF NOT EXISTS ontology_schema (
2207
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2208
+ source_type TEXT NOT NULL,
2209
+ relationship_type TEXT NOT NULL,
2210
+ target_type TEXT NOT NULL,
2211
+ description TEXT,
2212
+ bidirectional INTEGER DEFAULT 0,
2213
+ UNIQUE(source_type, relationship_type, target_type)
2214
+ );
2215
+
2216
+ CREATE INDEX IF NOT EXISTS idx_ontology_source ON ontology_schema(source_type);
2217
+ CREATE INDEX IF NOT EXISTS idx_ontology_target ON ontology_schema(target_type);
2218
+ CREATE INDEX IF NOT EXISTS idx_entities_type_name ON entities(type, name);
2219
+ `);
2220
+ try {
2221
+ const triples = [
2222
+ ["person", "worked_on", "project", 0],
2223
+ ["person", "worked_on", "file", 0],
2224
+ ["person", "uses", "tool", 0],
2225
+ ["person", "decided", "decision", 0],
2226
+ ["person", "created", "file", 0],
2227
+ ["person", "fixed", "file", 0],
2228
+ ["person", "reviewed", "file", 0],
2229
+ ["person", "reports_to", "person", 0],
2230
+ ["person", "manages", "person", 0],
2231
+ ["person", "communicates_with", "person", 1],
2232
+ ["person", "ordered_from", "supplier", 0],
2233
+ ["file", "depends_on", "file", 0],
2234
+ ["file", "part_of", "project", 0],
2235
+ ["file", "implements", "concept", 0],
2236
+ ["project", "depends_on", "project", 0],
2237
+ ["project", "uses", "tool", 0],
2238
+ ["decision", "rationale_for", "decision", 0],
2239
+ ["decision", "superseded_by", "decision", 0],
2240
+ ["decision", "related_to", "project", 0],
2241
+ ["concept", "related_to", "concept", 1],
2242
+ ["concept", "part_of", "project", 0],
2243
+ ["rationale", "rationale_for", "decision", 0],
2244
+ ["product", "part_of", "company", 0],
2245
+ ["product", "categorized_as", "category", 0],
2246
+ ["company", "located_at", "location", 0],
2247
+ ["supplier", "supplies", "product", 0],
2248
+ ["order", "ships_to", "location", 0],
2249
+ ["tool", "part_of", "project", 0]
2250
+ ];
2251
+ for (const [src, rel, tgt, bidir] of triples) {
2252
+ await client.execute({
2253
+ sql: `INSERT OR IGNORE INTO ontology_schema (source_type, relationship_type, target_type, bidirectional) VALUES (?, ?, ?, ?)`,
2254
+ args: [src, rel, tgt, bidir]
2255
+ });
2256
+ }
2257
+ } catch (e) {
2258
+ logCatchDebug("ontology seed", e);
2259
+ }
2260
+ let relationshipsValidFromColumnAdded = false;
2261
+ relationshipsValidFromColumnAdded = await executeAddColumn(client, "ALTER TABLE relationships ADD COLUMN valid_from TEXT");
2262
+ await executeAddColumn(client, "ALTER TABLE relationships ADD COLUMN valid_until TEXT");
2263
+ try {
2264
+ if (relationshipsValidFromColumnAdded) {
2265
+ await client.execute("UPDATE relationships SET valid_from = timestamp WHERE valid_from IS NULL AND timestamp IS NOT NULL");
2266
+ }
2267
+ } catch (e) {
2268
+ logCatchDebug("migration", e);
2269
+ }
2270
+ try {
2271
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_relationships_valid_from ON relationships(valid_from)");
2272
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_relationships_valid_until ON relationships(valid_until)");
2273
+ } catch (e) {
2274
+ logCatchDebug("migration", e);
2275
+ }
2276
+ await executeAddColumn(client, "ALTER TABLE entities ADD COLUMN parent_type TEXT");
2277
+ try {
2278
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_entities_parent_type ON entities(parent_type)");
2279
+ } catch (e) {
2280
+ logCatchDebug("migration", e);
2281
+ }
2282
+ for (const col of [
2283
+ "ALTER TABLE memories ADD COLUMN source_trust_level TEXT DEFAULT 'unknown'",
2284
+ "ALTER TABLE memories ADD COLUMN quarantined INTEGER DEFAULT 0",
2285
+ "ALTER TABLE memories ADD COLUMN quarantine_reason TEXT"
2286
+ ]) {
2287
+ await executeAddColumn(client, col);
2288
+ }
2289
+ try {
2290
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_quarantined ON memories(quarantined) WHERE quarantined = 1");
2291
+ } catch (e) {
2292
+ logCatchDebug("migration", e);
2293
+ }
2294
+ await client.executeMultiple(`
2295
+ CREATE TABLE IF NOT EXISTS sync_conflicts (
2296
+ id TEXT PRIMARY KEY,
2297
+ memory_id TEXT NOT NULL,
2298
+ local_version INTEGER,
2299
+ remote_version INTEGER,
2300
+ local_timestamp TEXT,
2301
+ remote_timestamp TEXT,
2302
+ local_device_id TEXT,
2303
+ resolution TEXT NOT NULL DEFAULT 'remote_wins',
2304
+ created_at TEXT NOT NULL
2305
+ );
2306
+ `);
2307
+ await executeAddColumn(client, "ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'project'", "migration add memories.visibility");
2308
+ for (const col of [
2309
+ "ALTER TABLE memories ADD COLUMN media_url TEXT",
2310
+ "ALTER TABLE memories ADD COLUMN media_type TEXT",
2311
+ "ALTER TABLE memories ADD COLUMN media_meta TEXT"
2312
+ ]) {
2313
+ await executeAddColumn(client, col);
2314
+ }
2315
+ await client.executeMultiple(`
2316
+ CREATE TABLE IF NOT EXISTS agent_centroids (
2317
+ agent_id TEXT NOT NULL,
2318
+ project_name TEXT NOT NULL,
2319
+ centroid_vector BLOB,
2320
+ sample_count INTEGER DEFAULT 0,
2321
+ updated_at TEXT NOT NULL,
2322
+ PRIMARY KEY (agent_id, project_name)
2323
+ );
2324
+ `);
2325
+ try {
2326
+ await client.execute(
2327
+ `CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)`
2328
+ );
2329
+ } catch (e) {
2330
+ logCatchDebug("migration", e);
2331
+ }
2332
+ await client.executeMultiple(`
2333
+ CREATE TABLE IF NOT EXISTS identity (
2334
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2335
+ agent_id TEXT NOT NULL UNIQUE,
2336
+ content_hash TEXT NOT NULL,
2337
+ updated_at TEXT NOT NULL,
2338
+ updated_by TEXT NOT NULL
2339
+ );
2340
+
2341
+ CREATE INDEX IF NOT EXISTS idx_identity_agent ON identity(agent_id);
2342
+ `);
2343
+ await client.executeMultiple(`
2344
+ CREATE TABLE IF NOT EXISTS chat_history (
2345
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2346
+ session_id TEXT NOT NULL,
2347
+ role TEXT NOT NULL,
2348
+ content TEXT NOT NULL,
2349
+ tool_name TEXT,
2350
+ tool_id TEXT,
2351
+ is_error INTEGER NOT NULL DEFAULT 0,
2352
+ timestamp INTEGER NOT NULL
2353
+ );
2354
+
2355
+ CREATE INDEX IF NOT EXISTS idx_chat_history_session
2356
+ ON chat_history(session_id, id);
2357
+ `);
2358
+ await client.executeMultiple(`
2359
+ CREATE TABLE IF NOT EXISTS session_events (
2360
+ id TEXT PRIMARY KEY,
2361
+ agent_id TEXT NOT NULL,
2362
+ agent_role TEXT NOT NULL,
2363
+ session_id TEXT NOT NULL,
2364
+ session_scope TEXT,
2365
+ project_name TEXT NOT NULL,
2366
+ event_index INTEGER NOT NULL,
2367
+ event_type TEXT NOT NULL,
2368
+ tool_name TEXT,
2369
+ tool_use_id TEXT,
2370
+ content TEXT NOT NULL,
2371
+ payload_json TEXT,
2372
+ has_error INTEGER NOT NULL DEFAULT 0,
2373
+ created_at TEXT NOT NULL
2374
+ );
2375
+
2376
+ CREATE INDEX IF NOT EXISTS idx_session_events_agent_time
2377
+ ON session_events(agent_id, created_at DESC);
2378
+
2379
+ CREATE INDEX IF NOT EXISTS idx_session_events_session_index
2380
+ ON session_events(session_id, event_index);
2381
+
2382
+ CREATE INDEX IF NOT EXISTS idx_session_events_scope_agent_time
2383
+ ON session_events(session_scope, agent_id, created_at DESC);
2384
+ `);
2385
+ await client.executeMultiple(`
2386
+ CREATE TABLE IF NOT EXISTS workspaces (
2387
+ id TEXT PRIMARY KEY,
2388
+ slug TEXT NOT NULL UNIQUE,
2389
+ name TEXT NOT NULL,
2390
+ owner_agent_id TEXT,
2391
+ created_at TEXT NOT NULL,
2392
+ metadata TEXT
2393
+ );
2394
+
2395
+ CREATE INDEX IF NOT EXISTS idx_workspaces_slug
2396
+ ON workspaces(slug);
2397
+ `);
2398
+ await client.executeMultiple(`
2399
+ CREATE TABLE IF NOT EXISTS documents (
2400
+ id TEXT PRIMARY KEY,
2401
+ workspace_id TEXT NOT NULL,
2402
+ filename TEXT NOT NULL,
2403
+ mime TEXT,
2404
+ source_type TEXT,
2405
+ user_id TEXT,
2406
+ uploaded_at TEXT NOT NULL,
2407
+ metadata TEXT,
2408
+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id)
2409
+ );
2410
+
2411
+ CREATE INDEX IF NOT EXISTS idx_documents_workspace
2412
+ ON documents(workspace_id);
2413
+
2414
+ CREATE INDEX IF NOT EXISTS idx_documents_user
2415
+ ON documents(user_id);
2416
+ `);
2417
+ for (const column of [
2418
+ "workspace_id TEXT",
2419
+ "document_id TEXT",
2420
+ "user_id TEXT",
2421
+ "char_offset INTEGER",
2422
+ "page_number INTEGER"
2423
+ ]) {
2424
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN ${column}`);
2425
+ }
2426
+ for (const col of [
2427
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
2428
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
2429
+ ]) {
2430
+ await executeAddColumn(client, col);
2431
+ }
2432
+ await client.executeMultiple(`
2433
+ CREATE INDEX IF NOT EXISTS idx_memories_workspace
2434
+ ON memories(workspace_id);
2435
+
2436
+ CREATE INDEX IF NOT EXISTS idx_memories_document
2437
+ ON memories(document_id);
2438
+
2439
+ CREATE INDEX IF NOT EXISTS idx_memories_user
2440
+ ON memories(user_id);
2441
+ `);
2442
+ await client.executeMultiple(`
2443
+ CREATE TABLE IF NOT EXISTS session_kills (
2444
+ id TEXT PRIMARY KEY,
2445
+ session_name TEXT NOT NULL,
2446
+ agent_id TEXT NOT NULL,
2447
+ killed_at TIMESTAMP NOT NULL,
2448
+ reason TEXT NOT NULL,
2449
+ ticks_idle INTEGER,
2450
+ estimated_tokens_saved INTEGER
2451
+ );
2452
+
2453
+ CREATE INDEX IF NOT EXISTS idx_session_kills_killed_at
2454
+ ON session_kills(killed_at);
2455
+
2456
+ CREATE INDEX IF NOT EXISTS idx_session_kills_agent
2457
+ ON session_kills(agent_id);
2458
+ `);
2459
+ await client.execute(`
2460
+ CREATE TABLE IF NOT EXISTS company_procedures (
2461
+ id TEXT PRIMARY KEY,
2462
+ title TEXT NOT NULL,
2463
+ content TEXT NOT NULL,
2464
+ priority TEXT NOT NULL DEFAULT 'p0',
2465
+ domain TEXT,
2466
+ kind TEXT NOT NULL DEFAULT 'procedure',
2467
+ active INTEGER NOT NULL DEFAULT 1,
2468
+ created_at TEXT NOT NULL,
2469
+ updated_at TEXT NOT NULL
2470
+ )
2471
+ `);
2472
+ await executeAddColumn(client, "ALTER TABLE company_procedures ADD COLUMN kind TEXT NOT NULL DEFAULT 'procedure'");
2473
+ const legacyProcedureObject = await client.execute({
2474
+ sql: "SELECT type FROM sqlite_master WHERE name = 'global_procedures'",
2475
+ args: []
2476
+ });
2477
+ const legacyProcedureType = legacyProcedureObject.rows[0]?.type == null ? null : String(legacyProcedureObject.rows[0].type);
2478
+ if (legacyProcedureType === "table") {
2479
+ await client.execute(`
2480
+ INSERT OR IGNORE INTO company_procedures
2481
+ (id, title, content, priority, domain, active, created_at, updated_at)
2482
+ SELECT id, title, content, priority, domain, active, created_at, updated_at
2483
+ FROM global_procedures
2484
+ `);
2485
+ await client.executeMultiple(`
2486
+ CREATE TRIGGER IF NOT EXISTS global_procedures_mirror_insert
2487
+ AFTER INSERT ON global_procedures
2488
+ BEGIN
2489
+ INSERT OR IGNORE INTO company_procedures
2490
+ (id, title, content, priority, domain, active, created_at, updated_at)
2491
+ VALUES
2492
+ (NEW.id, NEW.title, NEW.content, NEW.priority, NEW.domain, NEW.active, NEW.created_at, NEW.updated_at);
2493
+ END;
2494
+
2495
+ CREATE TRIGGER IF NOT EXISTS global_procedures_mirror_update
2496
+ AFTER UPDATE ON global_procedures
2497
+ BEGIN
2498
+ UPDATE company_procedures
2499
+ SET title = NEW.title,
2500
+ content = NEW.content,
2501
+ priority = NEW.priority,
2502
+ domain = NEW.domain,
2503
+ active = NEW.active,
2504
+ created_at = NEW.created_at,
2505
+ updated_at = NEW.updated_at
2506
+ WHERE id = OLD.id;
2507
+ END;
2508
+ `);
2509
+ } else {
2510
+ await client.execute(`
2511
+ CREATE VIEW IF NOT EXISTS global_procedures AS
2512
+ SELECT id, title, content, priority, domain, active, created_at, updated_at
2513
+ FROM company_procedures
2514
+ `);
2515
+ await client.executeMultiple(`
2516
+ CREATE TRIGGER IF NOT EXISTS global_procedures_insert
2517
+ INSTEAD OF INSERT ON global_procedures
2518
+ BEGIN
2519
+ INSERT INTO company_procedures
2520
+ (id, title, content, priority, domain, active, created_at, updated_at)
2521
+ VALUES
2522
+ (NEW.id, NEW.title, NEW.content, NEW.priority, NEW.domain, NEW.active, NEW.created_at, NEW.updated_at);
2523
+ END;
2524
+
2525
+ CREATE TRIGGER IF NOT EXISTS global_procedures_update
2526
+ INSTEAD OF UPDATE ON global_procedures
2527
+ BEGIN
2528
+ UPDATE company_procedures
2529
+ SET title = NEW.title,
2530
+ content = NEW.content,
2531
+ priority = NEW.priority,
2532
+ domain = NEW.domain,
2533
+ active = NEW.active,
2534
+ created_at = NEW.created_at,
2535
+ updated_at = NEW.updated_at
2536
+ WHERE id = OLD.id;
2537
+ END;
2538
+ `);
2539
+ }
2540
+ await client.executeMultiple(`
2541
+ CREATE TABLE IF NOT EXISTS conversations (
2542
+ id TEXT PRIMARY KEY,
2543
+ platform TEXT NOT NULL,
2544
+ external_id TEXT,
2545
+ sender_id TEXT NOT NULL,
2546
+ sender_name TEXT,
2547
+ sender_phone TEXT,
2548
+ sender_email TEXT,
2549
+ recipient_id TEXT,
2550
+ channel_id TEXT NOT NULL,
2551
+ thread_id TEXT,
2552
+ reply_to_id TEXT,
2553
+ content_text TEXT,
2554
+ content_media TEXT,
2555
+ content_metadata TEXT,
2556
+ agent_response TEXT,
2557
+ agent_name TEXT,
2558
+ timestamp TEXT NOT NULL,
2559
+ ingested_at TEXT NOT NULL
2560
+ );
2561
+
2562
+ CREATE INDEX IF NOT EXISTS idx_conversations_platform
2563
+ ON conversations(platform);
2564
+
2565
+ CREATE INDEX IF NOT EXISTS idx_conversations_sender
2566
+ ON conversations(sender_id);
2567
+
2568
+ CREATE INDEX IF NOT EXISTS idx_conversations_timestamp
2569
+ ON conversations(timestamp);
2570
+
2571
+ CREATE INDEX IF NOT EXISTS idx_conversations_thread
2572
+ ON conversations(thread_id);
2573
+
2574
+ CREATE INDEX IF NOT EXISTS idx_conversations_channel
2575
+ ON conversations(channel_id);
2576
+ `);
2577
+ await client.executeMultiple(`
2578
+ CREATE TABLE IF NOT EXISTS session_agent_map (
2579
+ session_uuid TEXT PRIMARY KEY,
2580
+ agent_id TEXT NOT NULL,
2581
+ session_name TEXT,
2582
+ task_id TEXT,
2583
+ project_name TEXT,
2584
+ started_at TEXT NOT NULL,
2585
+ cache_cold_count INTEGER NOT NULL DEFAULT 0
2586
+ );
2587
+
2588
+ CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
2589
+ ON session_agent_map(agent_id);
2590
+ `);
2591
+ await client.executeMultiple(`
2592
+ CREATE TABLE IF NOT EXISTS agent_file_reads (
2593
+ session_uuid TEXT NOT NULL,
2594
+ agent_id TEXT NOT NULL,
2595
+ file_path TEXT NOT NULL,
2596
+ read_at TEXT NOT NULL,
2597
+ commit_hash TEXT,
2598
+ PRIMARY KEY (session_uuid, file_path)
2599
+ );
2600
+
2601
+ CREATE INDEX IF NOT EXISTS idx_agent_file_reads_agent_read_at
2602
+ ON agent_file_reads(agent_id, read_at);
2603
+ `);
2604
+ try {
2605
+ const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
2606
+ if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
2607
+ await client.execute({
2608
+ sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
2609
+ SELECT session_id, agent_id, '', MIN(timestamp)
2610
+ FROM memories
2611
+ WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
2612
+ GROUP BY session_id, agent_id`,
2613
+ args: []
2614
+ });
2615
+ }
2616
+ } catch (e) {
2617
+ logCatchDebug("session_agent_map backfill", e);
2618
+ }
2619
+ await executeAddColumn(client, `ALTER TABLE session_agent_map ADD COLUMN cache_cold_count INTEGER NOT NULL DEFAULT 0`);
2620
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`);
2621
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`);
2622
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`);
2623
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`);
2624
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`);
2625
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`);
2626
+ let tasksDeviceIdColumnAdded = false;
2627
+ tasksDeviceIdColumnAdded = await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN device_id TEXT`);
2628
+ try {
2629
+ if (tasksDeviceIdColumnAdded) {
2630
+ const { loadDeviceId } = await import("./lib/license.js");
2631
+ const deviceId = loadDeviceId();
2632
+ if (deviceId) {
2633
+ await client.execute({
2634
+ sql: "UPDATE tasks SET device_id = ? WHERE device_id IS NULL",
2635
+ args: [deviceId]
2636
+ });
2637
+ }
2638
+ }
2639
+ } catch (e) {
2640
+ logCatchDebug("migration", e);
2641
+ }
2642
+ for (const col of [
2643
+ "ALTER TABLE tasks ADD COLUMN claimed_by TEXT",
2644
+ "ALTER TABLE tasks ADD COLUMN claimed_at TEXT",
2645
+ "ALTER TABLE tasks ADD COLUMN last_heartbeat_at TEXT",
2646
+ "ALTER TABLE tasks ADD COLUMN lease_expires_at TEXT",
2647
+ "ALTER TABLE tasks ADD COLUMN claim_count INTEGER NOT NULL DEFAULT 0"
2648
+ ]) {
2649
+ await executeAddColumn(client, col);
2650
+ }
2651
+ await executeAddColumn(client, "ALTER TABLE tasks ADD COLUMN assertions TEXT");
2652
+ for (const idx of [
2653
+ "CREATE INDEX IF NOT EXISTS idx_tasks_heartbeat_stale ON tasks(status, last_heartbeat_at) WHERE status = 'in_progress'",
2654
+ "CREATE INDEX IF NOT EXISTS idx_tasks_lease_expiry ON tasks(status, lease_expires_at) WHERE status = 'in_progress'",
2655
+ "CREATE INDEX IF NOT EXISTS idx_tasks_claimed_by ON tasks(claimed_by) WHERE claimed_by IS NOT NULL"
2656
+ ]) {
2657
+ try {
2658
+ await client.execute(idx);
2659
+ } catch (e) {
2660
+ logCatchDebug("migration", e);
2661
+ }
2662
+ }
2663
+ await client.executeMultiple(`
2664
+ CREATE TABLE IF NOT EXISTS core_memory (
2665
+ agent_id TEXT NOT NULL,
2666
+ key TEXT NOT NULL,
2667
+ value TEXT NOT NULL,
2668
+ updated_at TEXT NOT NULL,
2669
+ PRIMARY KEY (agent_id, key)
2670
+ );
2671
+ `);
2672
+ await client.executeMultiple(`
2673
+ CREATE TABLE IF NOT EXISTS oauth_tokens (
2674
+ id TEXT PRIMARY KEY,
2675
+ provider TEXT NOT NULL,
2676
+ access_token TEXT NOT NULL,
2677
+ refresh_token TEXT,
2678
+ token_type TEXT NOT NULL DEFAULT 'Bearer',
2679
+ scope TEXT,
2680
+ expires_at TEXT,
2681
+ user_id TEXT,
2682
+ created_at TEXT NOT NULL,
2683
+ updated_at TEXT NOT NULL
2684
+ );
2685
+
2686
+ CREATE INDEX IF NOT EXISTS idx_oauth_tokens_provider
2687
+ ON oauth_tokens(provider, user_id);
2688
+ `);
2689
+ await client.executeMultiple(`
2690
+ CREATE TABLE IF NOT EXISTS wiki_acl (
2691
+ user_id TEXT NOT NULL,
2692
+ workspace TEXT NOT NULL,
2693
+ access_level TEXT NOT NULL DEFAULT 'read',
2694
+ granted_by TEXT,
2695
+ created_at TEXT NOT NULL,
2696
+ PRIMARY KEY (user_id, workspace)
2697
+ );
2698
+ `);
2699
+ await client.executeMultiple(`
2700
+ CREATE TABLE IF NOT EXISTS workflow_definitions (
2701
+ id TEXT PRIMARY KEY,
2702
+ name TEXT NOT NULL,
2703
+ description TEXT,
2704
+ steps TEXT NOT NULL,
2705
+ trigger_event TEXT,
2706
+ created_at TEXT NOT NULL
2707
+ );
2708
+
2709
+ CREATE TABLE IF NOT EXISTS workflow_instances (
2710
+ id TEXT PRIMARY KEY,
2711
+ definition_id TEXT NOT NULL,
2712
+ status TEXT NOT NULL DEFAULT 'pending',
2713
+ current_step INTEGER NOT NULL DEFAULT 1,
2714
+ step_results TEXT NOT NULL DEFAULT '[]',
2715
+ context TEXT NOT NULL DEFAULT '{}',
2716
+ approval_task_id TEXT,
2717
+ created_at TEXT NOT NULL,
2718
+ updated_at TEXT NOT NULL,
2719
+ FOREIGN KEY (definition_id) REFERENCES workflow_definitions(id)
2720
+ );
2721
+
2722
+ CREATE INDEX IF NOT EXISTS idx_workflow_instances_status
2723
+ ON workflow_instances(status);
2724
+ `);
2725
+ await client.executeMultiple(`
2726
+ CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
2727
+ content_text,
2728
+ sender_name,
2729
+ agent_response,
2730
+ content='conversations',
2731
+ content_rowid='rowid'
2732
+ );
2733
+
2734
+ CREATE TRIGGER IF NOT EXISTS conversations_fts_ai AFTER INSERT ON conversations BEGIN
2735
+ INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
2736
+ VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
2737
+ END;
2738
+
2739
+ CREATE TRIGGER IF NOT EXISTS conversations_fts_ad AFTER DELETE ON conversations BEGIN
2740
+ INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
2741
+ VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
2742
+ END;
2743
+
2744
+ CREATE TRIGGER IF NOT EXISTS conversations_fts_au AFTER UPDATE OF content_text, sender_name, agent_response ON conversations BEGIN
2745
+ INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
2746
+ VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
2747
+ INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
2748
+ VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
2749
+ END;
2750
+ `);
2751
+ await client.executeMultiple(`
2752
+ CREATE TABLE IF NOT EXISTS memory_cards (
2753
+ id TEXT PRIMARY KEY,
2754
+ memory_id TEXT NOT NULL,
2755
+ agent_id TEXT NOT NULL,
2756
+ session_id TEXT NOT NULL,
2757
+ project_name TEXT,
2758
+ timestamp TEXT NOT NULL,
2759
+ card_type TEXT NOT NULL,
2760
+ subject TEXT,
2761
+ predicate TEXT,
2762
+ object TEXT,
2763
+ content TEXT NOT NULL,
2764
+ source_ref TEXT,
2765
+ confidence REAL DEFAULT 0.6,
2766
+ active INTEGER DEFAULT 1,
2767
+ created_at TEXT NOT NULL
2768
+ );
2769
+
2770
+ CREATE INDEX IF NOT EXISTS idx_memory_cards_agent
2771
+ ON memory_cards(agent_id, active, timestamp);
2772
+
2773
+ CREATE INDEX IF NOT EXISTS idx_memory_cards_memory
2774
+ ON memory_cards(memory_id);
2775
+
2776
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_cards_fts
2777
+ USING fts5(content, subject, predicate, object, content='memory_cards', content_rowid='rowid');
2778
+
2779
+ CREATE TRIGGER IF NOT EXISTS memory_cards_fts_ai AFTER INSERT ON memory_cards BEGIN
2780
+ INSERT INTO memory_cards_fts(rowid, content, subject, predicate, object)
2781
+ VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
2782
+ END;
2783
+
2784
+ CREATE TRIGGER IF NOT EXISTS memory_cards_fts_ad AFTER DELETE ON memory_cards BEGIN
2785
+ INSERT INTO memory_cards_fts(memory_cards_fts, rowid, content, subject, predicate, object)
2786
+ VALUES('delete', old.rowid, old.content, old.subject, old.predicate, old.object);
2787
+ END;
2788
+
2789
+ CREATE TRIGGER IF NOT EXISTS memory_cards_fts_au AFTER UPDATE ON memory_cards BEGIN
2790
+ INSERT INTO memory_cards_fts(memory_cards_fts, rowid, content, subject, predicate, object)
2791
+ VALUES('delete', old.rowid, old.content, old.subject, old.predicate, old.object);
2792
+ INSERT INTO memory_cards_fts(rowid, content, subject, predicate, object)
2793
+ VALUES (new.rowid, new.content, new.subject, new.predicate, new.object);
2794
+ END;
2795
+ `);
2796
+ await client.executeMultiple(`
2797
+ CREATE TABLE IF NOT EXISTS memory_reflections (
2798
+ id TEXT PRIMARY KEY,
2799
+ agent_id TEXT NOT NULL,
2800
+ project_name TEXT,
2801
+ insight_type TEXT NOT NULL CHECK (insight_type IN ('pattern', 'contradiction', 'evolution', 'summary')),
2802
+ subject TEXT NOT NULL,
2803
+ content TEXT NOT NULL,
2804
+ source_card_ids TEXT NOT NULL DEFAULT '[]',
2805
+ confidence REAL NOT NULL DEFAULT 0.7,
2806
+ active INTEGER NOT NULL DEFAULT 1,
2807
+ created_at TEXT NOT NULL,
2808
+ expires_at TEXT
2809
+ );
2810
+
2811
+ CREATE INDEX IF NOT EXISTS idx_memory_reflections_agent
2812
+ ON memory_reflections(agent_id, insight_type, created_at DESC);
2813
+
2814
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_reflections_fts USING fts5(
2815
+ content, subject,
2816
+ content=memory_reflections, content_rowid=rowid
2817
+ );
2818
+
2819
+ CREATE TRIGGER IF NOT EXISTS memory_reflections_fts_ai AFTER INSERT ON memory_reflections BEGIN
2820
+ INSERT INTO memory_reflections_fts(rowid, content, subject)
2821
+ VALUES (new.rowid, new.content, new.subject);
2822
+ END;
2823
+
2824
+ CREATE TRIGGER IF NOT EXISTS memory_reflections_fts_ad AFTER DELETE ON memory_reflections BEGIN
2825
+ INSERT INTO memory_reflections_fts(memory_reflections_fts, rowid, content, subject)
2826
+ VALUES ('delete', old.rowid, old.content, old.subject);
2827
+ END;
2828
+ `);
2829
+ await client.executeMultiple(`
2830
+ CREATE TABLE IF NOT EXISTS agent_sessions (
2831
+ id TEXT PRIMARY KEY,
2832
+ agent_id TEXT NOT NULL,
2833
+ project_name TEXT,
2834
+ started_at TEXT NOT NULL,
2835
+ last_event_at TEXT NOT NULL,
2836
+ event_count INTEGER NOT NULL DEFAULT 0,
2837
+ properties TEXT DEFAULT '{}'
2838
+ );
2839
+
2840
+ CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_time
2841
+ ON agent_sessions(agent_id, started_at);
2842
+
2843
+ CREATE TABLE IF NOT EXISTS agent_goals (
2844
+ id TEXT PRIMARY KEY,
2845
+ statement TEXT NOT NULL,
2846
+ owner_agent_id TEXT,
2847
+ project_name TEXT,
2848
+ status TEXT NOT NULL DEFAULT 'open',
2849
+ priority INTEGER NOT NULL DEFAULT 5,
2850
+ success_criteria TEXT,
2851
+ parent_goal_id TEXT,
2852
+ due_at TEXT,
2853
+ achieved_at TEXT,
2854
+ supersedes_id TEXT,
2855
+ created_at TEXT NOT NULL,
2856
+ updated_at TEXT NOT NULL,
2857
+ source_memory_id TEXT
2858
+ );
2859
+
2860
+ CREATE INDEX IF NOT EXISTS idx_agent_goals_project_status
2861
+ ON agent_goals(project_name, status, priority);
2862
+
2863
+ CREATE TABLE IF NOT EXISTS agent_events (
2864
+ id TEXT PRIMARY KEY,
2865
+ event_type TEXT NOT NULL,
2866
+ occurred_at TEXT NOT NULL,
2867
+ sequence_index INTEGER NOT NULL,
2868
+ actor_agent_id TEXT,
2869
+ agent_role TEXT,
2870
+ project_name TEXT,
2871
+ session_id TEXT,
2872
+ task_id TEXT,
2873
+ goal_id TEXT,
2874
+ parent_event_id TEXT,
2875
+ intention TEXT,
2876
+ outcome TEXT,
2877
+ evidence_memory_id TEXT,
2878
+ impact TEXT,
2879
+ payload TEXT DEFAULT '{}',
2880
+ created_at TEXT NOT NULL
2881
+ );
2882
+
2883
+ CREATE INDEX IF NOT EXISTS idx_agent_events_time
2884
+ ON agent_events(occurred_at, sequence_index);
2885
+
2886
+ CREATE INDEX IF NOT EXISTS idx_agent_events_session_seq
2887
+ ON agent_events(session_id, sequence_index);
2888
+
2889
+ CREATE INDEX IF NOT EXISTS idx_agent_events_goal_time
2890
+ ON agent_events(goal_id, occurred_at);
2891
+
2892
+ CREATE INDEX IF NOT EXISTS idx_agent_events_memory
2893
+ ON agent_events(evidence_memory_id);
2894
+
2895
+ CREATE TABLE IF NOT EXISTS agent_goal_links (
2896
+ id TEXT PRIMARY KEY,
2897
+ goal_id TEXT NOT NULL,
2898
+ link_type TEXT NOT NULL,
2899
+ target_id TEXT NOT NULL,
2900
+ target_type TEXT NOT NULL,
2901
+ created_at TEXT NOT NULL
2902
+ );
2903
+
2904
+ CREATE INDEX IF NOT EXISTS idx_agent_goal_links_goal
2905
+ ON agent_goal_links(goal_id, target_type);
2906
+
2907
+ CREATE TABLE IF NOT EXISTS agent_semantic_labels (
2908
+ id TEXT PRIMARY KEY,
2909
+ source_memory_id TEXT NOT NULL,
2910
+ event_id TEXT,
2911
+ labeler TEXT NOT NULL,
2912
+ schema_version INTEGER NOT NULL DEFAULT 1,
2913
+ confidence REAL NOT NULL DEFAULT 0,
2914
+ labels TEXT NOT NULL,
2915
+ created_at TEXT NOT NULL,
2916
+ updated_at TEXT NOT NULL
2917
+ );
2918
+
2919
+ CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_memory
2920
+ ON agent_semantic_labels(source_memory_id, labeler);
2921
+
2922
+ CREATE INDEX IF NOT EXISTS idx_agent_semantic_labels_event
2923
+ ON agent_semantic_labels(event_id);
2924
+
2925
+ CREATE TABLE IF NOT EXISTS agent_reflection_checkpoints (
2926
+ id TEXT PRIMARY KEY,
2927
+ project_name TEXT,
2928
+ session_id TEXT,
2929
+ window_start_at TEXT NOT NULL,
2930
+ window_end_at TEXT NOT NULL,
2931
+ event_count INTEGER NOT NULL DEFAULT 0,
2932
+ goal_count INTEGER NOT NULL DEFAULT 0,
2933
+ success_count INTEGER NOT NULL DEFAULT 0,
2934
+ failure_count INTEGER NOT NULL DEFAULT 0,
2935
+ risk_count INTEGER NOT NULL DEFAULT 0,
2936
+ summary TEXT NOT NULL,
2937
+ learnings TEXT NOT NULL DEFAULT '[]',
2938
+ next_actions TEXT NOT NULL DEFAULT '[]',
2939
+ evidence_event_ids TEXT NOT NULL DEFAULT '[]',
2940
+ confidence REAL NOT NULL DEFAULT 0,
2941
+ created_at TEXT NOT NULL
2942
+ );
2943
+
2944
+ CREATE INDEX IF NOT EXISTS idx_agent_reflection_project_time
2945
+ ON agent_reflection_checkpoints(project_name, window_end_at);
2946
+
2947
+ CREATE INDEX IF NOT EXISTS idx_agent_reflection_session_time
2948
+ ON agent_reflection_checkpoints(session_id, window_end_at);
2949
+ `);
2950
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`);
2951
+ try {
2952
+ await client.execute(
2953
+ `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
2954
+ );
2955
+ } catch (e) {
2956
+ logCatchDebug("migration", e);
2957
+ }
2958
+ try {
2959
+ await client.execute({
2960
+ sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
2961
+ args: []
2962
+ });
2963
+ await client.execute({
2964
+ sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
2965
+ args: []
2966
+ });
2967
+ } catch (e) {
2968
+ logCatchDebug("migration", e);
2969
+ }
2970
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`);
2971
+ try {
2972
+ await client.execute(
2973
+ `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
2974
+ );
2975
+ } catch (e) {
2976
+ logCatchDebug("migration", e);
2977
+ }
2978
+ for (const col of [
2979
+ "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
2980
+ "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
2981
+ ]) {
2982
+ await executeAddColumn(client, col);
2983
+ }
2984
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`);
2985
+ try {
2986
+ await client.execute(
2987
+ `CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
2988
+ );
2989
+ } catch (e) {
2990
+ logCatchDebug("migration", e);
2991
+ }
2992
+ for (const col of [
2993
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
2994
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
2995
+ ]) {
2996
+ await executeAddColumn(client, col);
2997
+ }
2998
+ try {
2999
+ await client.execute({
3000
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3001
+ args: []
3002
+ });
3003
+ } catch (e) {
3004
+ logCatchDebug("migration", e);
3005
+ }
3006
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`);
3007
+ try {
3008
+ await client.execute(
3009
+ `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
3010
+ );
3011
+ } catch (e) {
3012
+ logCatchDebug("migration", e);
3013
+ }
3014
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN trajectory TEXT`);
3015
+ for (const col of [
3016
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
3017
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
3018
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
3019
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
3020
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
3021
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
3022
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
3023
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
3024
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
3025
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
3026
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
3027
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
3028
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
3029
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
3030
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
3031
+ ]) {
3032
+ await executeAddColumn(client, col);
3033
+ }
3034
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN procedure_for TEXT`);
3035
+ try {
3036
+ await client.execute({
3037
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
3038
+ args: []
3039
+ });
3040
+ } catch (e) {
3041
+ logCatchDebug("migration", e);
3042
+ }
3043
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`);
3044
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`);
3045
+ await executeAddColumn(client, `ALTER TABLE behaviors ADD COLUMN device_id TEXT`);
3046
+ for (const col of [
3047
+ "use_count INTEGER DEFAULT 0",
3048
+ "success_count INTEGER DEFAULT 0",
3049
+ "last_used_at TEXT",
3050
+ "last_refined_at TEXT"
3051
+ ]) {
3052
+ await executeAddColumn(client, `ALTER TABLE behaviors ADD COLUMN ${col}`);
3053
+ }
3054
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN session_scope TEXT`);
3055
+ try {
3056
+ await client.execute(
3057
+ `CREATE INDEX IF NOT EXISTS idx_memories_agent_scope_project ON memories(agent_id, session_scope, project_name)`
3058
+ );
3059
+ } catch (e) {
3060
+ logCatchDebug("migration", e);
3061
+ }
3062
+ try {
3063
+ await client.execute({
3064
+ sql: `CREATE TABLE IF NOT EXISTS task_groups (
3065
+ id TEXT PRIMARY KEY,
3066
+ title TEXT NOT NULL,
3067
+ coordinator TEXT NOT NULL,
3068
+ session_scope TEXT,
3069
+ project_name TEXT,
3070
+ timeout_minutes INTEGER NOT NULL DEFAULT 120,
3071
+ on_partial_failure TEXT NOT NULL DEFAULT 'notify',
3072
+ barrier_fired INTEGER NOT NULL DEFAULT 0,
3073
+ barrier_fired_at TEXT,
3074
+ aggregated_result TEXT,
3075
+ created_at TEXT NOT NULL,
3076
+ updated_at TEXT NOT NULL
3077
+ )`,
3078
+ args: []
3079
+ });
3080
+ } catch (e) {
3081
+ logCatchDebug("migration", e);
3082
+ }
3083
+ await executeAddColumn(client, `ALTER TABLE tasks ADD COLUMN group_id TEXT`);
3084
+ try {
3085
+ await client.execute(
3086
+ `CREATE INDEX IF NOT EXISTS idx_tasks_group_id ON tasks(group_id) WHERE group_id IS NOT NULL`
3087
+ );
3088
+ } catch (e) {
3089
+ logCatchDebug("migration", e);
3090
+ }
3091
+ for (const col of [
3092
+ "ALTER TABLE tasks ADD COLUMN estimated_minutes INTEGER",
3093
+ "ALTER TABLE tasks ADD COLUMN estimate_confidence REAL",
3094
+ "ALTER TABLE tasks ADD COLUMN actual_minutes INTEGER",
3095
+ "ALTER TABLE tasks ADD COLUMN started_at TEXT"
3096
+ ]) {
3097
+ await executeAddColumn(client, col);
3098
+ }
3099
+ await executeAddColumn(client, `ALTER TABLE memories ADD COLUMN keywords TEXT DEFAULT ''`);
3100
+ try {
3101
+ await client.execute("SELECT keywords FROM memories_fts LIMIT 0");
3102
+ } catch {
3103
+ process.stderr.write("[database] Upgrading FTS5 to dual-column (raw_text + keywords)...\n");
3104
+ try {
3105
+ await client.executeMultiple(`
3106
+ DROP TRIGGER IF EXISTS memories_fts_ai;
3107
+ DROP TRIGGER IF EXISTS memories_fts_ad;
3108
+ DROP TRIGGER IF EXISTS memories_fts_au;
3109
+ DROP TRIGGER IF EXISTS memories_fts_soft_delete;
3110
+ DROP TABLE IF EXISTS memories_fts;
3111
+
3112
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
3113
+ raw_text,
3114
+ keywords,
3115
+ content='memories',
3116
+ content_rowid='rowid'
3117
+ );
3118
+
3119
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
3120
+ INSERT INTO memories_fts(rowid, raw_text, keywords) VALUES (new.rowid, new.raw_text, COALESCE(new.keywords, ''));
3121
+ END;
3122
+
3123
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories
3124
+ WHEN old.status IS NULL OR old.status != 'deleted' BEGIN
3125
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text, keywords) VALUES('delete', old.rowid, old.raw_text, COALESCE(old.keywords, ''));
3126
+ END;
3127
+
3128
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE OF raw_text, keywords ON memories
3129
+ WHEN new.status IS NULL OR new.status != 'deleted' BEGIN
3130
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text, keywords) VALUES('delete', old.rowid, old.raw_text, COALESCE(old.keywords, ''));
3131
+ INSERT INTO memories_fts(rowid, raw_text, keywords) VALUES (new.rowid, new.raw_text, COALESCE(new.keywords, ''));
3132
+ END;
3133
+
3134
+ CREATE TRIGGER IF NOT EXISTS memories_fts_soft_delete AFTER UPDATE ON memories
3135
+ WHEN new.status = 'deleted' AND (old.status IS NULL OR old.status != 'deleted') BEGIN
3136
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text, keywords) VALUES('delete', old.rowid, old.raw_text, COALESCE(old.keywords, ''));
3137
+ END;
3138
+ `);
3139
+ await client.execute("INSERT INTO memories_fts(memories_fts) VALUES('rebuild')");
3140
+ process.stderr.write("[database] FTS5 dual-column upgrade complete.\n");
3141
+ } catch (ftsUpgradeErr) {
3142
+ process.stderr.write(
3143
+ `[database] FTS5 upgrade failed: ${ftsUpgradeErr instanceof Error ? ftsUpgradeErr.message : String(ftsUpgradeErr)}
3144
+ `
3145
+ );
3146
+ }
3147
+ }
3148
+ const founderIdTables = ["memories", "tasks", "behaviors", "messages"];
3149
+ for (const table of founderIdTables) {
3150
+ await executeAddColumn(client, `ALTER TABLE ${table} ADD COLUMN founder_id TEXT DEFAULT NULL`);
3151
+ }
3152
+ await client.executeMultiple(`
3153
+ CREATE TABLE IF NOT EXISTS investigation_attempts (
3154
+ id TEXT PRIMARY KEY,
3155
+ session_id TEXT NOT NULL,
3156
+ task_id TEXT,
3157
+ agent_id TEXT NOT NULL,
3158
+ project_name TEXT,
3159
+ attempt_number INTEGER NOT NULL,
3160
+ investigation_key TEXT,
3161
+ hypothesis TEXT,
3162
+ confidence REAL,
3163
+ action_type TEXT,
3164
+ action_summary TEXT,
3165
+ verification_type TEXT,
3166
+ result TEXT NOT NULL,
3167
+ error_output TEXT,
3168
+ failure_category TEXT,
3169
+ lesson TEXT,
3170
+ timestamp TEXT NOT NULL,
3171
+ duration_ms INTEGER
3172
+ );
3173
+
3174
+ CREATE INDEX IF NOT EXISTS idx_investigation_session ON investigation_attempts(session_id);
3175
+ CREATE INDEX IF NOT EXISTS idx_investigation_agent ON investigation_attempts(agent_id);
3176
+ CREATE INDEX IF NOT EXISTS idx_investigation_key ON investigation_attempts(investigation_key);
3177
+ CREATE INDEX IF NOT EXISTS idx_investigation_result ON investigation_attempts(result);
3178
+ `);
3179
+ await client.executeMultiple(`
3180
+ CREATE TABLE IF NOT EXISTS behavior_attributions (
3181
+ id TEXT PRIMARY KEY,
3182
+ behavior_id TEXT NOT NULL,
3183
+ agent_id TEXT NOT NULL,
3184
+ investigation_key TEXT,
3185
+ attributed_at TEXT NOT NULL,
3186
+ FOREIGN KEY (behavior_id) REFERENCES behaviors(id)
3187
+ );
3188
+
3189
+ CREATE INDEX IF NOT EXISTS idx_attr_behavior ON behavior_attributions(behavior_id);
3190
+ `);
3191
+ try {
3192
+ await client.execute({
3193
+ sql: `CREATE TABLE IF NOT EXISTS co_activations (
3194
+ entity_a_id TEXT NOT NULL,
3195
+ entity_b_id TEXT NOT NULL,
3196
+ count INTEGER DEFAULT 1,
3197
+ last_activated_at TEXT DEFAULT (datetime('now')),
3198
+ PRIMARY KEY (entity_a_id, entity_b_id)
3199
+ )`,
3200
+ args: []
3201
+ });
3202
+ } catch (e) {
3203
+ logCatchDebug("migration", e);
3204
+ }
3205
+ await client.executeMultiple(`
3206
+ CREATE TABLE IF NOT EXISTS prospective_memories (
3207
+ id TEXT PRIMARY KEY,
3208
+ agent_id TEXT NOT NULL,
3209
+ content TEXT NOT NULL,
3210
+ due_date TEXT,
3211
+ recurrence TEXT NOT NULL DEFAULT 'once'
3212
+ CHECK (recurrence IN ('daily', 'weekly', 'monthly', 'once')),
3213
+ priority INTEGER NOT NULL DEFAULT 5,
3214
+ status TEXT NOT NULL DEFAULT 'pending'
3215
+ CHECK (status IN ('pending', 'completed', 'deferred')),
3216
+ last_surfaced TEXT,
3217
+ project_name TEXT,
3218
+ session_scope TEXT,
3219
+ created_at TEXT NOT NULL,
3220
+ updated_at TEXT NOT NULL
3221
+ );
3222
+
3223
+ CREATE INDEX IF NOT EXISTS idx_prospective_agent
3224
+ ON prospective_memories(agent_id);
3225
+
3226
+ CREATE INDEX IF NOT EXISTS idx_prospective_status
3227
+ ON prospective_memories(status);
3228
+
3229
+ CREATE INDEX IF NOT EXISTS idx_prospective_due
3230
+ ON prospective_memories(due_date);
3231
+
3232
+ CREATE INDEX IF NOT EXISTS idx_prospective_agent_status
3233
+ ON prospective_memories(agent_id, status);
3234
+ `);
3235
+ }
3236
+ var disposeTurso = disposeDatabase;
3237
+ async function disposeDatabase() {
3238
+ if (_walCheckpointTimer) {
3239
+ clearInterval(_walCheckpointTimer);
3240
+ _walCheckpointTimer = null;
3241
+ }
3242
+ if (_client) {
3243
+ try {
3244
+ await _client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3245
+ } catch (e) {
3246
+ logCatchDebug("WAL checkpoint", e);
3247
+ }
3248
+ }
3249
+ if (_daemonClient) {
3250
+ _daemonClient.close();
3251
+ _daemonClient = null;
3252
+ }
3253
+ if (_adapterClient && _adapterClient !== _resilientClient) {
3254
+ _adapterClient.close();
3255
+ }
3256
+ _adapterClient = null;
3257
+ if (_client) {
3258
+ _client.close();
3259
+ _client = null;
3260
+ _resilientClient = null;
3261
+ }
3262
+ releaseDbLock();
3263
+ }
3264
+
3265
+ export {
3266
+ WAL_ESCALATION_THRESHOLD_BYTES,
3267
+ checkpointWalIfLarge,
3268
+ initTurso,
3269
+ initDatabase,
3270
+ isInitialized,
3271
+ setExternalClient,
3272
+ SOFT_DELETE_RETENTION_DAYS,
3273
+ getClient,
3274
+ initDaemonClient,
3275
+ getRawClient,
3276
+ ensureSchema,
3277
+ disposeTurso,
3278
+ disposeDatabase,
3279
+ identityPath,
3280
+ getIdentity,
3281
+ updateIdentity,
3282
+ listIdentities,
3283
+ getIdentityInjection,
3284
+ EMPLOYEES_PATH,
3285
+ DEFAULT_BOOT_TIMEOUT_MS,
3286
+ DEFAULT_COORDINATOR_TEMPLATE_NAME,
3287
+ COORDINATOR_ROLE,
3288
+ normalizeRole,
3289
+ isCoordinatorRole,
3290
+ getCoordinatorEmployee,
3291
+ getCoordinatorName,
3292
+ getCoordinatorDisplayTitle,
3293
+ isCoordinatorName,
3294
+ canCoordinate,
3295
+ canManage,
3296
+ VALID_AGENT_NAME,
3297
+ VALID_AGENT_NAME_DESCRIPTION,
3298
+ validateEmployeeName,
3299
+ loadEmployees,
3300
+ saveEmployees,
3301
+ loadEmployeesSync,
3302
+ getEmployee,
3303
+ getEmployeeByRole,
3304
+ getEmployeeNamesByRole,
3305
+ hasRole,
3306
+ baseAgentName,
3307
+ isMultiInstance,
3308
+ shouldAutoInstance,
3309
+ addEmployee,
3310
+ hireEmployee,
3311
+ normalizeRosterCase,
3312
+ registerBinSymlinks
3313
+ };