exovault-mcp-server 1.0.0

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 (305) hide show
  1. package/dist/auth.d.ts +41 -0
  2. package/dist/auth.d.ts.map +1 -0
  3. package/dist/auth.js +236 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/auto-session.d.ts +39 -0
  6. package/dist/auto-session.d.ts.map +1 -0
  7. package/dist/auto-session.js +128 -0
  8. package/dist/auto-session.js.map +1 -0
  9. package/dist/buffer-persistence.d.ts +35 -0
  10. package/dist/buffer-persistence.d.ts.map +1 -0
  11. package/dist/buffer-persistence.js +110 -0
  12. package/dist/buffer-persistence.js.map +1 -0
  13. package/dist/coerce-params.d.ts +36 -0
  14. package/dist/coerce-params.d.ts.map +1 -0
  15. package/dist/coerce-params.js +120 -0
  16. package/dist/coerce-params.js.map +1 -0
  17. package/dist/crypto.d.ts +39 -0
  18. package/dist/crypto.d.ts.map +1 -0
  19. package/dist/crypto.js +119 -0
  20. package/dist/crypto.js.map +1 -0
  21. package/dist/db.d.ts +350 -0
  22. package/dist/db.d.ts.map +1 -0
  23. package/dist/db.js +866 -0
  24. package/dist/db.js.map +1 -0
  25. package/dist/embedding-config.d.ts +11 -0
  26. package/dist/embedding-config.d.ts.map +1 -0
  27. package/dist/embedding-config.js +24 -0
  28. package/dist/embedding-config.js.map +1 -0
  29. package/dist/entity-extraction.d.ts +22 -0
  30. package/dist/entity-extraction.d.ts.map +1 -0
  31. package/dist/entity-extraction.js +140 -0
  32. package/dist/entity-extraction.js.map +1 -0
  33. package/dist/episodic-headline.d.ts +6 -0
  34. package/dist/episodic-headline.d.ts.map +1 -0
  35. package/dist/episodic-headline.js +62 -0
  36. package/dist/episodic-headline.js.map +1 -0
  37. package/dist/error-sanitizer.d.ts +20 -0
  38. package/dist/error-sanitizer.d.ts.map +1 -0
  39. package/dist/error-sanitizer.js +54 -0
  40. package/dist/error-sanitizer.js.map +1 -0
  41. package/dist/extraction-budget.d.ts +39 -0
  42. package/dist/extraction-budget.d.ts.map +1 -0
  43. package/dist/extraction-budget.js +122 -0
  44. package/dist/extraction-budget.js.map +1 -0
  45. package/dist/extraction-llm.d.ts +22 -0
  46. package/dist/extraction-llm.d.ts.map +1 -0
  47. package/dist/extraction-llm.js +32 -0
  48. package/dist/extraction-llm.js.map +1 -0
  49. package/dist/extraction-prompt.d.ts +40 -0
  50. package/dist/extraction-prompt.d.ts.map +1 -0
  51. package/dist/extraction-prompt.js +176 -0
  52. package/dist/extraction-prompt.js.map +1 -0
  53. package/dist/gateway-client.d.ts +303 -0
  54. package/dist/gateway-client.d.ts.map +1 -0
  55. package/dist/gateway-client.js +285 -0
  56. package/dist/gateway-client.js.map +1 -0
  57. package/dist/gateway-init.d.ts +32 -0
  58. package/dist/gateway-init.d.ts.map +1 -0
  59. package/dist/gateway-init.js +71 -0
  60. package/dist/gateway-init.js.map +1 -0
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +1242 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/infer-task-status.d.ts +7 -0
  66. package/dist/infer-task-status.d.ts.map +1 -0
  67. package/dist/infer-task-status.js +23 -0
  68. package/dist/infer-task-status.js.map +1 -0
  69. package/dist/normalize-agent-id.d.ts +21 -0
  70. package/dist/normalize-agent-id.d.ts.map +1 -0
  71. package/dist/normalize-agent-id.js +54 -0
  72. package/dist/normalize-agent-id.js.map +1 -0
  73. package/dist/openai.d.ts +14 -0
  74. package/dist/openai.d.ts.map +1 -0
  75. package/dist/openai.js +43 -0
  76. package/dist/openai.js.map +1 -0
  77. package/dist/rlm/actions.d.ts +31 -0
  78. package/dist/rlm/actions.d.ts.map +1 -0
  79. package/dist/rlm/actions.js +241 -0
  80. package/dist/rlm/actions.js.map +1 -0
  81. package/dist/rlm/benchmark.d.ts +2 -0
  82. package/dist/rlm/benchmark.d.ts.map +1 -0
  83. package/dist/rlm/benchmark.js +215 -0
  84. package/dist/rlm/benchmark.js.map +1 -0
  85. package/dist/rlm/execute.d.ts +13 -0
  86. package/dist/rlm/execute.d.ts.map +1 -0
  87. package/dist/rlm/execute.js +366 -0
  88. package/dist/rlm/execute.js.map +1 -0
  89. package/dist/rlm/index.d.ts +6 -0
  90. package/dist/rlm/index.d.ts.map +1 -0
  91. package/dist/rlm/index.js +147 -0
  92. package/dist/rlm/index.js.map +1 -0
  93. package/dist/rlm/profiles.d.ts +9 -0
  94. package/dist/rlm/profiles.d.ts.map +1 -0
  95. package/dist/rlm/profiles.js +46 -0
  96. package/dist/rlm/profiles.js.map +1 -0
  97. package/dist/rlm/types.d.ts +98 -0
  98. package/dist/rlm/types.d.ts.map +1 -0
  99. package/dist/rlm/types.js +6 -0
  100. package/dist/rlm/types.js.map +1 -0
  101. package/dist/rlm/verify.d.ts +13 -0
  102. package/dist/rlm/verify.d.ts.map +1 -0
  103. package/dist/rlm/verify.js +58 -0
  104. package/dist/rlm/verify.js.map +1 -0
  105. package/dist/rlm/writeback.d.ts +7 -0
  106. package/dist/rlm/writeback.d.ts.map +1 -0
  107. package/dist/rlm/writeback.js +77 -0
  108. package/dist/rlm/writeback.js.map +1 -0
  109. package/dist/scripts/backfill-memory-embeddings.d.ts +2 -0
  110. package/dist/scripts/backfill-memory-embeddings.d.ts.map +1 -0
  111. package/dist/scripts/backfill-memory-embeddings.js +153 -0
  112. package/dist/scripts/backfill-memory-embeddings.js.map +1 -0
  113. package/dist/session-buffer.d.ts +104 -0
  114. package/dist/session-buffer.d.ts.map +1 -0
  115. package/dist/session-buffer.js +466 -0
  116. package/dist/session-buffer.js.map +1 -0
  117. package/dist/session-dedup.d.ts +30 -0
  118. package/dist/session-dedup.d.ts.map +1 -0
  119. package/dist/session-dedup.js +67 -0
  120. package/dist/session-dedup.js.map +1 -0
  121. package/dist/session-flush.d.ts +81 -0
  122. package/dist/session-flush.d.ts.map +1 -0
  123. package/dist/session-flush.js +169 -0
  124. package/dist/session-flush.js.map +1 -0
  125. package/dist/session-lifecycle.d.ts +72 -0
  126. package/dist/session-lifecycle.d.ts.map +1 -0
  127. package/dist/session-lifecycle.js +247 -0
  128. package/dist/session-lifecycle.js.map +1 -0
  129. package/dist/setup.d.ts +2 -0
  130. package/dist/setup.d.ts.map +1 -0
  131. package/dist/setup.js +260 -0
  132. package/dist/setup.js.map +1 -0
  133. package/dist/stopwords.d.ts +2 -0
  134. package/dist/stopwords.d.ts.map +1 -0
  135. package/dist/stopwords.js +20 -0
  136. package/dist/stopwords.js.map +1 -0
  137. package/dist/strip-html.d.ts +5 -0
  138. package/dist/strip-html.d.ts.map +1 -0
  139. package/dist/strip-html.js +35 -0
  140. package/dist/strip-html.js.map +1 -0
  141. package/dist/task-completion-flush.d.ts +36 -0
  142. package/dist/task-completion-flush.d.ts.map +1 -0
  143. package/dist/task-completion-flush.js +97 -0
  144. package/dist/task-completion-flush.js.map +1 -0
  145. package/dist/task-lifecycle-types.d.ts +13 -0
  146. package/dist/task-lifecycle-types.d.ts.map +1 -0
  147. package/dist/task-lifecycle-types.js +12 -0
  148. package/dist/task-lifecycle-types.js.map +1 -0
  149. package/dist/task-lifecycle.d.ts +78 -0
  150. package/dist/task-lifecycle.d.ts.map +1 -0
  151. package/dist/task-lifecycle.js +256 -0
  152. package/dist/task-lifecycle.js.map +1 -0
  153. package/dist/tools/agent-messages.d.ts +26 -0
  154. package/dist/tools/agent-messages.d.ts.map +1 -0
  155. package/dist/tools/agent-messages.js +123 -0
  156. package/dist/tools/agent-messages.js.map +1 -0
  157. package/dist/tools/agent-tasks.d.ts +24 -0
  158. package/dist/tools/agent-tasks.d.ts.map +1 -0
  159. package/dist/tools/agent-tasks.js +162 -0
  160. package/dist/tools/agent-tasks.js.map +1 -0
  161. package/dist/tools/archive-memory.d.ts +2 -0
  162. package/dist/tools/archive-memory.d.ts.map +1 -0
  163. package/dist/tools/archive-memory.js +19 -0
  164. package/dist/tools/archive-memory.js.map +1 -0
  165. package/dist/tools/blind-index.d.ts +29 -0
  166. package/dist/tools/blind-index.d.ts.map +1 -0
  167. package/dist/tools/blind-index.js +53 -0
  168. package/dist/tools/blind-index.js.map +1 -0
  169. package/dist/tools/cleanup-memories.d.ts +44 -0
  170. package/dist/tools/cleanup-memories.d.ts.map +1 -0
  171. package/dist/tools/cleanup-memories.js +126 -0
  172. package/dist/tools/cleanup-memories.js.map +1 -0
  173. package/dist/tools/context-checkpoint.d.ts +28 -0
  174. package/dist/tools/context-checkpoint.d.ts.map +1 -0
  175. package/dist/tools/context-checkpoint.js +140 -0
  176. package/dist/tools/context-checkpoint.js.map +1 -0
  177. package/dist/tools/context-profiles.d.ts +67 -0
  178. package/dist/tools/context-profiles.d.ts.map +1 -0
  179. package/dist/tools/context-profiles.js +30 -0
  180. package/dist/tools/context-profiles.js.map +1 -0
  181. package/dist/tools/create-note.d.ts +2 -0
  182. package/dist/tools/create-note.d.ts.map +1 -0
  183. package/dist/tools/create-note.js +60 -0
  184. package/dist/tools/create-note.js.map +1 -0
  185. package/dist/tools/create-vault.d.ts +5 -0
  186. package/dist/tools/create-vault.d.ts.map +1 -0
  187. package/dist/tools/create-vault.js +121 -0
  188. package/dist/tools/create-vault.js.map +1 -0
  189. package/dist/tools/decrypt-helpers.d.ts +31 -0
  190. package/dist/tools/decrypt-helpers.d.ts.map +1 -0
  191. package/dist/tools/decrypt-helpers.js +33 -0
  192. package/dist/tools/decrypt-helpers.js.map +1 -0
  193. package/dist/tools/delete-note.d.ts +2 -0
  194. package/dist/tools/delete-note.d.ts.map +1 -0
  195. package/dist/tools/delete-note.js +21 -0
  196. package/dist/tools/delete-note.js.map +1 -0
  197. package/dist/tools/explore-graph.d.ts +11 -0
  198. package/dist/tools/explore-graph.d.ts.map +1 -0
  199. package/dist/tools/explore-graph.js +169 -0
  200. package/dist/tools/explore-graph.js.map +1 -0
  201. package/dist/tools/get-related-memories.d.ts +2 -0
  202. package/dist/tools/get-related-memories.d.ts.map +1 -0
  203. package/dist/tools/get-related-memories.js +59 -0
  204. package/dist/tools/get-related-memories.js.map +1 -0
  205. package/dist/tools/knowledge-links.d.ts +17 -0
  206. package/dist/tools/knowledge-links.d.ts.map +1 -0
  207. package/dist/tools/knowledge-links.js +102 -0
  208. package/dist/tools/knowledge-links.js.map +1 -0
  209. package/dist/tools/list-active-agents.d.ts +5 -0
  210. package/dist/tools/list-active-agents.d.ts.map +1 -0
  211. package/dist/tools/list-active-agents.js +15 -0
  212. package/dist/tools/list-active-agents.js.map +1 -0
  213. package/dist/tools/list-notes.d.ts +2 -0
  214. package/dist/tools/list-notes.d.ts.map +1 -0
  215. package/dist/tools/list-notes.js +19 -0
  216. package/dist/tools/list-notes.js.map +1 -0
  217. package/dist/tools/list-vaults.d.ts +2 -0
  218. package/dist/tools/list-vaults.d.ts.map +1 -0
  219. package/dist/tools/list-vaults.js +19 -0
  220. package/dist/tools/list-vaults.js.map +1 -0
  221. package/dist/tools/mmr.d.ts +18 -0
  222. package/dist/tools/mmr.d.ts.map +1 -0
  223. package/dist/tools/mmr.js +67 -0
  224. package/dist/tools/mmr.js.map +1 -0
  225. package/dist/tools/read-memories.d.ts +2 -0
  226. package/dist/tools/read-memories.d.ts.map +1 -0
  227. package/dist/tools/read-memories.js +46 -0
  228. package/dist/tools/read-memories.js.map +1 -0
  229. package/dist/tools/read-note.d.ts +2 -0
  230. package/dist/tools/read-note.d.ts.map +1 -0
  231. package/dist/tools/read-note.js +35 -0
  232. package/dist/tools/read-note.js.map +1 -0
  233. package/dist/tools/read-notes.d.ts +6 -0
  234. package/dist/tools/read-notes.d.ts.map +1 -0
  235. package/dist/tools/read-notes.js +45 -0
  236. package/dist/tools/read-notes.js.map +1 -0
  237. package/dist/tools/resolve-vault-id.d.ts +6 -0
  238. package/dist/tools/resolve-vault-id.d.ts.map +1 -0
  239. package/dist/tools/resolve-vault-id.js +7 -0
  240. package/dist/tools/resolve-vault-id.js.map +1 -0
  241. package/dist/tools/rrf.d.ts +28 -0
  242. package/dist/tools/rrf.d.ts.map +1 -0
  243. package/dist/tools/rrf.js +19 -0
  244. package/dist/tools/rrf.js.map +1 -0
  245. package/dist/tools/search-and-read.d.ts +11 -0
  246. package/dist/tools/search-and-read.d.ts.map +1 -0
  247. package/dist/tools/search-and-read.js +208 -0
  248. package/dist/tools/search-and-read.js.map +1 -0
  249. package/dist/tools/search-memories.d.ts +13 -0
  250. package/dist/tools/search-memories.d.ts.map +1 -0
  251. package/dist/tools/search-memories.js +272 -0
  252. package/dist/tools/search-memories.js.map +1 -0
  253. package/dist/tools/search-notes.d.ts +2 -0
  254. package/dist/tools/search-notes.d.ts.map +1 -0
  255. package/dist/tools/search-notes.js +94 -0
  256. package/dist/tools/search-notes.js.map +1 -0
  257. package/dist/tools/semantic-search.d.ts +7 -0
  258. package/dist/tools/semantic-search.d.ts.map +1 -0
  259. package/dist/tools/semantic-search.js +85 -0
  260. package/dist/tools/semantic-search.js.map +1 -0
  261. package/dist/tools/session-start.d.ts +24 -0
  262. package/dist/tools/session-start.d.ts.map +1 -0
  263. package/dist/tools/session-start.js +256 -0
  264. package/dist/tools/session-start.js.map +1 -0
  265. package/dist/tools/stale-tasks.d.ts +22 -0
  266. package/dist/tools/stale-tasks.d.ts.map +1 -0
  267. package/dist/tools/stale-tasks.js +39 -0
  268. package/dist/tools/stale-tasks.js.map +1 -0
  269. package/dist/tools/temporal-decay.d.ts +21 -0
  270. package/dist/tools/temporal-decay.d.ts.map +1 -0
  271. package/dist/tools/temporal-decay.js +32 -0
  272. package/dist/tools/temporal-decay.js.map +1 -0
  273. package/dist/tools/update-memory.d.ts +19 -0
  274. package/dist/tools/update-memory.d.ts.map +1 -0
  275. package/dist/tools/update-memory.js +230 -0
  276. package/dist/tools/update-memory.js.map +1 -0
  277. package/dist/tools/update-note.d.ts +2 -0
  278. package/dist/tools/update-note.d.ts.map +1 -0
  279. package/dist/tools/update-note.js +79 -0
  280. package/dist/tools/update-note.js.map +1 -0
  281. package/dist/tools/vault-instruction-template.d.ts +17 -0
  282. package/dist/tools/vault-instruction-template.d.ts.map +1 -0
  283. package/dist/tools/vault-instruction-template.js +77 -0
  284. package/dist/tools/vault-instruction-template.js.map +1 -0
  285. package/dist/tools/wiki-link-sync.d.ts +34 -0
  286. package/dist/tools/wiki-link-sync.d.ts.map +1 -0
  287. package/dist/tools/wiki-link-sync.js +132 -0
  288. package/dist/tools/wiki-link-sync.js.map +1 -0
  289. package/dist/tools/wrap-tool-handler.d.ts +8 -0
  290. package/dist/tools/wrap-tool-handler.d.ts.map +1 -0
  291. package/dist/tools/wrap-tool-handler.js +32 -0
  292. package/dist/tools/wrap-tool-handler.js.map +1 -0
  293. package/dist/tools/write-memory.d.ts +34 -0
  294. package/dist/tools/write-memory.d.ts.map +1 -0
  295. package/dist/tools/write-memory.js +359 -0
  296. package/dist/tools/write-memory.js.map +1 -0
  297. package/dist/usage.d.ts +11 -0
  298. package/dist/usage.d.ts.map +1 -0
  299. package/dist/usage.js +38 -0
  300. package/dist/usage.js.map +1 -0
  301. package/dist/wiki-link-parser.d.ts +27 -0
  302. package/dist/wiki-link-parser.d.ts.map +1 -0
  303. package/dist/wiki-link-parser.js +93 -0
  304. package/dist/wiki-link-parser.js.map +1 -0
  305. package/package.json +38 -0
package/dist/db.js ADDED
@@ -0,0 +1,866 @@
1
+ import { sanitizeDbError } from "./error-sanitizer.js";
2
+ // ─── Query helpers ────────────────────────────────────────────────────────────
3
+ export async function getVaults(supabase, userId) {
4
+ const { data, error } = await supabase
5
+ .from("vaults")
6
+ .select("*")
7
+ .eq("user_id", userId)
8
+ .order("sort_order", { ascending: true });
9
+ if (error)
10
+ throw new Error(sanitizeDbError("fetch vaults", error.message));
11
+ return data ?? [];
12
+ }
13
+ export async function getNotes(supabase, userId, vaultId, limit = 20) {
14
+ let query = supabase
15
+ .from("notes")
16
+ .select("*")
17
+ .eq("user_id", userId)
18
+ .eq("is_trashed", false)
19
+ .order("updated_at", { ascending: false })
20
+ .limit(limit);
21
+ if (vaultId) {
22
+ query = query.eq("vault_id", vaultId);
23
+ }
24
+ const { data, error } = await query;
25
+ if (error)
26
+ throw new Error(sanitizeDbError("fetch notes", error.message));
27
+ return data ?? [];
28
+ }
29
+ export async function getNote(supabase, userId, noteId) {
30
+ const { data, error } = await supabase
31
+ .from("notes")
32
+ .select("*")
33
+ .eq("id", noteId)
34
+ .eq("user_id", userId)
35
+ .single();
36
+ if (error) {
37
+ if (error.code === "PGRST116")
38
+ return null; // not found
39
+ throw new Error(sanitizeDbError("fetch note", error.message));
40
+ }
41
+ return data;
42
+ }
43
+ export async function getVault(supabase, userId, vaultId) {
44
+ const { data, error } = await supabase
45
+ .from("vaults")
46
+ .select("*")
47
+ .eq("id", vaultId)
48
+ .eq("user_id", userId)
49
+ .single();
50
+ if (error) {
51
+ if (error.code === "PGRST116")
52
+ return null;
53
+ throw new Error(sanitizeDbError("fetch vault", error.message));
54
+ }
55
+ return data;
56
+ }
57
+ export async function countNotesByVault(supabase, userId) {
58
+ const { data, error } = await supabase
59
+ .from("notes")
60
+ .select("vault_id")
61
+ .eq("user_id", userId)
62
+ .eq("is_trashed", false)
63
+ .limit(10_000);
64
+ if (error)
65
+ throw new Error(sanitizeDbError("count notes", error.message));
66
+ const counts = {};
67
+ for (const row of data ?? []) {
68
+ counts[row.vault_id] = (counts[row.vault_id] ?? 0) + 1;
69
+ }
70
+ return counts;
71
+ }
72
+ export async function insertNote(supabase, data) {
73
+ const { data: row, error } = await supabase
74
+ .from("notes")
75
+ .insert(data)
76
+ .select("id")
77
+ .single();
78
+ if (error)
79
+ throw new Error(sanitizeDbError("insert note", error.message));
80
+ return row;
81
+ }
82
+ export async function updateNote(supabase, noteId, userId, updates) {
83
+ const { error } = await supabase
84
+ .from("notes")
85
+ .update({ ...updates, updated_at: new Date().toISOString() })
86
+ .eq("id", noteId)
87
+ .eq("user_id", userId);
88
+ if (error)
89
+ throw new Error(sanitizeDbError("update note", error.message));
90
+ }
91
+ export async function deleteNote(supabase, noteId, userId) {
92
+ const { error } = await supabase
93
+ .from("notes")
94
+ .delete()
95
+ .eq("id", noteId)
96
+ .eq("user_id", userId);
97
+ if (error)
98
+ throw new Error(sanitizeDbError("delete note", error.message));
99
+ }
100
+ // ─── Memory helpers ───────────────────────────────────────────────────────────
101
+ export async function getMemories(supabase, userId, options) {
102
+ const includeArchived = options?.includeArchived ?? false;
103
+ const limit = options?.limit ?? 50;
104
+ const orderBy = options?.orderBy ?? "updated_at";
105
+ let query = supabase
106
+ .from("memories")
107
+ .select("*")
108
+ .eq("user_id", userId)
109
+ .order(orderBy, { ascending: false })
110
+ .limit(limit);
111
+ if (!includeArchived)
112
+ query = query.eq("is_archived", false);
113
+ if (options?.vaultId)
114
+ query = query.eq("vault_id", options.vaultId);
115
+ if (options?.memoryType)
116
+ query = query.eq("memory_type", options.memoryType);
117
+ if (options?.minImportance)
118
+ query = query.gte("importance", options.minImportance);
119
+ const { data, error } = await query;
120
+ if (error)
121
+ throw new Error(sanitizeDbError("fetch memories", error.message));
122
+ return (data ?? []);
123
+ }
124
+ export async function getMemory(supabase, userId, memoryId) {
125
+ const { data, error } = await supabase
126
+ .from("memories")
127
+ .select("*")
128
+ .eq("id", memoryId)
129
+ .eq("user_id", userId)
130
+ .single();
131
+ if (error) {
132
+ if (error.code === "PGRST116")
133
+ return null;
134
+ throw new Error(sanitizeDbError("fetch memory", error.message));
135
+ }
136
+ return data;
137
+ }
138
+ export async function getMemoryByExternalWriteId(supabase, userId, externalWriteId) {
139
+ const { data, error } = await supabase
140
+ .from("memories")
141
+ .select("*")
142
+ .eq("user_id", userId)
143
+ .eq("external_write_id", externalWriteId)
144
+ .limit(1);
145
+ if (error)
146
+ throw new Error(sanitizeDbError("fetch memory by externalWriteId", error.message));
147
+ return (data && data.length > 0 ? data[0] : null);
148
+ }
149
+ export async function insertMemory(supabase, data) {
150
+ const { data: row, error } = await supabase
151
+ .from("memories")
152
+ .insert({
153
+ ...data,
154
+ indexing_status: data.indexing_status ?? "pending",
155
+ importance: data.importance ?? 3,
156
+ confidence: data.confidence ?? 3,
157
+ })
158
+ .select("*")
159
+ .single();
160
+ if (error)
161
+ throw new Error(sanitizeDbError("insert memory", error.message));
162
+ return row;
163
+ }
164
+ export async function updateMemory(supabase, memoryId, userId, updates) {
165
+ const { data, error } = await supabase
166
+ .from("memories")
167
+ .update({ ...updates, updated_at: new Date().toISOString() })
168
+ .eq("id", memoryId)
169
+ .eq("user_id", userId)
170
+ .select("*")
171
+ .single();
172
+ if (error)
173
+ throw new Error(sanitizeDbError("update memory", error.message));
174
+ return data;
175
+ }
176
+ /**
177
+ * Update last_accessed_at for a batch of memory IDs.
178
+ * Fire-and-forget — errors are silently ignored so this never blocks the caller.
179
+ *
180
+ * Tries RPC `increment_memory_access` for atomic counter increment.
181
+ * Falls back to simple timestamp update if RPC doesn't exist yet.
182
+ */
183
+ export async function touchMemories(supabase, userId, memoryIds) {
184
+ if (memoryIds.length === 0)
185
+ return;
186
+ try {
187
+ const now = new Date().toISOString();
188
+ await Promise.all(memoryIds.map(async (id) => {
189
+ const { error: rpcError } = await supabase.rpc("increment_memory_access", {
190
+ p_memory_id: id,
191
+ p_user_id: userId,
192
+ p_accessed_at: now,
193
+ });
194
+ if (rpcError) {
195
+ // RPC doesn't exist yet — just update timestamp
196
+ await supabase
197
+ .from("memories")
198
+ .update({ last_accessed_at: now })
199
+ .eq("id", id)
200
+ .eq("user_id", userId);
201
+ }
202
+ }));
203
+ }
204
+ catch {
205
+ // Fire-and-forget — never fail the parent operation
206
+ }
207
+ }
208
+ export async function archiveMemory(supabase, memoryId, userId, archived) {
209
+ const { error } = await supabase
210
+ .from("memories")
211
+ .update({ is_archived: archived, updated_at: new Date().toISOString() })
212
+ .eq("id", memoryId)
213
+ .eq("user_id", userId);
214
+ if (error)
215
+ throw new Error(sanitizeDbError("archive memory", error.message));
216
+ }
217
+ export async function deleteMemory(supabase, memoryId, userId) {
218
+ const { error } = await supabase
219
+ .from("memories")
220
+ .delete()
221
+ .eq("id", memoryId)
222
+ .eq("user_id", userId);
223
+ if (error)
224
+ throw new Error(sanitizeDbError("delete memory", error.message));
225
+ }
226
+ export async function replaceMemoryEmbeddings(supabase, input) {
227
+ const { error: deleteError } = await supabase
228
+ .from("memory_embeddings")
229
+ .delete()
230
+ .eq("memory_id", input.memoryId)
231
+ .eq("user_id", input.userId);
232
+ if (deleteError) {
233
+ throw new Error(sanitizeDbError("delete memory embeddings", deleteError.message));
234
+ }
235
+ if (input.rows.length === 0)
236
+ return;
237
+ const { error: insertError } = await supabase
238
+ .from("memory_embeddings")
239
+ .insert(input.rows.map((row) => ({
240
+ memory_id: input.memoryId,
241
+ user_id: input.userId,
242
+ chunk_index: row.chunkIndex,
243
+ embedding: row.embedding,
244
+ content_hash: input.contentHash,
245
+ embedding_model: input.embeddingModel,
246
+ token_count: row.tokenCount ?? null,
247
+ })));
248
+ if (insertError) {
249
+ throw new Error(sanitizeDbError("insert memory embeddings", insertError.message));
250
+ }
251
+ }
252
+ /**
253
+ * Fetch embeddings for a batch of memory IDs.
254
+ * Returns one embedding per memory (chunk_index = 0 preferred).
255
+ */
256
+ export async function getEmbeddingsForMemories(supabase, userId, memoryIds) {
257
+ if (memoryIds.length === 0)
258
+ return [];
259
+ const { data, error } = await supabase
260
+ .from("memory_embeddings")
261
+ .select("memory_id, embedding")
262
+ .eq("user_id", userId)
263
+ .in("memory_id", memoryIds)
264
+ .order("chunk_index", { ascending: true });
265
+ if (error)
266
+ throw new Error(sanitizeDbError("fetch memory embeddings", error.message));
267
+ // Keep only the first embedding per memory (chunk_index=0)
268
+ const seen = new Set();
269
+ const result = [];
270
+ for (const row of data ?? []) {
271
+ if (!seen.has(row.memory_id)) {
272
+ seen.add(row.memory_id);
273
+ result.push({ memory_id: row.memory_id, embedding: row.embedding });
274
+ }
275
+ }
276
+ return result;
277
+ }
278
+ export async function matchNoteEmbeddings(supabase, embedding, userId, threshold = 0.5, count = 20) {
279
+ const { data, error } = await supabase.rpc("match_note_embeddings", {
280
+ query_embedding: JSON.stringify(embedding),
281
+ match_threshold: threshold,
282
+ match_count: count,
283
+ p_user_id: userId,
284
+ });
285
+ if (error)
286
+ throw new Error(sanitizeDbError("match note embeddings", error.message));
287
+ return (data ?? []);
288
+ }
289
+ export async function matchMemoryEmbeddings(supabase, embedding, userId, threshold = 0.5, count = 20) {
290
+ const { data, error } = await supabase.rpc("match_memory_embeddings", {
291
+ query_embedding: JSON.stringify(embedding),
292
+ match_threshold: threshold,
293
+ match_count: count,
294
+ p_user_id: userId,
295
+ });
296
+ if (error)
297
+ throw new Error(sanitizeDbError("match memory embeddings", error.message));
298
+ return (data ?? []);
299
+ }
300
+ /**
301
+ * Searches memories by HMAC-hashed blind tokens (privacy-preserving keyword search).
302
+ * Returns memory IDs ranked by number of matching tokens.
303
+ */
304
+ export async function matchMemoriesByBlindTokens(supabase, tokenHashes, userId, vaultId, count = 20, includeArchived = false) {
305
+ if (tokenHashes.length === 0)
306
+ return [];
307
+ const { data, error } = await supabase.rpc("match_memories_by_blind_tokens", {
308
+ p_user_id: userId,
309
+ p_token_hashes: tokenHashes,
310
+ p_vault_id: vaultId ?? null,
311
+ p_match_count: count,
312
+ p_include_archived: includeArchived,
313
+ });
314
+ if (error)
315
+ throw new Error(sanitizeDbError("match memories by blind tokens", error.message));
316
+ return (data ?? []);
317
+ }
318
+ /**
319
+ * Searches notes by HMAC-hashed blind tokens (privacy-preserving keyword search).
320
+ * Returns note IDs ranked by number of matching tokens.
321
+ */
322
+ export async function matchNotesByBlindTokens(supabase, tokenHashes, userId, vaultId, count = 20, includeTrashed = false) {
323
+ if (tokenHashes.length === 0)
324
+ return [];
325
+ const { data, error } = await supabase.rpc("match_notes_by_blind_tokens", {
326
+ p_user_id: userId,
327
+ p_token_hashes: tokenHashes,
328
+ p_vault_id: vaultId ?? null,
329
+ p_match_count: count,
330
+ p_include_trashed: includeTrashed,
331
+ });
332
+ if (error)
333
+ throw new Error(sanitizeDbError("match notes by blind tokens", error.message));
334
+ return (data ?? []);
335
+ }
336
+ // ─── Batch fetch helper ──────────────────────────────────────────────────────
337
+ export async function getNotesByIds(supabase, userId, noteIds) {
338
+ if (noteIds.length === 0)
339
+ return [];
340
+ const { data, error } = await supabase
341
+ .from("notes")
342
+ .select("*")
343
+ .eq("user_id", userId)
344
+ .eq("is_trashed", false)
345
+ .in("id", noteIds);
346
+ if (error)
347
+ throw new Error(sanitizeDbError("fetch notes by IDs", error.message));
348
+ return data ?? [];
349
+ }
350
+ export async function getNoteByImportSource(supabase, userId, vaultId, importSource, importSourceId) {
351
+ const { data, error } = await supabase
352
+ .from("notes")
353
+ .select("*")
354
+ .eq("user_id", userId)
355
+ .eq("vault_id", vaultId)
356
+ .eq("import_source", importSource)
357
+ .eq("import_source_id", importSourceId)
358
+ .limit(1);
359
+ if (error)
360
+ throw new Error(sanitizeDbError("fetch note by import source", error.message));
361
+ return (data && data.length > 0 ? data[0] : null);
362
+ }
363
+ export async function getMemoriesByIds(supabase, userId, memoryIds) {
364
+ if (memoryIds.length === 0)
365
+ return [];
366
+ const { data, error } = await supabase
367
+ .from("memories")
368
+ .select("*")
369
+ .eq("user_id", userId)
370
+ .in("id", memoryIds);
371
+ if (error)
372
+ throw new Error(sanitizeDbError("fetch memories by IDs", error.message));
373
+ return (data ?? []);
374
+ }
375
+ export async function getActiveAgents(supabase, userId, sinceDays = 30, limit = 20) {
376
+ const since = new Date();
377
+ since.setDate(since.getDate() - sinceDays);
378
+ const { data, error } = await supabase
379
+ .from("memories")
380
+ .select("agent_id, model_id, updated_at")
381
+ .eq("user_id", userId)
382
+ .eq("is_archived", false)
383
+ .gte("updated_at", since.toISOString())
384
+ .limit(10_000);
385
+ if (error)
386
+ throw new Error(sanitizeDbError("fetch active agents", error.message));
387
+ // Aggregate in JS since Supabase client doesn't support GROUP BY
388
+ const agentMap = new Map();
389
+ for (const row of data ?? []) {
390
+ const key = `${row.agent_id ?? "unknown"}::${row.model_id ?? "unknown"}`;
391
+ const existing = agentMap.get(key);
392
+ if (existing) {
393
+ existing.count++;
394
+ if (row.updated_at > existing.lastActiveAt) {
395
+ existing.lastActiveAt = row.updated_at;
396
+ }
397
+ }
398
+ else {
399
+ agentMap.set(key, {
400
+ modelId: row.model_id,
401
+ count: 1,
402
+ lastActiveAt: row.updated_at,
403
+ });
404
+ }
405
+ }
406
+ const agents = [];
407
+ for (const [key, value] of agentMap) {
408
+ const agentId = key.split("::")[0];
409
+ agents.push({
410
+ agentId,
411
+ modelId: value.modelId,
412
+ memoryCount: value.count,
413
+ lastActiveAt: value.lastActiveAt,
414
+ });
415
+ }
416
+ agents.sort((a, b) => b.lastActiveAt.localeCompare(a.lastActiveAt));
417
+ return agents.slice(0, limit);
418
+ }
419
+ export async function getMemoryHealthStats(supabase, userId, vaultId) {
420
+ let query = supabase
421
+ .from("memories")
422
+ .select("indexing_status, is_archived")
423
+ .eq("user_id", userId);
424
+ if (vaultId)
425
+ query = query.eq("vault_id", vaultId);
426
+ query = query.limit(10_000);
427
+ const { data, error } = await query;
428
+ if (error)
429
+ throw new Error(sanitizeDbError("fetch memory health", error.message));
430
+ const rows = (data ?? []);
431
+ const stats = { total: rows.length, ready: 0, pending: 0, failed: 0, archived: 0 };
432
+ for (const row of rows) {
433
+ if (row.is_archived) {
434
+ stats.archived++;
435
+ }
436
+ else if (row.indexing_status === "ready") {
437
+ stats.ready++;
438
+ }
439
+ else if (row.indexing_status === "pending") {
440
+ stats.pending++;
441
+ }
442
+ else if (row.indexing_status === "failed") {
443
+ stats.failed++;
444
+ }
445
+ }
446
+ return stats;
447
+ }
448
+ // ─── Entity + relationship helpers ──────────────────────────────────────────
449
+ export async function searchMemoriesByEntity(supabase, userId, entity, limit = 20) {
450
+ const { data, error } = await supabase
451
+ .from("memories")
452
+ .select("*")
453
+ .eq("user_id", userId)
454
+ .contains("entities", JSON.stringify([entity]))
455
+ .order("updated_at", { ascending: false })
456
+ .limit(limit);
457
+ if (error)
458
+ throw new Error(sanitizeDbError("search memories by entity", error.message));
459
+ return (data ?? []);
460
+ }
461
+ export async function getRelatedMemoriesById(supabase, userId, memoryId, limit = 20) {
462
+ // Fetch the source memory
463
+ const source = await getMemory(supabase, userId, memoryId);
464
+ if (!source)
465
+ return { source: null, related: [], linkedNotes: [], entityMatches: [], supersessionChain: [] };
466
+ // Query knowledge_links for this memory (both directions)
467
+ const links = await getLinksForNode(supabase, userId, "memory", memoryId, {
468
+ direction: "both",
469
+ limit: limit * 2,
470
+ });
471
+ // Separate linked memory IDs and linked note IDs from knowledge_links
472
+ const linkedMemoryIds = new Set();
473
+ const linkedNoteIds = new Set();
474
+ for (const link of links) {
475
+ const otherId = link.source_id === memoryId ? link.target_id : link.source_id;
476
+ const otherType = link.source_id === memoryId ? link.target_type : link.source_type;
477
+ if (otherType === "memory" && otherId !== memoryId) {
478
+ linkedMemoryIds.add(otherId);
479
+ }
480
+ else if (otherType === "note") {
481
+ linkedNoteIds.add(otherId);
482
+ }
483
+ }
484
+ // Fallback: also check JSONB related_memory_ids (for links not yet migrated)
485
+ for (const r of source.related_memory_ids ?? []) {
486
+ linkedMemoryIds.add(r.memoryId);
487
+ }
488
+ // Fallback: also check JSONB source_note_ids
489
+ for (const noteId of source.source_note_ids ?? []) {
490
+ linkedNoteIds.add(noteId);
491
+ }
492
+ const related = linkedMemoryIds.size > 0
493
+ ? await getMemoriesByIds(supabase, userId, Array.from(linkedMemoryIds))
494
+ : [];
495
+ // Fetch linked notes
496
+ const linkedNotes = linkedNoteIds.size > 0
497
+ ? await getNotesByIds(supabase, userId, Array.from(linkedNoteIds))
498
+ : [];
499
+ // Entity-based matches (excluding self and already-related)
500
+ const entities = source.entities ?? [];
501
+ let entityMatches = [];
502
+ if (entities.length > 0) {
503
+ const allEntityResults = [];
504
+ for (const entity of entities.slice(0, 5)) {
505
+ const matches = await searchMemoriesByEntity(supabase, userId, entity, limit);
506
+ allEntityResults.push(...matches);
507
+ }
508
+ const seen = new Set([memoryId, ...linkedMemoryIds]);
509
+ entityMatches = allEntityResults
510
+ .filter((m) => {
511
+ if (seen.has(m.id))
512
+ return false;
513
+ seen.add(m.id);
514
+ return true;
515
+ })
516
+ .slice(0, limit);
517
+ }
518
+ // Supersession chain: follow superseded_by_id forward, and look for memories that point to this one
519
+ const supersessionChain = [];
520
+ if (source.superseded_by_id) {
521
+ const successor = await getMemory(supabase, userId, source.superseded_by_id);
522
+ if (successor)
523
+ supersessionChain.push(successor);
524
+ }
525
+ const { data: predecessors, error: predError } = await supabase
526
+ .from("memories")
527
+ .select("*")
528
+ .eq("user_id", userId)
529
+ .eq("superseded_by_id", memoryId)
530
+ .limit(limit);
531
+ if (!predError && predecessors) {
532
+ supersessionChain.push(...predecessors);
533
+ }
534
+ return { source, related, linkedNotes, entityMatches, supersessionChain };
535
+ }
536
+ export async function insertKnowledgeLink(supabase, data) {
537
+ const { data: row, error } = await supabase
538
+ .from("knowledge_links")
539
+ .insert({
540
+ user_id: data.user_id,
541
+ source_type: data.source_type,
542
+ source_id: data.source_id,
543
+ target_type: data.target_type,
544
+ target_id: data.target_id,
545
+ relation_type: data.relation_type,
546
+ encrypted_label: data.encrypted_label ?? null,
547
+ label_iv: data.label_iv ?? null,
548
+ is_pending: data.is_pending ?? false,
549
+ pending_target_hash: data.pending_target_hash ?? null,
550
+ created_by: data.created_by ?? null,
551
+ })
552
+ .select()
553
+ .single();
554
+ if (error)
555
+ throw new Error(sanitizeDbError("insert knowledge link", error.message));
556
+ return row;
557
+ }
558
+ export async function deleteKnowledgeLink(supabase, linkId, userId) {
559
+ const { error } = await supabase
560
+ .from("knowledge_links")
561
+ .delete()
562
+ .eq("id", linkId)
563
+ .eq("user_id", userId);
564
+ if (error)
565
+ throw new Error(sanitizeDbError("delete knowledge link", error.message));
566
+ }
567
+ export async function getLinksForNode(supabase, userId, nodeType, nodeId, options) {
568
+ const direction = options?.direction ?? "both";
569
+ const limit = options?.limit ?? 100;
570
+ if (direction === "outgoing") {
571
+ const { data, error } = await supabase
572
+ .from("knowledge_links")
573
+ .select()
574
+ .eq("user_id", userId)
575
+ .eq("source_type", nodeType)
576
+ .eq("source_id", nodeId)
577
+ .order("created_at", { ascending: false })
578
+ .limit(limit);
579
+ if (error)
580
+ throw new Error(sanitizeDbError("fetch outgoing links", error.message));
581
+ return (data ?? []);
582
+ }
583
+ if (direction === "incoming") {
584
+ const { data, error } = await supabase
585
+ .from("knowledge_links")
586
+ .select()
587
+ .eq("user_id", userId)
588
+ .eq("target_type", nodeType)
589
+ .eq("target_id", nodeId)
590
+ .order("created_at", { ascending: false })
591
+ .limit(limit);
592
+ if (error)
593
+ throw new Error(sanitizeDbError("fetch incoming links", error.message));
594
+ return (data ?? []);
595
+ }
596
+ // Both directions — two queries merged
597
+ const [outgoing, incoming] = await Promise.all([
598
+ supabase
599
+ .from("knowledge_links")
600
+ .select()
601
+ .eq("user_id", userId)
602
+ .eq("source_type", nodeType)
603
+ .eq("source_id", nodeId)
604
+ .order("created_at", { ascending: false })
605
+ .limit(limit),
606
+ supabase
607
+ .from("knowledge_links")
608
+ .select()
609
+ .eq("user_id", userId)
610
+ .eq("target_type", nodeType)
611
+ .eq("target_id", nodeId)
612
+ .order("created_at", { ascending: false })
613
+ .limit(limit),
614
+ ]);
615
+ if (outgoing.error)
616
+ throw new Error(sanitizeDbError("fetch outgoing links", outgoing.error.message));
617
+ if (incoming.error)
618
+ throw new Error(sanitizeDbError("fetch incoming links", incoming.error.message));
619
+ // Deduplicate by link ID (a link could appear in both if source and target are same type)
620
+ const seen = new Set();
621
+ const merged = [];
622
+ for (const row of [...(outgoing.data ?? []), ...(incoming.data ?? [])]) {
623
+ const link = row;
624
+ if (!seen.has(link.id)) {
625
+ seen.add(link.id);
626
+ merged.push(link);
627
+ }
628
+ }
629
+ return merged.slice(0, limit);
630
+ }
631
+ export async function getBacklinks(supabase, userId, nodeType, nodeId, limit = 50) {
632
+ return getLinksForNode(supabase, userId, nodeType, nodeId, {
633
+ direction: "incoming",
634
+ limit,
635
+ });
636
+ }
637
+ export async function deleteLinksForNode(supabase, userId, nodeType, nodeId) {
638
+ // Delete outgoing links
639
+ const { error: outErr } = await supabase
640
+ .from("knowledge_links")
641
+ .delete()
642
+ .eq("user_id", userId)
643
+ .eq("source_type", nodeType)
644
+ .eq("source_id", nodeId);
645
+ if (outErr)
646
+ throw new Error(sanitizeDbError("delete outgoing links", outErr.message));
647
+ // Delete incoming links
648
+ const { error: inErr } = await supabase
649
+ .from("knowledge_links")
650
+ .delete()
651
+ .eq("user_id", userId)
652
+ .eq("target_type", nodeType)
653
+ .eq("target_id", nodeId);
654
+ if (inErr)
655
+ throw new Error(sanitizeDbError("delete incoming links", inErr.message));
656
+ }
657
+ export async function bulkInsertKnowledgeLinks(supabase, links) {
658
+ if (links.length === 0)
659
+ return 0;
660
+ const rows = links.map((l) => ({
661
+ user_id: l.user_id,
662
+ source_type: l.source_type,
663
+ source_id: l.source_id,
664
+ target_type: l.target_type,
665
+ target_id: l.target_id,
666
+ relation_type: l.relation_type,
667
+ encrypted_label: l.encrypted_label ?? null,
668
+ label_iv: l.label_iv ?? null,
669
+ is_pending: l.is_pending ?? false,
670
+ pending_target_hash: l.pending_target_hash ?? null,
671
+ created_by: l.created_by ?? null,
672
+ }));
673
+ // Use upsert with ignoreDuplicates to handle unique constraint violations
674
+ const { data, error } = await supabase
675
+ .from("knowledge_links")
676
+ .upsert(rows, { onConflict: "user_id,source_type,source_id,target_type,target_id,relation_type", ignoreDuplicates: true })
677
+ .select("id");
678
+ if (error)
679
+ throw new Error(sanitizeDbError("bulk insert knowledge links", error.message));
680
+ return data?.length ?? 0;
681
+ }
682
+ /**
683
+ * Resolves pending wiki-links by matching stored SHA-256 hashes against
684
+ * candidate note names. Caller provides a map of hash → noteId.
685
+ * Use `hashPendingTarget()` from crypto.ts to build the map keys.
686
+ */
687
+ export async function resolvePendingLinks(supabase, userId, hashToNoteId) {
688
+ if (hashToNoteId.size === 0)
689
+ return 0;
690
+ // Fetch all pending links for this user
691
+ const { data: pending, error } = await supabase
692
+ .from("knowledge_links")
693
+ .select()
694
+ .eq("user_id", userId)
695
+ .eq("is_pending", true)
696
+ .limit(10_000);
697
+ if (error)
698
+ throw new Error(sanitizeDbError("fetch pending links", error.message));
699
+ if (!pending || pending.length === 0)
700
+ return 0;
701
+ let resolved = 0;
702
+ for (const link of pending) {
703
+ if (!link.pending_target_hash)
704
+ continue;
705
+ const noteId = hashToNoteId.get(link.pending_target_hash);
706
+ if (!noteId)
707
+ continue;
708
+ const { error: updateErr } = await supabase
709
+ .from("knowledge_links")
710
+ .update({
711
+ target_id: noteId,
712
+ target_type: "note",
713
+ is_pending: false,
714
+ pending_target_hash: null,
715
+ })
716
+ .eq("id", link.id)
717
+ .eq("user_id", userId);
718
+ if (!updateErr)
719
+ resolved++;
720
+ }
721
+ return resolved;
722
+ }
723
+ export async function getKnowledgeGraph(supabase, userId, nodeType, nodeId, maxHops = 2, maxNodes = 50) {
724
+ const { data, error } = await supabase.rpc("get_knowledge_graph", {
725
+ p_user_id: userId,
726
+ p_node_type: nodeType,
727
+ p_node_id: nodeId,
728
+ p_max_hops: maxHops,
729
+ p_max_nodes: maxNodes,
730
+ });
731
+ if (error)
732
+ throw new Error(sanitizeDbError("traverse knowledge graph", error.message));
733
+ return (data ?? []);
734
+ }
735
+ export async function insertAgentMessage(supabase, data) {
736
+ const { data: row, error } = await supabase
737
+ .from("agent_messages")
738
+ .insert({
739
+ user_id: data.user_id,
740
+ vault_id: data.vault_id ?? null,
741
+ sender_type: data.sender_type,
742
+ sender_id: data.sender_id,
743
+ target_type: data.target_type,
744
+ target_id: data.target_id,
745
+ category: data.category ?? "info",
746
+ priority: data.priority ?? 3,
747
+ subject: data.subject ?? null,
748
+ encrypted_content: data.encrypted_content,
749
+ content_iv: data.content_iv,
750
+ expires_at: data.expires_at ?? undefined,
751
+ metadata: data.metadata ?? null,
752
+ parent_message_id: data.parent_message_id ?? null,
753
+ thread_id: data.thread_id ?? null,
754
+ })
755
+ .select("*")
756
+ .single();
757
+ if (error)
758
+ throw new Error(sanitizeDbError("insert agent message", error.message));
759
+ return row;
760
+ }
761
+ /**
762
+ * Fetch pending messages for a target agent (or broadcast).
763
+ * Returns priority-sorted, non-expired messages.
764
+ */
765
+ export async function getPendingMessages(supabase, userId, targetId, limit = 5) {
766
+ const { data, error } = await supabase
767
+ .from("agent_messages")
768
+ .select("*")
769
+ .eq("user_id", userId)
770
+ .eq("status", "pending")
771
+ .or(`target_id.eq.${targetId},target_id.eq.*`)
772
+ .gte("expires_at", new Date().toISOString())
773
+ .order("priority", { ascending: false })
774
+ .order("created_at", { ascending: true })
775
+ .limit(limit);
776
+ if (error)
777
+ throw new Error(sanitizeDbError("fetch pending messages", error.message));
778
+ return (data ?? []);
779
+ }
780
+ /**
781
+ * Fetch messages with flexible filtering.
782
+ */
783
+ export async function getAgentMessages(supabase, userId, targetId, options) {
784
+ const limit = options?.limit ?? 20;
785
+ const includeBroadcast = options?.includeBroadcast ?? true;
786
+ let query = supabase
787
+ .from("agent_messages")
788
+ .select("*")
789
+ .eq("user_id", userId)
790
+ .order("priority", { ascending: false })
791
+ .order("created_at", { ascending: false })
792
+ .limit(limit);
793
+ if (includeBroadcast) {
794
+ query = query.or(`target_id.eq.${targetId},target_id.eq.*`);
795
+ }
796
+ else {
797
+ query = query.eq("target_id", targetId);
798
+ }
799
+ if (options?.status)
800
+ query = query.eq("status", options.status);
801
+ if (options?.category)
802
+ query = query.eq("category", options.category);
803
+ if (options?.vaultId)
804
+ query = query.eq("vault_id", options.vaultId);
805
+ const { data, error } = await query;
806
+ if (error)
807
+ throw new Error(sanitizeDbError("fetch agent messages", error.message));
808
+ return (data ?? []);
809
+ }
810
+ /**
811
+ * Bulk-update message status and optional extra fields (e.g. delivered_at, acknowledged_at).
812
+ */
813
+ export async function bulkUpdateMessageStatus(supabase, messageIds, userId, status, extraFields) {
814
+ if (messageIds.length === 0)
815
+ return;
816
+ const { error } = await supabase
817
+ .from("agent_messages")
818
+ .update({
819
+ status,
820
+ updated_at: new Date().toISOString(),
821
+ ...extraFields,
822
+ })
823
+ .eq("user_id", userId)
824
+ .in("id", messageIds);
825
+ if (error)
826
+ throw new Error(sanitizeDbError("bulk update message status", error.message));
827
+ }
828
+ /**
829
+ * Resolve thread ID for a reply. If the parent has a thread_id, use it.
830
+ * If not, the parent becomes the thread root (set parent's thread_id = parent.id).
831
+ * Returns the resolved thread_id.
832
+ */
833
+ export async function resolveThreadId(supabase, userId, parentMessageId) {
834
+ const { data: parent, error } = await supabase
835
+ .from("agent_messages")
836
+ .select("id, thread_id")
837
+ .eq("id", parentMessageId)
838
+ .eq("user_id", userId)
839
+ .single();
840
+ if (error || !parent)
841
+ throw new Error("Parent message not found");
842
+ if (parent.thread_id) {
843
+ return parent.thread_id;
844
+ }
845
+ // Parent becomes the thread root
846
+ await supabase
847
+ .from("agent_messages")
848
+ .update({ thread_id: parent.id, updated_at: new Date().toISOString() })
849
+ .eq("id", parent.id)
850
+ .eq("user_id", userId);
851
+ return parent.id;
852
+ }
853
+ /**
854
+ * Fetch all messages in a thread, ordered chronologically.
855
+ */
856
+ export async function getThread(supabase, userId, threadId) {
857
+ const { data, error } = await supabase
858
+ .from("agent_messages")
859
+ .select("*")
860
+ .eq("user_id", userId)
861
+ .eq("thread_id", threadId)
862
+ .order("created_at", { ascending: true });
863
+ if (error)
864
+ throw new Error(sanitizeDbError("fetch thread", error.message));
865
+ return (data ?? []);
866
+ }