comisai 1.0.10 → 1.0.12

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 (251) hide show
  1. package/README.md +3 -3
  2. package/node_modules/@comis/agent/dist/executor/pi-executor.d.ts +1 -4
  3. package/node_modules/@comis/agent/package.json +1 -1
  4. package/node_modules/@comis/channels/package.json +1 -1
  5. package/node_modules/@comis/cli/dist/wizard/steps/06-channels.js +88 -11
  6. package/node_modules/@comis/cli/dist/wizard/steps/10-write-config.js +4 -0
  7. package/node_modules/@comis/cli/dist/wizard/types.d.ts +1 -0
  8. package/node_modules/@comis/cli/package.json +1 -1
  9. package/node_modules/@comis/core/package.json +1 -1
  10. package/node_modules/@comis/daemon/package.json +1 -1
  11. package/node_modules/@comis/gateway/package.json +1 -1
  12. package/node_modules/@comis/infra/package.json +1 -1
  13. package/node_modules/@comis/memory/package.json +1 -1
  14. package/node_modules/@comis/scheduler/package.json +1 -1
  15. package/node_modules/@comis/shared/package.json +1 -1
  16. package/node_modules/@comis/skills/package.json +1 -1
  17. package/package.json +18 -18
  18. package/node_modules/@comis/core/dist/approval/approval-gate.test.d.ts +0 -1
  19. package/node_modules/@comis/core/dist/approval/approval-gate.test.js +0 -1003
  20. package/node_modules/@comis/core/dist/bootstrap.test.d.ts +0 -1
  21. package/node_modules/@comis/core/dist/bootstrap.test.js +0 -150
  22. package/node_modules/@comis/core/dist/config/backup.test.d.ts +0 -1
  23. package/node_modules/@comis/core/dist/config/backup.test.js +0 -160
  24. package/node_modules/@comis/core/dist/config/env-substitution.test.d.ts +0 -1
  25. package/node_modules/@comis/core/dist/config/env-substitution.test.js +0 -259
  26. package/node_modules/@comis/core/dist/config/field-metadata.test.d.ts +0 -1
  27. package/node_modules/@comis/core/dist/config/field-metadata.test.js +0 -72
  28. package/node_modules/@comis/core/dist/config/git-manager.test.d.ts +0 -1
  29. package/node_modules/@comis/core/dist/config/git-manager.test.js +0 -1042
  30. package/node_modules/@comis/core/dist/config/immutable-keys.test.d.ts +0 -1
  31. package/node_modules/@comis/core/dist/config/immutable-keys.test.js +0 -283
  32. package/node_modules/@comis/core/dist/config/include-resolver.test.d.ts +0 -1
  33. package/node_modules/@comis/core/dist/config/include-resolver.test.js +0 -292
  34. package/node_modules/@comis/core/dist/config/layered.test.d.ts +0 -1
  35. package/node_modules/@comis/core/dist/config/layered.test.js +0 -211
  36. package/node_modules/@comis/core/dist/config/loader.test.d.ts +0 -1
  37. package/node_modules/@comis/core/dist/config/loader.test.js +0 -300
  38. package/node_modules/@comis/core/dist/config/migrate.test.d.ts +0 -1
  39. package/node_modules/@comis/core/dist/config/migrate.test.js +0 -244
  40. package/node_modules/@comis/core/dist/config/partial-validator.test.d.ts +0 -1
  41. package/node_modules/@comis/core/dist/config/partial-validator.test.js +0 -98
  42. package/node_modules/@comis/core/dist/config/schema-agent-model.test.d.ts +0 -1
  43. package/node_modules/@comis/core/dist/config/schema-agent-model.test.js +0 -279
  44. package/node_modules/@comis/core/dist/config/schema-agent-session.test.d.ts +0 -1
  45. package/node_modules/@comis/core/dist/config/schema-agent-session.test.js +0 -242
  46. package/node_modules/@comis/core/dist/config/schema-agent.test.d.ts +0 -1
  47. package/node_modules/@comis/core/dist/config/schema-agent.test.js +0 -645
  48. package/node_modules/@comis/core/dist/config/schema-channel.test.d.ts +0 -1
  49. package/node_modules/@comis/core/dist/config/schema-channel.test.js +0 -341
  50. package/node_modules/@comis/core/dist/config/schema-coalescer.test.d.ts +0 -1
  51. package/node_modules/@comis/core/dist/config/schema-coalescer.test.js +0 -51
  52. package/node_modules/@comis/core/dist/config/schema-context-engine.test.d.ts +0 -1
  53. package/node_modules/@comis/core/dist/config/schema-context-engine.test.js +0 -797
  54. package/node_modules/@comis/core/dist/config/schema-context-guard.test.d.ts +0 -1
  55. package/node_modules/@comis/core/dist/config/schema-context-guard.test.js +0 -111
  56. package/node_modules/@comis/core/dist/config/schema-daemon.test.d.ts +0 -1
  57. package/node_modules/@comis/core/dist/config/schema-daemon.test.js +0 -107
  58. package/node_modules/@comis/core/dist/config/schema-delivery-timing.test.d.ts +0 -1
  59. package/node_modules/@comis/core/dist/config/schema-delivery-timing.test.js +0 -51
  60. package/node_modules/@comis/core/dist/config/schema-documentation.test.d.ts +0 -1
  61. package/node_modules/@comis/core/dist/config/schema-documentation.test.js +0 -63
  62. package/node_modules/@comis/core/dist/config/schema-gateway.test.d.ts +0 -1
  63. package/node_modules/@comis/core/dist/config/schema-gateway.test.js +0 -219
  64. package/node_modules/@comis/core/dist/config/schema-gemini-cache.test.d.ts +0 -1
  65. package/node_modules/@comis/core/dist/config/schema-gemini-cache.test.js +0 -41
  66. package/node_modules/@comis/core/dist/config/schema-integrations.test.d.ts +0 -1
  67. package/node_modules/@comis/core/dist/config/schema-integrations.test.js +0 -92
  68. package/node_modules/@comis/core/dist/config/schema-lifecycle-reactions.test.d.ts +0 -1
  69. package/node_modules/@comis/core/dist/config/schema-lifecycle-reactions.test.js +0 -61
  70. package/node_modules/@comis/core/dist/config/schema-misc.test.d.ts +0 -1
  71. package/node_modules/@comis/core/dist/config/schema-misc.test.js +0 -394
  72. package/node_modules/@comis/core/dist/config/schema-observability.test.d.ts +0 -1
  73. package/node_modules/@comis/core/dist/config/schema-observability.test.js +0 -76
  74. package/node_modules/@comis/core/dist/config/schema-providers.test.d.ts +0 -1
  75. package/node_modules/@comis/core/dist/config/schema-providers.test.js +0 -294
  76. package/node_modules/@comis/core/dist/config/schema-queue.test.d.ts +0 -1
  77. package/node_modules/@comis/core/dist/config/schema-queue.test.js +0 -234
  78. package/node_modules/@comis/core/dist/config/schema-response-prefix.test.d.ts +0 -1
  79. package/node_modules/@comis/core/dist/config/schema-response-prefix.test.js +0 -36
  80. package/node_modules/@comis/core/dist/config/schema-security.test.d.ts +0 -1
  81. package/node_modules/@comis/core/dist/config/schema-security.test.js +0 -54
  82. package/node_modules/@comis/core/dist/config/schema-sender-trust-display.test.d.ts +0 -1
  83. package/node_modules/@comis/core/dist/config/schema-sender-trust-display.test.js +0 -60
  84. package/node_modules/@comis/core/dist/config/schema-serializer.test.d.ts +0 -1
  85. package/node_modules/@comis/core/dist/config/schema-serializer.test.js +0 -73
  86. package/node_modules/@comis/core/dist/config/schema-telegram-file-guard.test.d.ts +0 -1
  87. package/node_modules/@comis/core/dist/config/schema-telegram-file-guard.test.js +0 -39
  88. package/node_modules/@comis/core/dist/context/context.test.d.ts +0 -1
  89. package/node_modules/@comis/core/dist/context/context.test.js +0 -276
  90. package/node_modules/@comis/core/dist/domain/agent-response.test.d.ts +0 -1
  91. package/node_modules/@comis/core/dist/domain/agent-response.test.js +0 -159
  92. package/node_modules/@comis/core/dist/domain/credential-mapping.test.d.ts +0 -1
  93. package/node_modules/@comis/core/dist/domain/credential-mapping.test.js +0 -135
  94. package/node_modules/@comis/core/dist/domain/delivery-origin.test.d.ts +0 -1
  95. package/node_modules/@comis/core/dist/domain/delivery-origin.test.js +0 -68
  96. package/node_modules/@comis/core/dist/domain/execution-graph.test.d.ts +0 -1
  97. package/node_modules/@comis/core/dist/domain/execution-graph.test.js +0 -441
  98. package/node_modules/@comis/core/dist/domain/memory-entry.test.d.ts +0 -1
  99. package/node_modules/@comis/core/dist/domain/memory-entry.test.js +0 -193
  100. package/node_modules/@comis/core/dist/domain/normalized-message.test.d.ts +0 -1
  101. package/node_modules/@comis/core/dist/domain/normalized-message.test.js +0 -255
  102. package/node_modules/@comis/core/dist/domain/session-key.test.d.ts +0 -1
  103. package/node_modules/@comis/core/dist/domain/session-key.test.js +0 -291
  104. package/node_modules/@comis/core/dist/domain/subagent-context-config.test.d.ts +0 -1
  105. package/node_modules/@comis/core/dist/domain/subagent-context-config.test.js +0 -131
  106. package/node_modules/@comis/core/dist/domain/subagent-context-types.test.d.ts +0 -1
  107. package/node_modules/@comis/core/dist/domain/subagent-context-types.test.js +0 -182
  108. package/node_modules/@comis/core/dist/event-bus/bus.test.d.ts +0 -1
  109. package/node_modules/@comis/core/dist/event-bus/bus.test.js +0 -152
  110. package/node_modules/@comis/core/dist/event-bus/events-agent.test.d.ts +0 -1
  111. package/node_modules/@comis/core/dist/event-bus/events-agent.test.js +0 -409
  112. package/node_modules/@comis/core/dist/event-bus/events-channel.test.d.ts +0 -1
  113. package/node_modules/@comis/core/dist/event-bus/events-channel.test.js +0 -240
  114. package/node_modules/@comis/core/dist/event-bus/events-infra.test.d.ts +0 -1
  115. package/node_modules/@comis/core/dist/event-bus/events-infra.test.js +0 -416
  116. package/node_modules/@comis/core/dist/event-bus/events-messaging.test.d.ts +0 -1
  117. package/node_modules/@comis/core/dist/event-bus/events-messaging.test.js +0 -433
  118. package/node_modules/@comis/core/dist/event-bus/events.test.d.ts +0 -1
  119. package/node_modules/@comis/core/dist/event-bus/events.test.js +0 -93
  120. package/node_modules/@comis/core/dist/hooks/hook-runner-global.test.d.ts +0 -1
  121. package/node_modules/@comis/core/dist/hooks/hook-runner-global.test.js +0 -29
  122. package/node_modules/@comis/core/dist/hooks/hook-runner.test.d.ts +0 -1
  123. package/node_modules/@comis/core/dist/hooks/hook-runner.test.js +0 -490
  124. package/node_modules/@comis/core/dist/hooks/hook-strategies.test.d.ts +0 -1
  125. package/node_modules/@comis/core/dist/hooks/hook-strategies.test.js +0 -209
  126. package/node_modules/@comis/core/dist/hooks/integration.test.d.ts +0 -1
  127. package/node_modules/@comis/core/dist/hooks/integration.test.js +0 -235
  128. package/node_modules/@comis/core/dist/hooks/plugin-registry.test.d.ts +0 -1
  129. package/node_modules/@comis/core/dist/hooks/plugin-registry.test.js +0 -470
  130. package/node_modules/@comis/core/dist/load-env.test.d.ts +0 -1
  131. package/node_modules/@comis/core/dist/load-env.test.js +0 -21
  132. package/node_modules/@comis/core/dist/security/action-classifier.test.d.ts +0 -1
  133. package/node_modules/@comis/core/dist/security/action-classifier.test.js +0 -298
  134. package/node_modules/@comis/core/dist/security/audit-aggregator.test.d.ts +0 -1
  135. package/node_modules/@comis/core/dist/security/audit-aggregator.test.js +0 -128
  136. package/node_modules/@comis/core/dist/security/audit.test.d.ts +0 -1
  137. package/node_modules/@comis/core/dist/security/audit.test.js +0 -154
  138. package/node_modules/@comis/core/dist/security/canary-token.test.d.ts +0 -1
  139. package/node_modules/@comis/core/dist/security/canary-token.test.js +0 -45
  140. package/node_modules/@comis/core/dist/security/config-redaction.test.d.ts +0 -1
  141. package/node_modules/@comis/core/dist/security/config-redaction.test.js +0 -107
  142. package/node_modules/@comis/core/dist/security/external-content.test.d.ts +0 -1
  143. package/node_modules/@comis/core/dist/security/external-content.test.js +0 -210
  144. package/node_modules/@comis/core/dist/security/injection-patterns.test.d.ts +0 -1
  145. package/node_modules/@comis/core/dist/security/injection-patterns.test.js +0 -213
  146. package/node_modules/@comis/core/dist/security/injection-rate-limiter.test.d.ts +0 -1
  147. package/node_modules/@comis/core/dist/security/injection-rate-limiter.test.js +0 -194
  148. package/node_modules/@comis/core/dist/security/input-guard.test.d.ts +0 -1
  149. package/node_modules/@comis/core/dist/security/input-guard.test.js +0 -215
  150. package/node_modules/@comis/core/dist/security/input-security-guard.test.d.ts +0 -1
  151. package/node_modules/@comis/core/dist/security/input-security-guard.test.js +0 -215
  152. package/node_modules/@comis/core/dist/security/input-validator.test.d.ts +0 -1
  153. package/node_modules/@comis/core/dist/security/input-validator.test.js +0 -133
  154. package/node_modules/@comis/core/dist/security/log-sanitizer.test.d.ts +0 -1
  155. package/node_modules/@comis/core/dist/security/log-sanitizer.test.js +0 -260
  156. package/node_modules/@comis/core/dist/security/memory-write-validator.test.d.ts +0 -1
  157. package/node_modules/@comis/core/dist/security/memory-write-validator.test.js +0 -62
  158. package/node_modules/@comis/core/dist/security/output-guard.test.d.ts +0 -1
  159. package/node_modules/@comis/core/dist/security/output-guard.test.js +0 -397
  160. package/node_modules/@comis/core/dist/security/safe-path.test.d.ts +0 -1
  161. package/node_modules/@comis/core/dist/security/safe-path.test.js +0 -95
  162. package/node_modules/@comis/core/dist/security/scoped-secret-manager.test.d.ts +0 -1
  163. package/node_modules/@comis/core/dist/security/scoped-secret-manager.test.js +0 -231
  164. package/node_modules/@comis/core/dist/security/secret-access.test.d.ts +0 -1
  165. package/node_modules/@comis/core/dist/security/secret-access.test.js +0 -41
  166. package/node_modules/@comis/core/dist/security/secret-crypto.test.d.ts +0 -1
  167. package/node_modules/@comis/core/dist/security/secret-crypto.test.js +0 -113
  168. package/node_modules/@comis/core/dist/security/secret-manager.test.d.ts +0 -1
  169. package/node_modules/@comis/core/dist/security/secret-manager.test.js +0 -394
  170. package/node_modules/@comis/core/dist/security/secret-ref-resolver.test.d.ts +0 -1
  171. package/node_modules/@comis/core/dist/security/secret-ref-resolver.test.js +0 -268
  172. package/node_modules/@comis/core/dist/security/secrets-audit.test.d.ts +0 -10
  173. package/node_modules/@comis/core/dist/security/secrets-audit.test.js +0 -295
  174. package/node_modules/@comis/core/dist/security/ssrf-guard.test.d.ts +0 -1
  175. package/node_modules/@comis/core/dist/security/ssrf-guard.test.js +0 -127
  176. package/node_modules/@comis/core/dist/security/token-generator.test.d.ts +0 -1
  177. package/node_modules/@comis/core/dist/security/token-generator.test.js +0 -35
  178. package/node_modules/@comis/core/dist/tool-metadata.test.d.ts +0 -1
  179. package/node_modules/@comis/core/dist/tool-metadata.test.js +0 -112
  180. package/node_modules/@comis/memory/dist/compaction.test.d.ts +0 -1
  181. package/node_modules/@comis/memory/dist/compaction.test.js +0 -298
  182. package/node_modules/@comis/memory/dist/context-schema.test.d.ts +0 -1
  183. package/node_modules/@comis/memory/dist/context-schema.test.js +0 -246
  184. package/node_modules/@comis/memory/dist/context-store.test.d.ts +0 -1
  185. package/node_modules/@comis/memory/dist/context-store.test.js +0 -708
  186. package/node_modules/@comis/memory/dist/credential-mapping-schema.test.d.ts +0 -7
  187. package/node_modules/@comis/memory/dist/credential-mapping-schema.test.js +0 -73
  188. package/node_modules/@comis/memory/dist/credential-mapping-store.test.d.ts +0 -8
  189. package/node_modules/@comis/memory/dist/credential-mapping-store.test.js +0 -340
  190. package/node_modules/@comis/memory/dist/delivery-mirror-adapter.test.d.ts +0 -1
  191. package/node_modules/@comis/memory/dist/delivery-mirror-adapter.test.js +0 -165
  192. package/node_modules/@comis/memory/dist/delivery-queue-adapter.test.d.ts +0 -1
  193. package/node_modules/@comis/memory/dist/delivery-queue-adapter.test.js +0 -263
  194. package/node_modules/@comis/memory/dist/embedding-batch-indexer.test.d.ts +0 -1
  195. package/node_modules/@comis/memory/dist/embedding-batch-indexer.test.js +0 -202
  196. package/node_modules/@comis/memory/dist/embedding-cache-lru.test.d.ts +0 -1
  197. package/node_modules/@comis/memory/dist/embedding-cache-lru.test.js +0 -241
  198. package/node_modules/@comis/memory/dist/embedding-cache-sqlite.test.d.ts +0 -8
  199. package/node_modules/@comis/memory/dist/embedding-cache-sqlite.test.js +0 -678
  200. package/node_modules/@comis/memory/dist/embedding-cache.test.d.ts +0 -1
  201. package/node_modules/@comis/memory/dist/embedding-cache.test.js +0 -213
  202. package/node_modules/@comis/memory/dist/embedding-fingerprint.test.d.ts +0 -1
  203. package/node_modules/@comis/memory/dist/embedding-fingerprint.test.js +0 -87
  204. package/node_modules/@comis/memory/dist/embedding-hash.test.d.ts +0 -1
  205. package/node_modules/@comis/memory/dist/embedding-hash.test.js +0 -31
  206. package/node_modules/@comis/memory/dist/embedding-integration.test.d.ts +0 -8
  207. package/node_modules/@comis/memory/dist/embedding-integration.test.js +0 -232
  208. package/node_modules/@comis/memory/dist/embedding-provider-factory.test.d.ts +0 -1
  209. package/node_modules/@comis/memory/dist/embedding-provider-factory.test.js +0 -176
  210. package/node_modules/@comis/memory/dist/embedding-provider-local.test.d.ts +0 -8
  211. package/node_modules/@comis/memory/dist/embedding-provider-local.test.js +0 -76
  212. package/node_modules/@comis/memory/dist/embedding-provider-openai.test.d.ts +0 -8
  213. package/node_modules/@comis/memory/dist/embedding-provider-openai.test.js +0 -100
  214. package/node_modules/@comis/memory/dist/embedding-queue.test.d.ts +0 -1
  215. package/node_modules/@comis/memory/dist/embedding-queue.test.js +0 -236
  216. package/node_modules/@comis/memory/dist/hybrid-search.test.d.ts +0 -1
  217. package/node_modules/@comis/memory/dist/hybrid-search.test.js +0 -476
  218. package/node_modules/@comis/memory/dist/identity-link-store.test.d.ts +0 -1
  219. package/node_modules/@comis/memory/dist/identity-link-store.test.js +0 -88
  220. package/node_modules/@comis/memory/dist/memory-api.test.d.ts +0 -1
  221. package/node_modules/@comis/memory/dist/memory-api.test.js +0 -495
  222. package/node_modules/@comis/memory/dist/memory-concurrency.test.d.ts +0 -20
  223. package/node_modules/@comis/memory/dist/memory-concurrency.test.js +0 -222
  224. package/node_modules/@comis/memory/dist/named-graph-store.test.d.ts +0 -1
  225. package/node_modules/@comis/memory/dist/named-graph-store.test.js +0 -227
  226. package/node_modules/@comis/memory/dist/observability-store.test.d.ts +0 -1
  227. package/node_modules/@comis/memory/dist/observability-store.test.js +0 -704
  228. package/node_modules/@comis/memory/dist/row-mapper.test.d.ts +0 -1
  229. package/node_modules/@comis/memory/dist/row-mapper.test.js +0 -409
  230. package/node_modules/@comis/memory/dist/schema.test.d.ts +0 -1
  231. package/node_modules/@comis/memory/dist/schema.test.js +0 -240
  232. package/node_modules/@comis/memory/dist/secret-store-schema.test.d.ts +0 -7
  233. package/node_modules/@comis/memory/dist/secret-store-schema.test.js +0 -90
  234. package/node_modules/@comis/memory/dist/session-store.test.d.ts +0 -1
  235. package/node_modules/@comis/memory/dist/session-store.test.js +0 -262
  236. package/node_modules/@comis/memory/dist/setup-secrets.test.d.ts +0 -1
  237. package/node_modules/@comis/memory/dist/setup-secrets.test.js +0 -140
  238. package/node_modules/@comis/memory/dist/sqlite-memory-adapter.test.d.ts +0 -1
  239. package/node_modules/@comis/memory/dist/sqlite-memory-adapter.test.js +0 -888
  240. package/node_modules/@comis/memory/dist/sqlite-secret-store.test.d.ts +0 -8
  241. package/node_modules/@comis/memory/dist/sqlite-secret-store.test.js +0 -245
  242. package/node_modules/@comis/shared/dist/abort.test.d.ts +0 -1
  243. package/node_modules/@comis/shared/dist/abort.test.js +0 -71
  244. package/node_modules/@comis/shared/dist/result.test.d.ts +0 -1
  245. package/node_modules/@comis/shared/dist/result.test.js +0 -178
  246. package/node_modules/@comis/shared/dist/suppress-error.test.d.ts +0 -1
  247. package/node_modules/@comis/shared/dist/suppress-error.test.js +0 -50
  248. package/node_modules/@comis/shared/dist/timeout.test.d.ts +0 -1
  249. package/node_modules/@comis/shared/dist/timeout.test.js +0 -75
  250. package/node_modules/@comis/shared/dist/ttl-cache.test.d.ts +0 -1
  251. package/node_modules/@comis/shared/dist/ttl-cache.test.js +0 -81
@@ -1,708 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
2
- import Database from "better-sqlite3";
3
- import { createContextStore } from "./context-store.js";
4
- let db;
5
- let store;
6
- let convCounter = 0;
7
- /** Create a fresh store for each test. */
8
- function freshStore() {
9
- const d = new Database(":memory:");
10
- d.pragma("foreign_keys = ON");
11
- const s = createContextStore(d);
12
- return { db: d, store: s };
13
- }
14
- /** Helper: create a test conversation with unique session key. */
15
- function createTestConversation(s) {
16
- convCounter++;
17
- return s.createConversation({
18
- tenantId: "t1",
19
- agentId: "a1",
20
- sessionKey: `sess-${convCounter}`,
21
- });
22
- }
23
- beforeEach(() => {
24
- convCounter = 0;
25
- const fresh = freshStore();
26
- db = fresh.db;
27
- store = fresh.store;
28
- });
29
- // =========================================================================
30
- // Group 1: Conversations
31
- // =========================================================================
32
- describe("conversations", () => {
33
- it("createConversation returns conversation_id with conv_ prefix", () => {
34
- const id = createTestConversation(store);
35
- expect(id).toMatch(/^conv_[0-9a-f]{16}$/);
36
- });
37
- it("getConversation returns inserted row", () => {
38
- const id = store.createConversation({
39
- tenantId: "t1",
40
- agentId: "a1",
41
- sessionKey: "sess-get",
42
- title: "Test Title",
43
- });
44
- const row = store.getConversation(id);
45
- expect(row).toBeDefined();
46
- expect(row.conversation_id).toBe(id);
47
- expect(row.tenant_id).toBe("t1");
48
- expect(row.agent_id).toBe("a1");
49
- expect(row.session_key).toBe("sess-get");
50
- expect(row.title).toBe("Test Title");
51
- expect(row.created_at).toBeTruthy();
52
- expect(row.updated_at).toBeTruthy();
53
- });
54
- it("getConversationBySession finds by tenant+session", () => {
55
- const id = store.createConversation({
56
- tenantId: "t1",
57
- agentId: "a1",
58
- sessionKey: "sess-find",
59
- });
60
- const row = store.getConversationBySession("t1", "sess-find");
61
- expect(row).toBeDefined();
62
- expect(row.conversation_id).toBe(id);
63
- });
64
- it("getConversationBySession returns undefined for missing", () => {
65
- const row = store.getConversationBySession("t1", "nonexistent");
66
- expect(row).toBeUndefined();
67
- });
68
- it("touchConversation updates updated_at", () => {
69
- const id = createTestConversation(store);
70
- const before = store.getConversation(id).updated_at;
71
- // Touch should update the timestamp
72
- store.touchConversation(id);
73
- const after = store.getConversation(id).updated_at;
74
- // At minimum, they should both be valid timestamps
75
- expect(before).toBeTruthy();
76
- expect(after).toBeTruthy();
77
- // SQLite datetime('now') has 1-second resolution, so we just verify
78
- // the field exists and is a valid datetime string
79
- expect(after).toMatch(/^\d{4}-\d{2}-\d{2}/);
80
- });
81
- });
82
- // =========================================================================
83
- // Group 2: Messages
84
- // =========================================================================
85
- describe("messages", () => {
86
- it("insertMessage returns auto-incremented message_id", () => {
87
- const convId = createTestConversation(store);
88
- const id1 = store.insertMessage({
89
- conversationId: convId,
90
- seq: 1,
91
- role: "user",
92
- content: "msg1",
93
- contentHash: "h1",
94
- tokenCount: 5,
95
- });
96
- const id2 = store.insertMessage({
97
- conversationId: convId,
98
- seq: 2,
99
- role: "assistant",
100
- content: "msg2",
101
- contentHash: "h2",
102
- tokenCount: 10,
103
- });
104
- const id3 = store.insertMessage({
105
- conversationId: convId,
106
- seq: 3,
107
- role: "user",
108
- content: "msg3",
109
- contentHash: "h3",
110
- tokenCount: 7,
111
- });
112
- expect(id1).toBe(1);
113
- expect(id2).toBe(2);
114
- expect(id3).toBe(3);
115
- });
116
- it("insertMessage creates FTS entry", () => {
117
- const convId = createTestConversation(store);
118
- const msgId = store.insertMessage({
119
- conversationId: convId,
120
- seq: 1,
121
- role: "user",
122
- content: "hello world test phrase",
123
- contentHash: "hfts",
124
- tokenCount: 4,
125
- });
126
- // Query FTS5 directly
127
- const results = db
128
- .prepare("SELECT rowid, content FROM ctx_messages_fts WHERE ctx_messages_fts MATCH 'hello'")
129
- .all();
130
- expect(results).toHaveLength(1);
131
- expect(results[0].rowid).toBe(msgId);
132
- expect(results[0].content).toBe("hello world test phrase");
133
- });
134
- it("getMessagesByConversation returns messages in seq order", () => {
135
- const convId = createTestConversation(store);
136
- store.insertMessage({ conversationId: convId, seq: 3, role: "user", content: "c", contentHash: "h3", tokenCount: 1 });
137
- store.insertMessage({ conversationId: convId, seq: 1, role: "user", content: "a", contentHash: "h1", tokenCount: 1 });
138
- store.insertMessage({ conversationId: convId, seq: 2, role: "assistant", content: "b", contentHash: "h2", tokenCount: 1 });
139
- const msgs = store.getMessagesByConversation(convId);
140
- expect(msgs).toHaveLength(3);
141
- expect(msgs[0].seq).toBe(1);
142
- expect(msgs[1].seq).toBe(2);
143
- expect(msgs[2].seq).toBe(3);
144
- });
145
- it("getMessagesByConversation with afterSeq filters correctly", () => {
146
- const convId = createTestConversation(store);
147
- for (let i = 1; i <= 5; i++) {
148
- store.insertMessage({ conversationId: convId, seq: i, role: "user", content: `m${i}`, contentHash: `h${i}`, tokenCount: 1 });
149
- }
150
- const msgs = store.getMessagesByConversation(convId, { afterSeq: 3 });
151
- expect(msgs).toHaveLength(2);
152
- expect(msgs[0].seq).toBe(4);
153
- expect(msgs[1].seq).toBe(5);
154
- });
155
- it("getMessagesByConversation with limit caps results", () => {
156
- const convId = createTestConversation(store);
157
- for (let i = 1; i <= 10; i++) {
158
- store.insertMessage({ conversationId: convId, seq: i, role: "user", content: `m${i}`, contentHash: `h${i}`, tokenCount: 1 });
159
- }
160
- const msgs = store.getMessagesByConversation(convId, { limit: 3 });
161
- expect(msgs).toHaveLength(3);
162
- });
163
- it("getMessagesByIds batch fetches correctly", () => {
164
- const convId = createTestConversation(store);
165
- const ids = [];
166
- for (let i = 1; i <= 5; i++) {
167
- ids.push(store.insertMessage({ conversationId: convId, seq: i, role: "user", content: `m${i}`, contentHash: `h${i}`, tokenCount: 1 }));
168
- }
169
- const results = store.getMessagesByIds([ids[0], ids[2], ids[4]]);
170
- expect(results).toHaveLength(3);
171
- expect(results[0].seq).toBe(1);
172
- expect(results[1].seq).toBe(3);
173
- expect(results[2].seq).toBe(5);
174
- });
175
- it("getMessagesByIds returns empty for empty input", () => {
176
- const results = store.getMessagesByIds([]);
177
- expect(results).toEqual([]);
178
- });
179
- it("getMessagesByIds handles large batches", () => {
180
- const convId = createTestConversation(store);
181
- // Insert 550 messages to exercise chunking (>500)
182
- for (let i = 1; i <= 550; i++) {
183
- store.insertMessage({
184
- conversationId: convId,
185
- seq: i,
186
- role: "user",
187
- content: `msg-${i}`,
188
- contentHash: `hash-${i}`,
189
- tokenCount: 1,
190
- });
191
- }
192
- // Fetch all 550 by IDs
193
- const allIds = Array.from({ length: 550 }, (_, i) => i + 1);
194
- const results = store.getMessagesByIds(allIds);
195
- expect(results).toHaveLength(550);
196
- });
197
- it("getMessageByHash finds by content_hash", () => {
198
- const convId = createTestConversation(store);
199
- store.insertMessage({
200
- conversationId: convId,
201
- seq: 1,
202
- role: "user",
203
- content: "unique content",
204
- contentHash: "unique-hash-123",
205
- tokenCount: 2,
206
- });
207
- const row = store.getMessageByHash(convId, "unique-hash-123");
208
- expect(row).toBeDefined();
209
- expect(row.content).toBe("unique content");
210
- });
211
- it("getLastMessageSeq returns max seq", () => {
212
- const convId = createTestConversation(store);
213
- store.insertMessage({ conversationId: convId, seq: 1, role: "user", content: "a", contentHash: "h1", tokenCount: 1 });
214
- store.insertMessage({ conversationId: convId, seq: 2, role: "assistant", content: "b", contentHash: "h2", tokenCount: 1 });
215
- store.insertMessage({ conversationId: convId, seq: 3, role: "user", content: "c", contentHash: "h3", tokenCount: 1 });
216
- expect(store.getLastMessageSeq(convId)).toBe(3);
217
- });
218
- it("getLastMessageSeq returns 0 for empty conversation", () => {
219
- const convId = createTestConversation(store);
220
- expect(store.getLastMessageSeq(convId)).toBe(0);
221
- });
222
- });
223
- // =========================================================================
224
- // Group 3: Message Parts
225
- // =========================================================================
226
- describe("message parts", () => {
227
- it("insertParts and getPartsByMessage round-trips correctly", () => {
228
- const convId = createTestConversation(store);
229
- const msgId = store.insertMessage({
230
- conversationId: convId,
231
- seq: 1,
232
- role: "user",
233
- content: "multipart",
234
- contentHash: "hmp",
235
- tokenCount: 1,
236
- });
237
- store.insertParts(msgId, [
238
- { ordinal: 0, partType: "text", content: "Hello" },
239
- { ordinal: 1, partType: "image", metadata: '{"url":"http://..."}' },
240
- { ordinal: 2, partType: "text", content: "World" },
241
- ]);
242
- const parts = store.getPartsByMessage(msgId);
243
- expect(parts).toHaveLength(3);
244
- expect(parts[0].ordinal).toBe(0);
245
- expect(parts[0].part_type).toBe("text");
246
- expect(parts[0].content).toBe("Hello");
247
- expect(parts[1].ordinal).toBe(1);
248
- expect(parts[1].part_type).toBe("image");
249
- expect(parts[1].metadata).toBe('{"url":"http://..."}');
250
- expect(parts[2].ordinal).toBe(2);
251
- expect(parts[2].content).toBe("World");
252
- });
253
- it("getPartsByMessages batches across multiple messages", () => {
254
- const convId = createTestConversation(store);
255
- const msgIds = [];
256
- for (let i = 1; i <= 3; i++) {
257
- const msgId = store.insertMessage({
258
- conversationId: convId,
259
- seq: i,
260
- role: "user",
261
- content: `msg-${i}`,
262
- contentHash: `h${i}`,
263
- tokenCount: 1,
264
- });
265
- msgIds.push(msgId);
266
- store.insertParts(msgId, [
267
- { ordinal: 0, partType: "text", content: `part-${i}-a` },
268
- { ordinal: 1, partType: "text", content: `part-${i}-b` },
269
- ]);
270
- }
271
- const partsMap = store.getPartsByMessages(msgIds);
272
- expect(partsMap.size).toBe(3);
273
- for (const msgId of msgIds) {
274
- const parts = partsMap.get(msgId);
275
- expect(parts).toHaveLength(2);
276
- }
277
- });
278
- });
279
- // =========================================================================
280
- // Group 4: Summaries
281
- // =========================================================================
282
- describe("summaries", () => {
283
- it("insertSummary stores and FTS-indexes the summary", () => {
284
- const convId = createTestConversation(store);
285
- const sumId = store.insertSummary({
286
- summaryId: "sum-001",
287
- conversationId: convId,
288
- kind: "leaf",
289
- depth: 0,
290
- content: "Important discussion about architecture",
291
- tokenCount: 20,
292
- });
293
- expect(sumId).toBe("sum-001");
294
- // Verify stored
295
- const row = store.getSummary("sum-001");
296
- expect(row).toBeDefined();
297
- expect(row.content).toBe("Important discussion about architecture");
298
- expect(row.kind).toBe("leaf");
299
- expect(row.depth).toBe(0);
300
- // Verify FTS indexed
301
- const fts = db
302
- .prepare("SELECT summary_id, content FROM ctx_summaries_fts WHERE content MATCH 'architecture'")
303
- .all();
304
- expect(fts).toHaveLength(1);
305
- expect(fts[0].summary_id).toBe("sum-001");
306
- });
307
- it("getSummariesByConversation returns all summaries", () => {
308
- const convId = createTestConversation(store);
309
- store.insertSummary({ summaryId: "s1", conversationId: convId, kind: "leaf", depth: 0, content: "sum1", tokenCount: 5 });
310
- store.insertSummary({ summaryId: "s2", conversationId: convId, kind: "leaf", depth: 0, content: "sum2", tokenCount: 5 });
311
- store.insertSummary({ summaryId: "s3", conversationId: convId, kind: "condensed", depth: 1, content: "sum3", tokenCount: 5 });
312
- const sums = store.getSummariesByConversation(convId);
313
- expect(sums).toHaveLength(3);
314
- });
315
- it("getSummariesByConversation filters by depth", () => {
316
- const convId = createTestConversation(store);
317
- store.insertSummary({ summaryId: "sd0a", conversationId: convId, kind: "leaf", depth: 0, content: "d0a", tokenCount: 5 });
318
- store.insertSummary({ summaryId: "sd0b", conversationId: convId, kind: "leaf", depth: 0, content: "d0b", tokenCount: 5 });
319
- store.insertSummary({ summaryId: "sd1a", conversationId: convId, kind: "condensed", depth: 1, content: "d1a", tokenCount: 5 });
320
- const depth0 = store.getSummariesByConversation(convId, { depth: 0 });
321
- expect(depth0).toHaveLength(2);
322
- expect(depth0.every((s) => s.depth === 0)).toBe(true);
323
- });
324
- it("updateSummaryCountsDirty sets dirty flag", () => {
325
- const convId = createTestConversation(store);
326
- store.insertSummary({ summaryId: "sd", conversationId: convId, kind: "leaf", depth: 0, content: "dirty test", tokenCount: 5 });
327
- // Initially counts_dirty = 0
328
- expect(store.getSummary("sd").counts_dirty).toBe(0);
329
- // Set dirty
330
- store.updateSummaryCountsDirty(["sd"], true);
331
- expect(store.getSummary("sd").counts_dirty).toBe(1);
332
- // Clear dirty
333
- store.updateSummaryCountsDirty(["sd"], false);
334
- expect(store.getSummary("sd").counts_dirty).toBe(0);
335
- });
336
- it("deleteSummary removes from table and FTS", () => {
337
- const convId = createTestConversation(store);
338
- store.insertSummary({ summaryId: "sdel", conversationId: convId, kind: "leaf", depth: 0, content: "delete me please", tokenCount: 5 });
339
- // Verify it exists
340
- expect(store.getSummary("sdel")).toBeDefined();
341
- // Delete
342
- store.deleteSummary("sdel");
343
- // Verify gone from table
344
- expect(store.getSummary("sdel")).toBeUndefined();
345
- // Verify gone from FTS
346
- const fts = db
347
- .prepare("SELECT summary_id FROM ctx_summaries_fts WHERE content MATCH 'delete'")
348
- .all();
349
- expect(fts).toHaveLength(0);
350
- });
351
- });
352
- // =========================================================================
353
- // Group 5: Summary Links
354
- // =========================================================================
355
- describe("summary links", () => {
356
- it("linkSummaryMessages and getSourceMessageIds round-trip", () => {
357
- const convId = createTestConversation(store);
358
- const msgIds = [];
359
- for (let i = 1; i <= 3; i++) {
360
- msgIds.push(store.insertMessage({ conversationId: convId, seq: i, role: "user", content: `m${i}`, contentHash: `h${i}`, tokenCount: 1 }));
361
- }
362
- store.insertSummary({ summaryId: "slink", conversationId: convId, kind: "leaf", depth: 0, content: "link test", tokenCount: 5 });
363
- store.linkSummaryMessages("slink", msgIds);
364
- const result = store.getSourceMessageIds("slink");
365
- expect(result).toEqual(msgIds);
366
- });
367
- it("linkSummaryParents and getParentSummaryIds round-trip", () => {
368
- const convId = createTestConversation(store);
369
- store.insertSummary({ summaryId: "parent1", conversationId: convId, kind: "leaf", depth: 0, content: "p1", tokenCount: 5 });
370
- store.insertSummary({ summaryId: "parent2", conversationId: convId, kind: "leaf", depth: 0, content: "p2", tokenCount: 5 });
371
- store.insertSummary({ summaryId: "child", conversationId: convId, kind: "condensed", depth: 1, content: "child", tokenCount: 5 });
372
- store.linkSummaryParents("child", ["parent1", "parent2"]);
373
- const parents = store.getParentSummaryIds("child");
374
- expect(parents).toEqual(["parent1", "parent2"]);
375
- });
376
- it("getChildSummaryIds returns children of a parent", () => {
377
- const convId = createTestConversation(store);
378
- store.insertSummary({ summaryId: "p1", conversationId: convId, kind: "leaf", depth: 0, content: "parent", tokenCount: 5 });
379
- store.insertSummary({ summaryId: "c1", conversationId: convId, kind: "condensed", depth: 1, content: "child", tokenCount: 5 });
380
- store.linkSummaryParents("c1", ["p1"]);
381
- const children = store.getChildSummaryIds("p1");
382
- expect(children).toEqual(["c1"]);
383
- });
384
- });
385
- // =========================================================================
386
- // Group 6: Context Items
387
- // =========================================================================
388
- describe("context items", () => {
389
- it("replaceContextItems sets and getContextItems retrieves", () => {
390
- const convId = createTestConversation(store);
391
- const m1 = store.insertMessage({ conversationId: convId, seq: 1, role: "user", content: "a", contentHash: "h1", tokenCount: 1 });
392
- const m2 = store.insertMessage({ conversationId: convId, seq: 2, role: "assistant", content: "b", contentHash: "h2", tokenCount: 1 });
393
- store.insertSummary({ summaryId: "si", conversationId: convId, kind: "leaf", depth: 0, content: "sum", tokenCount: 5 });
394
- store.replaceContextItems(convId, [
395
- { ordinal: 0, itemType: "message", messageId: m1 },
396
- { ordinal: 1, itemType: "summary", summaryId: "si" },
397
- { ordinal: 2, itemType: "message", messageId: m2 },
398
- ]);
399
- const items = store.getContextItems(convId);
400
- expect(items).toHaveLength(3);
401
- expect(items[0].ordinal).toBe(0);
402
- expect(items[0].item_type).toBe("message");
403
- expect(items[0].message_id).toBe(m1);
404
- expect(items[1].ordinal).toBe(1);
405
- expect(items[1].item_type).toBe("summary");
406
- expect(items[1].summary_id).toBe("si");
407
- expect(items[2].ordinal).toBe(2);
408
- expect(items[2].item_type).toBe("message");
409
- expect(items[2].message_id).toBe(m2);
410
- });
411
- it("replaceContextItems replaces existing items", () => {
412
- const convId = createTestConversation(store);
413
- const m1 = store.insertMessage({ conversationId: convId, seq: 1, role: "user", content: "a", contentHash: "h1", tokenCount: 1 });
414
- const m2 = store.insertMessage({ conversationId: convId, seq: 2, role: "user", content: "b", contentHash: "h2", tokenCount: 1 });
415
- // First set
416
- store.replaceContextItems(convId, [
417
- { ordinal: 0, itemType: "message", messageId: m1 },
418
- ]);
419
- expect(store.getContextItems(convId)).toHaveLength(1);
420
- // Replace with different items
421
- store.replaceContextItems(convId, [
422
- { ordinal: 0, itemType: "message", messageId: m2 },
423
- ]);
424
- const items = store.getContextItems(convId);
425
- expect(items).toHaveLength(1);
426
- expect(items[0].message_id).toBe(m2);
427
- });
428
- });
429
- // =========================================================================
430
- // Group 7: Large Files
431
- // =========================================================================
432
- describe("large files", () => {
433
- it("insertLargeFile and getLargeFile round-trip", () => {
434
- const convId = createTestConversation(store);
435
- const fileId = store.insertLargeFile({
436
- fileId: "file-001",
437
- conversationId: convId,
438
- fileName: "document.pdf",
439
- mimeType: "application/pdf",
440
- byteSize: 12345,
441
- contentHash: "filehash123",
442
- storagePath: "/data/files/file-001.pdf",
443
- explorationSummary: "A PDF document about testing",
444
- });
445
- expect(fileId).toBe("file-001");
446
- const row = store.getLargeFile("file-001");
447
- expect(row).toBeDefined();
448
- expect(row.file_name).toBe("document.pdf");
449
- expect(row.mime_type).toBe("application/pdf");
450
- expect(row.byte_size).toBe(12345);
451
- expect(row.content_hash).toBe("filehash123");
452
- expect(row.storage_path).toBe("/data/files/file-001.pdf");
453
- expect(row.exploration_summary).toBe("A PDF document about testing");
454
- });
455
- it("getLargeFileByHash finds by conversation+hash", () => {
456
- const convId = createTestConversation(store);
457
- store.insertLargeFile({
458
- fileId: "file-hash-test",
459
- conversationId: convId,
460
- contentHash: "unique-file-hash",
461
- storagePath: "/data/files/test.bin",
462
- });
463
- const row = store.getLargeFileByHash(convId, "unique-file-hash");
464
- expect(row).toBeDefined();
465
- expect(row.file_id).toBe("file-hash-test");
466
- });
467
- });
468
- // =========================================================================
469
- // Group 8: Expansion Grants
470
- // =========================================================================
471
- describe("expansion grants", () => {
472
- it("createGrant and getGrant round-trip", () => {
473
- const convId = createTestConversation(store);
474
- const grantId = store.createGrant({
475
- grantId: "grant-001",
476
- issuerSession: "sess-1",
477
- conversationIds: [convId],
478
- summaryIds: ["sum-a", "sum-b"],
479
- maxDepth: 5,
480
- tokenCap: 8000,
481
- expiresAt: "2099-12-31 23:59:59",
482
- });
483
- expect(grantId).toBe("grant-001");
484
- const row = store.getGrant("grant-001");
485
- expect(row).toBeDefined();
486
- expect(row.issuer_session).toBe("sess-1");
487
- expect(JSON.parse(row.conversation_ids)).toEqual([convId]);
488
- expect(JSON.parse(row.summary_ids)).toEqual(["sum-a", "sum-b"]);
489
- expect(row.max_depth).toBe(5);
490
- expect(row.token_cap).toBe(8000);
491
- expect(row.tokens_consumed).toBe(0);
492
- expect(row.revoked).toBe(0);
493
- });
494
- it("getActiveGrants filters by session and non-revoked non-expired", () => {
495
- const convId = createTestConversation(store);
496
- // Active grant
497
- store.createGrant({
498
- grantId: "g-active",
499
- issuerSession: "sess-x",
500
- conversationIds: [convId],
501
- expiresAt: "2099-12-31 23:59:59",
502
- });
503
- // Revoked grant
504
- store.createGrant({
505
- grantId: "g-revoked",
506
- issuerSession: "sess-x",
507
- conversationIds: [convId],
508
- expiresAt: "2099-12-31 23:59:59",
509
- });
510
- store.revokeGrant("g-revoked");
511
- // Expired grant
512
- store.createGrant({
513
- grantId: "g-expired",
514
- issuerSession: "sess-x",
515
- conversationIds: [convId],
516
- expiresAt: "2000-01-01 00:00:00",
517
- });
518
- const active = store.getActiveGrants("sess-x");
519
- expect(active).toHaveLength(1);
520
- expect(active[0].grant_id).toBe("g-active");
521
- });
522
- it("consumeGrantTokens increments tokens_consumed", () => {
523
- const convId = createTestConversation(store);
524
- store.createGrant({
525
- grantId: "g-consume",
526
- issuerSession: "sess-c",
527
- conversationIds: [convId],
528
- expiresAt: "2099-12-31 23:59:59",
529
- });
530
- store.consumeGrantTokens("g-consume", 100);
531
- expect(store.getGrant("g-consume").tokens_consumed).toBe(100);
532
- store.consumeGrantTokens("g-consume", 200);
533
- expect(store.getGrant("g-consume").tokens_consumed).toBe(300);
534
- });
535
- it("revokeGrant sets revoked flag", () => {
536
- const convId = createTestConversation(store);
537
- store.createGrant({
538
- grantId: "g-revoke",
539
- issuerSession: "sess-r",
540
- conversationIds: [convId],
541
- expiresAt: "2099-12-31 23:59:59",
542
- });
543
- store.revokeGrant("g-revoke");
544
- expect(store.getGrant("g-revoke").revoked).toBe(1);
545
- });
546
- it("cleanupExpiredGrants removes expired and revoked", () => {
547
- const convId = createTestConversation(store);
548
- // Expired grant
549
- store.createGrant({
550
- grantId: "g-exp",
551
- issuerSession: "sess-cl",
552
- conversationIds: [convId],
553
- expiresAt: "2000-01-01 00:00:00",
554
- });
555
- // Revoked grant
556
- store.createGrant({
557
- grantId: "g-rev",
558
- issuerSession: "sess-cl",
559
- conversationIds: [convId],
560
- expiresAt: "2099-12-31 23:59:59",
561
- });
562
- store.revokeGrant("g-rev");
563
- // Active grant (should survive)
564
- store.createGrant({
565
- grantId: "g-keep",
566
- issuerSession: "sess-cl",
567
- conversationIds: [convId],
568
- expiresAt: "2099-12-31 23:59:59",
569
- });
570
- const count = store.cleanupExpiredGrants();
571
- expect(count).toBe(2);
572
- expect(store.getGrant("g-exp")).toBeUndefined();
573
- expect(store.getGrant("g-rev")).toBeUndefined();
574
- expect(store.getGrant("g-keep")).toBeDefined();
575
- });
576
- });
577
- // =========================================================================
578
- // Group 9: FTS5 Search
579
- // =========================================================================
580
- describe("FTS5 search", () => {
581
- it("searchMessages in fts mode finds matching messages", () => {
582
- const convId = createTestConversation(store);
583
- store.insertMessage({ conversationId: convId, seq: 1, role: "user", content: "the quick brown fox", contentHash: "h1", tokenCount: 4 });
584
- store.insertMessage({ conversationId: convId, seq: 2, role: "user", content: "lazy dog jumps", contentHash: "h2", tokenCount: 3 });
585
- store.insertMessage({ conversationId: convId, seq: 3, role: "user", content: "brown bear sleeps", contentHash: "h3", tokenCount: 3 });
586
- store.insertMessage({ conversationId: convId, seq: 4, role: "user", content: "hello world", contentHash: "h4", tokenCount: 2 });
587
- store.insertMessage({ conversationId: convId, seq: 5, role: "user", content: "another brown animal", contentHash: "h5", tokenCount: 3 });
588
- const results = store.searchMessages(convId, "brown", { mode: "fts", limit: 10 });
589
- expect(results.length).toBeGreaterThanOrEqual(2);
590
- // All results should contain "brown" content
591
- for (const r of results) {
592
- expect(r.content.toLowerCase()).toContain("brown");
593
- }
594
- });
595
- it("searchMessages in fts mode respects limit", () => {
596
- const convId = createTestConversation(store);
597
- for (let i = 1; i <= 10; i++) {
598
- store.insertMessage({
599
- conversationId: convId,
600
- seq: i,
601
- role: "user",
602
- content: `matching keyword phrase ${i}`,
603
- contentHash: `hk${i}`,
604
- tokenCount: 3,
605
- });
606
- }
607
- const results = store.searchMessages(convId, "keyword", { mode: "fts", limit: 3 });
608
- expect(results).toHaveLength(3);
609
- });
610
- it("searchMessages in regex mode uses LIKE pre-filter", () => {
611
- const convId = createTestConversation(store);
612
- store.insertMessage({ conversationId: convId, seq: 1, role: "user", content: "error code 404 not found", contentHash: "hr1", tokenCount: 5 });
613
- store.insertMessage({ conversationId: convId, seq: 2, role: "user", content: "error code 500 server", contentHash: "hr2", tokenCount: 4 });
614
- store.insertMessage({ conversationId: convId, seq: 3, role: "user", content: "all good here", contentHash: "hr3", tokenCount: 3 });
615
- const results = store.searchMessages(convId, "error.*\\d+", { mode: "regex", limit: 10 });
616
- expect(results).toHaveLength(2);
617
- });
618
- it("searchSummaries in fts mode finds matching summaries", () => {
619
- const convId = createTestConversation(store);
620
- store.insertSummary({ summaryId: "sf1", conversationId: convId, kind: "leaf", depth: 0, content: "discussion about databases", tokenCount: 5 });
621
- store.insertSummary({ summaryId: "sf2", conversationId: convId, kind: "leaf", depth: 0, content: "review of architecture patterns", tokenCount: 5 });
622
- store.insertSummary({ summaryId: "sf3", conversationId: convId, kind: "leaf", depth: 0, content: "database migration plan", tokenCount: 5 });
623
- const results = store.searchSummaries(convId, "database", { mode: "fts", limit: 10 });
624
- // "databases" stems to "databas" via porter, "database" stems to "databas" -- should match
625
- expect(results.length).toBeGreaterThanOrEqual(1);
626
- });
627
- it("searchMessages returns empty for no matches", () => {
628
- const convId = createTestConversation(store);
629
- store.insertMessage({ conversationId: convId, seq: 1, role: "user", content: "hello world", contentHash: "hn", tokenCount: 2 });
630
- const results = store.searchMessages(convId, "xyznonexistent", { mode: "fts", limit: 10 });
631
- expect(results).toEqual([]);
632
- });
633
- });
634
- // =========================================================================
635
- // Group 10: deleteConversation (bulk)
636
- // =========================================================================
637
- describe("deleteConversation", () => {
638
- it("deleteConversation removes all related data", () => {
639
- const convId = createTestConversation(store);
640
- // Insert messages
641
- const m1 = store.insertMessage({ conversationId: convId, seq: 1, role: "user", content: "msg1", contentHash: "h1", tokenCount: 1 });
642
- const m2 = store.insertMessage({ conversationId: convId, seq: 2, role: "assistant", content: "msg2", contentHash: "h2", tokenCount: 1 });
643
- // Insert parts
644
- store.insertParts(m1, [{ ordinal: 0, partType: "text", content: "part1" }]);
645
- // Insert summaries
646
- store.insertSummary({ summaryId: "sdel1", conversationId: convId, kind: "leaf", depth: 0, content: "sum1", tokenCount: 5 });
647
- store.insertSummary({ summaryId: "sdel2", conversationId: convId, kind: "condensed", depth: 1, content: "sum2", tokenCount: 5 });
648
- // Link summary to messages
649
- store.linkSummaryMessages("sdel1", [m1, m2]);
650
- store.linkSummaryParents("sdel2", ["sdel1"]);
651
- // Context items
652
- store.replaceContextItems(convId, [
653
- { ordinal: 0, itemType: "message", messageId: m1 },
654
- { ordinal: 1, itemType: "summary", summaryId: "sdel1" },
655
- ]);
656
- // Large file
657
- store.insertLargeFile({
658
- fileId: "fdel",
659
- conversationId: convId,
660
- storagePath: "/tmp/fdel",
661
- });
662
- // Grant
663
- store.createGrant({
664
- grantId: "gdel",
665
- issuerSession: "sess-del",
666
- conversationIds: [convId],
667
- expiresAt: "2099-12-31 23:59:59",
668
- });
669
- // Delete everything
670
- store.deleteConversation(convId);
671
- // Verify all tables are empty for that conversation
672
- expect(store.getConversation(convId)).toBeUndefined();
673
- const msgs = db.prepare("SELECT COUNT(*) as c FROM ctx_messages WHERE conversation_id = ?").get(convId);
674
- expect(msgs.c).toBe(0);
675
- const parts = db.prepare("SELECT COUNT(*) as c FROM ctx_message_parts").get();
676
- expect(parts.c).toBe(0);
677
- const sums = db.prepare("SELECT COUNT(*) as c FROM ctx_summaries WHERE conversation_id = ?").get(convId);
678
- expect(sums.c).toBe(0);
679
- const sumMsgs = db.prepare("SELECT COUNT(*) as c FROM ctx_summary_messages").get();
680
- expect(sumMsgs.c).toBe(0);
681
- const sumParents = db.prepare("SELECT COUNT(*) as c FROM ctx_summary_parents").get();
682
- expect(sumParents.c).toBe(0);
683
- const items = db.prepare("SELECT COUNT(*) as c FROM ctx_context_items WHERE conversation_id = ?").get(convId);
684
- expect(items.c).toBe(0);
685
- const files = db.prepare("SELECT COUNT(*) as c FROM ctx_large_files WHERE conversation_id = ?").get(convId);
686
- expect(files.c).toBe(0);
687
- });
688
- it("deleteConversation cleans up FTS entries", () => {
689
- const convId = createTestConversation(store);
690
- // Insert messages with FTS
691
- store.insertMessage({ conversationId: convId, seq: 1, role: "user", content: "fts cleanup verification msg", contentHash: "hfts1", tokenCount: 4 });
692
- store.insertMessage({ conversationId: convId, seq: 2, role: "user", content: "another fts cleanup msg", contentHash: "hfts2", tokenCount: 4 });
693
- // Insert summaries with FTS
694
- store.insertSummary({ summaryId: "sfts1", conversationId: convId, kind: "leaf", depth: 0, content: "fts cleanup verification summary", tokenCount: 5 });
695
- // Verify FTS entries exist before delete
696
- const msgFtsBefore = db.prepare("SELECT COUNT(*) as c FROM ctx_messages_fts WHERE ctx_messages_fts MATCH 'cleanup'").get();
697
- expect(msgFtsBefore.c).toBeGreaterThan(0);
698
- const sumFtsBefore = db.prepare("SELECT COUNT(*) as c FROM ctx_summaries_fts WHERE content MATCH 'cleanup'").get();
699
- expect(sumFtsBefore.c).toBeGreaterThan(0);
700
- // Delete conversation
701
- store.deleteConversation(convId);
702
- // Verify FTS entries are cleaned up
703
- const msgFtsAfter = db.prepare("SELECT COUNT(*) as c FROM ctx_messages_fts WHERE ctx_messages_fts MATCH 'cleanup'").get();
704
- expect(msgFtsAfter.c).toBe(0);
705
- const sumFtsAfter = db.prepare("SELECT COUNT(*) as c FROM ctx_summaries_fts WHERE content MATCH 'cleanup'").get();
706
- expect(sumFtsAfter.c).toBe(0);
707
- });
708
- });