@vellumai/assistant 0.5.5 → 0.5.7

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 (382) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +4 -5
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docs/architecture/keychain-broker.md +45 -240
  7. package/docs/architecture/security.md +0 -17
  8. package/docs/credential-execution-service.md +2 -2
  9. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  10. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
  11. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  12. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  13. package/package.json +2 -3
  14. package/src/__tests__/actor-token-service.test.ts +1 -2
  15. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  16. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  17. package/src/__tests__/btw-routes.test.ts +0 -39
  18. package/src/__tests__/call-domain.test.ts +0 -128
  19. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  21. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  22. package/src/__tests__/checker.test.ts +4 -2
  23. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  24. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  25. package/src/__tests__/config-schema.test.ts +3 -3
  26. package/src/__tests__/context-window-manager.test.ts +78 -0
  27. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  28. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  29. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  30. package/src/__tests__/conversation-title-service.test.ts +117 -1
  31. package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
  32. package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
  33. package/src/__tests__/credential-security-e2e.test.ts +0 -66
  34. package/src/__tests__/credential-security-invariants.test.ts +4 -45
  35. package/src/__tests__/credentials-cli.test.ts +78 -0
  36. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  37. package/src/__tests__/docker-signing-key-bootstrap.test.ts +98 -0
  38. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  39. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  40. package/src/__tests__/host-shell-tool.test.ts +6 -7
  41. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  42. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  43. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  44. package/src/__tests__/intent-routing.test.ts +0 -13
  45. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  46. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  47. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  48. package/src/__tests__/memory-regressions.test.ts +8 -30
  49. package/src/__tests__/migration-export-http.test.ts +2 -2
  50. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  51. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  52. package/src/__tests__/migration-validate-http.test.ts +2 -2
  53. package/src/__tests__/non-member-access-request.test.ts +0 -5
  54. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  55. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  56. package/src/__tests__/permission-types.test.ts +1 -0
  57. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  58. package/src/__tests__/qdrant-manager.test.ts +28 -2
  59. package/src/__tests__/registry.test.ts +0 -6
  60. package/src/__tests__/require-fresh-approval.test.ts +4 -0
  61. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  62. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  63. package/src/__tests__/secure-keys.test.ts +83 -263
  64. package/src/__tests__/shell-identity.test.ts +96 -6
  65. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  66. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  67. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  68. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  69. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  70. package/src/__tests__/skill-load-tool.test.ts +0 -2
  71. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  72. package/src/__tests__/skills.test.ts +0 -2
  73. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  74. package/src/__tests__/suggestion-routes.test.ts +1 -32
  75. package/src/__tests__/system-prompt.test.ts +0 -1
  76. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
  77. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  78. package/src/__tests__/tool-executor.test.ts +4 -0
  79. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  80. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  81. package/src/__tests__/update-bulletin.test.ts +0 -2
  82. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  83. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
  84. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  85. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
  86. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  87. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  88. package/src/calls/audio-store.test.ts +97 -0
  89. package/src/calls/audio-store.ts +205 -0
  90. package/src/calls/call-controller.ts +85 -7
  91. package/src/calls/call-domain.ts +3 -0
  92. package/src/calls/call-store.ts +10 -3
  93. package/src/calls/fish-audio-client.ts +117 -0
  94. package/src/calls/relay-server.ts +27 -0
  95. package/src/calls/twilio-routes.ts +2 -1
  96. package/src/calls/types.ts +1 -0
  97. package/src/calls/voice-ingress-preflight.ts +0 -42
  98. package/src/calls/voice-quality.ts +26 -5
  99. package/src/calls/voice-session-bridge.ts +6 -12
  100. package/src/cli/commands/config.ts +1 -4
  101. package/src/cli/commands/conversations.ts +0 -18
  102. package/src/cli/commands/credentials.ts +34 -4
  103. package/src/cli/commands/oauth/index.ts +7 -0
  104. package/src/cli/commands/oauth/platform.ts +179 -0
  105. package/src/cli/commands/platform.ts +3 -3
  106. package/src/config/assistant-feature-flags.ts +186 -5
  107. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  108. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  109. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  110. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  111. package/src/config/bundled-tool-registry.ts +1 -11
  112. package/src/config/env-registry.ts +1 -1
  113. package/src/config/env.ts +16 -16
  114. package/src/config/feature-flag-registry.json +48 -16
  115. package/src/config/loader.ts +98 -31
  116. package/src/config/schema.ts +4 -25
  117. package/src/config/schemas/calls.ts +13 -0
  118. package/src/config/schemas/fish-audio.ts +39 -0
  119. package/src/config/schemas/memory.ts +0 -4
  120. package/src/config/schemas/platform.ts +1 -1
  121. package/src/config/schemas/security.ts +4 -4
  122. package/src/config/types.ts +0 -1
  123. package/src/contacts/contact-store.ts +39 -0
  124. package/src/contacts/types.ts +2 -0
  125. package/src/context/window-manager.ts +53 -2
  126. package/src/credential-execution/approval-bridge.ts +1 -0
  127. package/src/credential-execution/executable-discovery.ts +28 -4
  128. package/src/credential-execution/feature-gates.ts +16 -0
  129. package/src/credential-execution/process-manager.ts +38 -0
  130. package/src/daemon/assistant-attachments.ts +9 -0
  131. package/src/daemon/config-watcher.ts +6 -4
  132. package/src/daemon/conversation-agent-loop.ts +0 -60
  133. package/src/daemon/conversation-memory.ts +0 -117
  134. package/src/daemon/conversation-runtime-assembly.ts +0 -2
  135. package/src/daemon/conversation-tool-setup.ts +0 -105
  136. package/src/daemon/conversation.ts +10 -1
  137. package/src/daemon/handlers/config-vercel.ts +92 -0
  138. package/src/daemon/handlers/conversations.ts +0 -11
  139. package/src/daemon/handlers/skills.ts +2 -15
  140. package/src/daemon/install-symlink.ts +195 -0
  141. package/src/daemon/lifecycle.ts +229 -96
  142. package/src/daemon/message-types/conversations.ts +3 -4
  143. package/src/daemon/message-types/diagnostics.ts +3 -22
  144. package/src/daemon/message-types/messages.ts +0 -2
  145. package/src/daemon/message-types/upgrades.ts +8 -0
  146. package/src/daemon/server.ts +30 -92
  147. package/src/events/domain-events.ts +2 -1
  148. package/src/followups/followup-store.ts +5 -2
  149. package/src/inbound/platform-callback-registration.ts +3 -3
  150. package/src/instrument.ts +8 -5
  151. package/src/memory/conversation-crud.ts +0 -236
  152. package/src/memory/conversation-title-service.ts +76 -11
  153. package/src/memory/db-init.ts +15 -11
  154. package/src/memory/indexer.ts +15 -106
  155. package/src/memory/items-extractor.ts +15 -1
  156. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  157. package/src/memory/job-handlers/embedding.ts +0 -79
  158. package/src/memory/job-utils.ts +1 -1
  159. package/src/memory/jobs-store.ts +30 -13
  160. package/src/memory/jobs-worker.ts +31 -27
  161. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  162. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  163. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  164. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  165. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  166. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  167. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  168. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  169. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  170. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  171. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  172. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  173. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  174. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  175. package/src/memory/migrations/116-messages-fts.ts +106 -1
  176. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  177. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  178. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  179. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  180. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  181. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  182. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  183. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  184. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  185. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  186. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  187. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  188. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  189. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  190. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  191. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  192. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  193. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  194. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  195. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  196. package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
  197. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  198. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  199. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  200. package/src/memory/migrations/index.ts +5 -3
  201. package/src/memory/migrations/registry.ts +90 -0
  202. package/src/memory/migrations/validate-migration-state.ts +137 -11
  203. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  204. package/src/memory/qdrant-client.ts +4 -6
  205. package/src/memory/qdrant-manager.ts +64 -7
  206. package/src/memory/schema/calls.ts +1 -0
  207. package/src/memory/schema/contacts.ts +1 -0
  208. package/src/memory/schema/conversations.ts +0 -3
  209. package/src/memory/schema/index.ts +0 -2
  210. package/src/messaging/draft-store.ts +2 -2
  211. package/src/notifications/decision-engine.ts +4 -1
  212. package/src/oauth/connection-resolver.ts +6 -4
  213. package/src/permissions/checker.ts +0 -38
  214. package/src/permissions/defaults.ts +3 -3
  215. package/src/permissions/shell-identity.ts +76 -22
  216. package/src/permissions/trust-client.ts +2 -13
  217. package/src/permissions/trust-store.ts +8 -3
  218. package/src/permissions/types.ts +4 -2
  219. package/src/platform/client.ts +35 -7
  220. package/src/prompts/persona-resolver.ts +138 -0
  221. package/src/prompts/system-prompt.ts +36 -4
  222. package/src/prompts/templates/users/default.md +1 -0
  223. package/src/providers/registry.ts +27 -40
  224. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  225. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  226. package/src/runtime/auth/external-assistant-id.ts +13 -59
  227. package/src/runtime/auth/route-policy.ts +29 -1
  228. package/src/runtime/auth/token-service.ts +53 -15
  229. package/src/runtime/channel-readiness-service.ts +1 -16
  230. package/src/runtime/http-server.ts +29 -2
  231. package/src/runtime/middleware/error-handler.ts +1 -9
  232. package/src/runtime/routes/audio-routes.ts +40 -0
  233. package/src/runtime/routes/btw-routes.ts +0 -17
  234. package/src/runtime/routes/conversation-management-routes.ts +0 -36
  235. package/src/runtime/routes/conversation-query-routes.ts +106 -2
  236. package/src/runtime/routes/conversation-routes.ts +4 -43
  237. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  238. package/src/runtime/routes/identity-routes.ts +18 -29
  239. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  240. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  241. package/src/runtime/routes/integrations/vercel.ts +89 -0
  242. package/src/runtime/routes/log-export-routes.ts +5 -0
  243. package/src/runtime/routes/memory-item-routes.test.ts +221 -3
  244. package/src/runtime/routes/memory-item-routes.ts +144 -4
  245. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  246. package/src/runtime/routes/migration-routes.ts +17 -1
  247. package/src/runtime/routes/notification-routes.ts +58 -0
  248. package/src/runtime/routes/schedule-routes.ts +65 -0
  249. package/src/runtime/routes/settings-routes.ts +41 -1
  250. package/src/runtime/routes/tts-routes.ts +86 -0
  251. package/src/runtime/routes/upgrade-broadcast-routes.ts +175 -0
  252. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  253. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  254. package/src/runtime/routes/workspace-routes.ts +1 -1
  255. package/src/runtime/routes/workspace-utils.ts +86 -2
  256. package/src/schedule/schedule-store.ts +0 -21
  257. package/src/security/ces-credential-client.ts +59 -22
  258. package/src/security/ces-rpc-credential-backend.ts +85 -0
  259. package/src/security/credential-backend.ts +12 -88
  260. package/src/security/keychain-broker-client.ts +10 -2
  261. package/src/security/secure-keys.ts +94 -113
  262. package/src/skills/catalog-install.ts +13 -7
  263. package/src/skills/inline-command-render.ts +5 -1
  264. package/src/skills/inline-command-runner.ts +30 -2
  265. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  266. package/src/tools/calls/call-start.ts +1 -0
  267. package/src/tools/executor.ts +0 -4
  268. package/src/tools/memory/handlers.ts +1 -129
  269. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  270. package/src/tools/network/web-fetch.ts +3 -1
  271. package/src/tools/permission-checker.ts +18 -0
  272. package/src/tools/skills/execute.ts +1 -1
  273. package/src/tools/skills/load.ts +9 -2
  274. package/src/tools/types.ts +0 -8
  275. package/src/util/errors.ts +0 -12
  276. package/src/util/platform.ts +8 -55
  277. package/src/util/xml.ts +8 -0
  278. package/src/workspace/git-service.ts +5 -2
  279. package/src/workspace/heartbeat-service.ts +5 -24
  280. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  281. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  282. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  283. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  284. package/src/workspace/migrations/006-services-config.ts +49 -0
  285. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  286. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  287. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  288. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  289. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  290. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  291. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  292. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  293. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  294. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  295. package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
  296. package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
  297. package/src/workspace/migrations/registry.ts +8 -0
  298. package/src/workspace/migrations/runner.ts +106 -2
  299. package/src/workspace/migrations/types.ts +4 -0
  300. package/src/__tests__/archive-recall.test.ts +0 -560
  301. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  302. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  303. package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
  304. package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
  305. package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
  306. package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
  307. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
  308. package/src/__tests__/diagnostics-export.test.ts +0 -288
  309. package/src/__tests__/local-gateway-health.test.ts +0 -209
  310. package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
  311. package/src/__tests__/memory-brief-time.test.ts +0 -285
  312. package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
  313. package/src/__tests__/memory-chunk-archive.test.ts +0 -400
  314. package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
  315. package/src/__tests__/memory-episode-archive.test.ts +0 -370
  316. package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
  317. package/src/__tests__/memory-observation-archive.test.ts +0 -375
  318. package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
  319. package/src/__tests__/memory-reducer-job.test.ts +0 -538
  320. package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
  321. package/src/__tests__/memory-reducer-store.test.ts +0 -728
  322. package/src/__tests__/memory-reducer-types.test.ts +0 -707
  323. package/src/__tests__/memory-reducer.test.ts +0 -704
  324. package/src/__tests__/memory-simplified-config.test.ts +0 -281
  325. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  326. package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
  327. package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
  328. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  329. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  330. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  331. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  332. package/src/__tests__/swarm-recursion.test.ts +0 -197
  333. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  334. package/src/__tests__/swarm-tool.test.ts +0 -185
  335. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  336. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  337. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  338. package/src/commands/cc-command-registry.ts +0 -248
  339. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  340. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  341. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  342. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  343. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  344. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  345. package/src/config/schemas/memory-simplified.ts +0 -101
  346. package/src/config/schemas/swarm.ts +0 -82
  347. package/src/logfire.ts +0 -135
  348. package/src/memory/archive-recall.ts +0 -516
  349. package/src/memory/archive-store.ts +0 -400
  350. package/src/memory/brief-formatting.ts +0 -33
  351. package/src/memory/brief-open-loops.ts +0 -266
  352. package/src/memory/brief-time.ts +0 -162
  353. package/src/memory/brief.ts +0 -75
  354. package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
  355. package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
  356. package/src/memory/migrations/185-memory-brief-state.ts +0 -52
  357. package/src/memory/migrations/186-memory-archive.ts +0 -109
  358. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
  359. package/src/memory/reducer-scheduler.ts +0 -242
  360. package/src/memory/reducer-store.ts +0 -271
  361. package/src/memory/reducer-types.ts +0 -106
  362. package/src/memory/reducer.ts +0 -467
  363. package/src/memory/schema/memory-archive.ts +0 -121
  364. package/src/memory/schema/memory-brief.ts +0 -55
  365. package/src/runtime/local-gateway-health.ts +0 -275
  366. package/src/security/secret-ingress.ts +0 -68
  367. package/src/swarm/backend-claude-code.ts +0 -225
  368. package/src/swarm/checkpoint.ts +0 -137
  369. package/src/swarm/graph-utils.ts +0 -53
  370. package/src/swarm/index.ts +0 -55
  371. package/src/swarm/limits.ts +0 -66
  372. package/src/swarm/orchestrator.ts +0 -424
  373. package/src/swarm/plan-validator.ts +0 -117
  374. package/src/swarm/router-planner.ts +0 -162
  375. package/src/swarm/router-prompts.ts +0 -39
  376. package/src/swarm/synthesizer.ts +0 -81
  377. package/src/swarm/types.ts +0 -72
  378. package/src/swarm/worker-backend.ts +0 -131
  379. package/src/swarm/worker-prompts.ts +0 -80
  380. package/src/swarm/worker-runner.ts +0 -170
  381. package/src/tools/claude-code/claude-code.ts +0 -610
  382. package/src/tools/swarm/delegate.ts +0 -205
@@ -1,728 +0,0 @@
1
- import { mkdtempSync, rmSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
5
-
6
- const testDir = mkdtempSync(join(tmpdir(), "reducer-store-test-"));
7
-
8
- mock.module("../util/platform.js", () => ({
9
- getDataDir: () => testDir,
10
- getRootDir: () => testDir,
11
- isMacOS: () => process.platform === "darwin",
12
- isLinux: () => process.platform === "linux",
13
- isWindows: () => process.platform === "win32",
14
- getPidPath: () => join(testDir, "test.pid"),
15
- getDbPath: () => join(testDir, "test.db"),
16
- getLogPath: () => join(testDir, "test.log"),
17
- ensureDataDir: () => {},
18
- }));
19
-
20
- mock.module("../util/logger.js", () => ({
21
- getLogger: () =>
22
- new Proxy({} as Record<string, unknown>, {
23
- get: () => () => {},
24
- }),
25
- }));
26
-
27
- import { initializeDb, resetDb } from "../memory/db.js";
28
- import { getSqlite } from "../memory/db-connection.js";
29
- import { resetTestTables } from "../memory/raw-query.js";
30
- import {
31
- applyReducerResult,
32
- getActiveOpenLoops,
33
- getActiveTimeContexts,
34
- } from "../memory/reducer-store.js";
35
- import type { ReducerResult } from "../memory/reducer-types.js";
36
-
37
- initializeDb();
38
-
39
- // ── Helpers ──────────────────────────────────────────────────────────
40
-
41
- const SCOPE = "test-scope";
42
- const NOW = 1_700_000_000_000;
43
- const HOUR = 60 * 60 * 1000;
44
-
45
- function insertConversation(id: string): void {
46
- const raw = getSqlite();
47
- raw.run(
48
- `INSERT INTO conversations (id, title, created_at, updated_at, conversation_type, source, memory_scope_id, is_auto_title)
49
- VALUES (?, 'Test', ?, ?, 'standard', 'user', ?, 1)`,
50
- [id, NOW, NOW, SCOPE],
51
- );
52
- }
53
-
54
- function insertMessage(opts: {
55
- id: string;
56
- conversationId: string;
57
- role?: string;
58
- content?: string;
59
- createdAt?: number;
60
- }): void {
61
- const raw = getSqlite();
62
- raw.run(
63
- `INSERT INTO messages (id, conversation_id, role, content, created_at)
64
- VALUES (?, ?, ?, ?, ?)`,
65
- [
66
- opts.id,
67
- opts.conversationId,
68
- opts.role ?? "user",
69
- opts.content ?? "test message",
70
- opts.createdAt ?? NOW,
71
- ],
72
- );
73
- }
74
-
75
- function getRawConversation(conversationId: string): Record<string, unknown> {
76
- const raw = getSqlite();
77
- return raw
78
- .query(`SELECT * FROM conversations WHERE id = ?`)
79
- .get(conversationId) as Record<string, unknown>;
80
- }
81
-
82
- function getAllTimeContexts(): Array<Record<string, unknown>> {
83
- const raw = getSqlite();
84
- return raw.query(`SELECT * FROM time_contexts`).all() as Array<
85
- Record<string, unknown>
86
- >;
87
- }
88
-
89
- function getAllOpenLoops(): Array<Record<string, unknown>> {
90
- const raw = getSqlite();
91
- return raw.query(`SELECT * FROM open_loops`).all() as Array<
92
- Record<string, unknown>
93
- >;
94
- }
95
-
96
- function setDirtyTail(conversationId: string, messageId: string): void {
97
- const raw = getSqlite();
98
- raw.run(
99
- `UPDATE conversations SET memory_dirty_tail_since_message_id = ? WHERE id = ?`,
100
- [messageId, conversationId],
101
- );
102
- }
103
-
104
- function makeEmptyResult(): ReducerResult {
105
- return {
106
- timeContexts: [],
107
- openLoops: [],
108
- archiveObservations: [],
109
- archiveEpisodes: [],
110
- };
111
- }
112
-
113
- // ── Teardown ─────────────────────────────────────────────────────────
114
-
115
- afterAll(() => {
116
- resetDb();
117
- try {
118
- rmSync(testDir, { recursive: true });
119
- } catch {
120
- /* best effort */
121
- }
122
- });
123
-
124
- beforeEach(() => {
125
- resetTestTables("messages", "conversations", "time_contexts", "open_loops");
126
- });
127
-
128
- // ── Tests ────────────────────────────────────────────────────────────
129
-
130
- describe("applyReducerResult", () => {
131
- describe("time context operations", () => {
132
- test("creates a new time context", () => {
133
- insertConversation("conv-1");
134
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
135
-
136
- const result: ReducerResult = {
137
- ...makeEmptyResult(),
138
- timeContexts: [
139
- {
140
- action: "create",
141
- summary: "User traveling next week",
142
- source: "conversation",
143
- activeFrom: NOW,
144
- activeUntil: NOW + 7 * 24 * HOUR,
145
- },
146
- ],
147
- };
148
-
149
- applyReducerResult({
150
- result,
151
- conversationId: "conv-1",
152
- scopeId: SCOPE,
153
- reducedThroughMessageId: "msg-1",
154
- now: NOW,
155
- });
156
-
157
- const contexts = getAllTimeContexts();
158
- expect(contexts).toHaveLength(1);
159
- expect(contexts[0].summary).toBe("User traveling next week");
160
- expect(contexts[0].source).toBe("conversation");
161
- expect(contexts[0].scope_id).toBe(SCOPE);
162
- expect(contexts[0].active_from).toBe(NOW);
163
- expect(contexts[0].active_until).toBe(NOW + 7 * 24 * HOUR);
164
- });
165
-
166
- test("updates an existing time context", () => {
167
- insertConversation("conv-1");
168
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
169
-
170
- // First, create a time context directly
171
- const raw = getSqlite();
172
- raw.run(
173
- `INSERT INTO time_contexts (id, scope_id, summary, source, active_from, active_until, created_at, updated_at)
174
- VALUES ('tc-1', ?, 'Original summary', 'conversation', ?, ?, ?, ?)`,
175
- [SCOPE, NOW, NOW + HOUR, NOW, NOW],
176
- );
177
-
178
- const result: ReducerResult = {
179
- ...makeEmptyResult(),
180
- timeContexts: [
181
- {
182
- action: "update",
183
- id: "tc-1",
184
- summary: "Updated summary",
185
- activeUntil: NOW + 2 * HOUR,
186
- },
187
- ],
188
- };
189
-
190
- applyReducerResult({
191
- result,
192
- conversationId: "conv-1",
193
- scopeId: SCOPE,
194
- reducedThroughMessageId: "msg-1",
195
- now: NOW + 100,
196
- });
197
-
198
- const contexts = getAllTimeContexts();
199
- expect(contexts).toHaveLength(1);
200
- expect(contexts[0].summary).toBe("Updated summary");
201
- expect(contexts[0].active_until).toBe(NOW + 2 * HOUR);
202
- expect(contexts[0].updated_at).toBe(NOW + 100);
203
- });
204
-
205
- test("resolves (deletes) a time context", () => {
206
- insertConversation("conv-1");
207
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
208
-
209
- const raw = getSqlite();
210
- raw.run(
211
- `INSERT INTO time_contexts (id, scope_id, summary, source, active_from, active_until, created_at, updated_at)
212
- VALUES ('tc-1', ?, 'Some context', 'conversation', ?, ?, ?, ?)`,
213
- [SCOPE, NOW, NOW + HOUR, NOW, NOW],
214
- );
215
-
216
- const result: ReducerResult = {
217
- ...makeEmptyResult(),
218
- timeContexts: [{ action: "resolve", id: "tc-1" }],
219
- };
220
-
221
- applyReducerResult({
222
- result,
223
- conversationId: "conv-1",
224
- scopeId: SCOPE,
225
- reducedThroughMessageId: "msg-1",
226
- now: NOW,
227
- });
228
-
229
- expect(getAllTimeContexts()).toHaveLength(0);
230
- });
231
- });
232
-
233
- describe("open loop operations", () => {
234
- test("creates a new open loop", () => {
235
- insertConversation("conv-1");
236
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
237
-
238
- const result: ReducerResult = {
239
- ...makeEmptyResult(),
240
- openLoops: [
241
- {
242
- action: "create",
243
- summary: "Waiting for Bob's reply",
244
- source: "conversation",
245
- dueAt: NOW + 24 * HOUR,
246
- },
247
- ],
248
- };
249
-
250
- applyReducerResult({
251
- result,
252
- conversationId: "conv-1",
253
- scopeId: SCOPE,
254
- reducedThroughMessageId: "msg-1",
255
- now: NOW,
256
- });
257
-
258
- const loops = getAllOpenLoops();
259
- expect(loops).toHaveLength(1);
260
- expect(loops[0].summary).toBe("Waiting for Bob's reply");
261
- expect(loops[0].status).toBe("open");
262
- expect(loops[0].source).toBe("conversation");
263
- expect(loops[0].due_at).toBe(NOW + 24 * HOUR);
264
- expect(loops[0].scope_id).toBe(SCOPE);
265
- });
266
-
267
- test("creates an open loop without dueAt", () => {
268
- insertConversation("conv-1");
269
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
270
-
271
- const result: ReducerResult = {
272
- ...makeEmptyResult(),
273
- openLoops: [
274
- {
275
- action: "create",
276
- summary: "Need to follow up on project",
277
- source: "conversation",
278
- },
279
- ],
280
- };
281
-
282
- applyReducerResult({
283
- result,
284
- conversationId: "conv-1",
285
- scopeId: SCOPE,
286
- reducedThroughMessageId: "msg-1",
287
- now: NOW,
288
- });
289
-
290
- const loops = getAllOpenLoops();
291
- expect(loops).toHaveLength(1);
292
- expect(loops[0].due_at).toBeNull();
293
- });
294
-
295
- test("updates an existing open loop", () => {
296
- insertConversation("conv-1");
297
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
298
-
299
- const raw = getSqlite();
300
- raw.run(
301
- `INSERT INTO open_loops (id, scope_id, summary, status, source, due_at, created_at, updated_at)
302
- VALUES ('ol-1', ?, 'Original loop', 'open', 'conversation', NULL, ?, ?)`,
303
- [SCOPE, NOW, NOW],
304
- );
305
-
306
- const result: ReducerResult = {
307
- ...makeEmptyResult(),
308
- openLoops: [
309
- {
310
- action: "update",
311
- id: "ol-1",
312
- summary: "Updated loop summary",
313
- dueAt: NOW + 48 * HOUR,
314
- },
315
- ],
316
- };
317
-
318
- applyReducerResult({
319
- result,
320
- conversationId: "conv-1",
321
- scopeId: SCOPE,
322
- reducedThroughMessageId: "msg-1",
323
- now: NOW + 100,
324
- });
325
-
326
- const loops = getAllOpenLoops();
327
- expect(loops).toHaveLength(1);
328
- expect(loops[0].summary).toBe("Updated loop summary");
329
- expect(loops[0].due_at).toBe(NOW + 48 * HOUR);
330
- expect(loops[0].updated_at).toBe(NOW + 100);
331
- });
332
-
333
- test("resolves an open loop with resolved status", () => {
334
- insertConversation("conv-1");
335
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
336
-
337
- const raw = getSqlite();
338
- raw.run(
339
- `INSERT INTO open_loops (id, scope_id, summary, status, source, created_at, updated_at)
340
- VALUES ('ol-1', ?, 'Pending loop', 'open', 'conversation', ?, ?)`,
341
- [SCOPE, NOW, NOW],
342
- );
343
-
344
- const result: ReducerResult = {
345
- ...makeEmptyResult(),
346
- openLoops: [{ action: "resolve", id: "ol-1", status: "resolved" }],
347
- };
348
-
349
- applyReducerResult({
350
- result,
351
- conversationId: "conv-1",
352
- scopeId: SCOPE,
353
- reducedThroughMessageId: "msg-1",
354
- now: NOW,
355
- });
356
-
357
- const loops = getAllOpenLoops();
358
- expect(loops).toHaveLength(1);
359
- expect(loops[0].status).toBe("resolved");
360
- });
361
-
362
- test("resolves an open loop with expired status", () => {
363
- insertConversation("conv-1");
364
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
365
-
366
- const raw = getSqlite();
367
- raw.run(
368
- `INSERT INTO open_loops (id, scope_id, summary, status, source, created_at, updated_at)
369
- VALUES ('ol-1', ?, 'Expired loop', 'open', 'conversation', ?, ?)`,
370
- [SCOPE, NOW, NOW],
371
- );
372
-
373
- const result: ReducerResult = {
374
- ...makeEmptyResult(),
375
- openLoops: [{ action: "resolve", id: "ol-1", status: "expired" }],
376
- };
377
-
378
- applyReducerResult({
379
- result,
380
- conversationId: "conv-1",
381
- scopeId: SCOPE,
382
- reducedThroughMessageId: "msg-1",
383
- now: NOW,
384
- });
385
-
386
- const loops = getAllOpenLoops();
387
- expect(loops).toHaveLength(1);
388
- expect(loops[0].status).toBe("expired");
389
- });
390
- });
391
-
392
- describe("checkpoint advancement", () => {
393
- test("advances memoryReducedThroughMessageId and memoryLastReducedAt", () => {
394
- insertConversation("conv-1");
395
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
396
-
397
- applyReducerResult({
398
- result: makeEmptyResult(),
399
- conversationId: "conv-1",
400
- scopeId: SCOPE,
401
- reducedThroughMessageId: "msg-1",
402
- now: NOW + 500,
403
- });
404
-
405
- const conv = getRawConversation("conv-1");
406
- expect(conv.memory_reduced_through_message_id).toBe("msg-1");
407
- expect(conv.memory_last_reduced_at).toBe(NOW + 500);
408
- });
409
-
410
- test("clears dirty tail when fully caught up (no later messages)", () => {
411
- insertConversation("conv-1");
412
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
413
- setDirtyTail("conv-1", "msg-1");
414
-
415
- applyReducerResult({
416
- result: makeEmptyResult(),
417
- conversationId: "conv-1",
418
- scopeId: SCOPE,
419
- reducedThroughMessageId: "msg-1",
420
- now: NOW,
421
- });
422
-
423
- const conv = getRawConversation("conv-1");
424
- expect(conv.memory_dirty_tail_since_message_id).toBeNull();
425
- });
426
-
427
- test("preserves dirty tail when messages exist after the reduced-through message", () => {
428
- insertConversation("conv-1");
429
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
430
- insertMessage({
431
- id: "msg-2",
432
- conversationId: "conv-1",
433
- createdAt: NOW + 1000,
434
- });
435
- setDirtyTail("conv-1", "msg-1");
436
-
437
- applyReducerResult({
438
- result: makeEmptyResult(),
439
- conversationId: "conv-1",
440
- scopeId: SCOPE,
441
- reducedThroughMessageId: "msg-1",
442
- now: NOW,
443
- });
444
-
445
- const conv = getRawConversation("conv-1");
446
- // Dirty tail should remain since msg-2 still needs reducing
447
- expect(conv.memory_dirty_tail_since_message_id).toBe("msg-1");
448
- // But checkpoint should still advance
449
- expect(conv.memory_reduced_through_message_id).toBe("msg-1");
450
- });
451
-
452
- test("advances checkpoint when reducing through the middle of a conversation", () => {
453
- insertConversation("conv-1");
454
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
455
- insertMessage({
456
- id: "msg-2",
457
- conversationId: "conv-1",
458
- createdAt: NOW + 1000,
459
- });
460
- insertMessage({
461
- id: "msg-3",
462
- conversationId: "conv-1",
463
- createdAt: NOW + 2000,
464
- });
465
- setDirtyTail("conv-1", "msg-1");
466
-
467
- // Reduce through msg-2 (msg-3 still pending)
468
- applyReducerResult({
469
- result: makeEmptyResult(),
470
- conversationId: "conv-1",
471
- scopeId: SCOPE,
472
- reducedThroughMessageId: "msg-2",
473
- now: NOW + 3000,
474
- });
475
-
476
- const conv = getRawConversation("conv-1");
477
- expect(conv.memory_reduced_through_message_id).toBe("msg-2");
478
- expect(conv.memory_dirty_tail_since_message_id).toBe("msg-1");
479
- expect(conv.memory_last_reduced_at).toBe(NOW + 3000);
480
- });
481
- });
482
-
483
- describe("idempotent application", () => {
484
- test("applying the same result twice leaves state stable", () => {
485
- insertConversation("conv-1");
486
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
487
- setDirtyTail("conv-1", "msg-1");
488
-
489
- const result: ReducerResult = {
490
- ...makeEmptyResult(),
491
- openLoops: [
492
- {
493
- action: "create",
494
- summary: "Loop from first apply",
495
- source: "conversation",
496
- },
497
- ],
498
- timeContexts: [
499
- {
500
- action: "create",
501
- summary: "Context from first apply",
502
- source: "conversation",
503
- activeFrom: NOW,
504
- activeUntil: NOW + HOUR,
505
- },
506
- ],
507
- };
508
-
509
- const params = {
510
- result,
511
- conversationId: "conv-1",
512
- scopeId: SCOPE,
513
- reducedThroughMessageId: "msg-1",
514
- now: NOW,
515
- };
516
-
517
- // First application
518
- applyReducerResult(params);
519
-
520
- const convAfterFirst = getRawConversation("conv-1");
521
-
522
- // Second application — create ops will insert new rows since UUIDs differ,
523
- // but checkpoint state should remain consistent
524
- applyReducerResult(params);
525
-
526
- const convAfterSecond = getRawConversation("conv-1");
527
-
528
- // Checkpoint columns should be stable
529
- expect(convAfterSecond.memory_reduced_through_message_id).toBe(
530
- convAfterFirst.memory_reduced_through_message_id,
531
- );
532
- expect(convAfterSecond.memory_last_reduced_at).toBe(
533
- convAfterFirst.memory_last_reduced_at,
534
- );
535
- expect(convAfterSecond.memory_dirty_tail_since_message_id).toBe(
536
- convAfterFirst.memory_dirty_tail_since_message_id,
537
- );
538
- });
539
-
540
- test("applying an empty result still advances the checkpoint", () => {
541
- insertConversation("conv-1");
542
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
543
- setDirtyTail("conv-1", "msg-1");
544
-
545
- applyReducerResult({
546
- result: makeEmptyResult(),
547
- conversationId: "conv-1",
548
- scopeId: SCOPE,
549
- reducedThroughMessageId: "msg-1",
550
- now: NOW + 100,
551
- });
552
-
553
- const conv = getRawConversation("conv-1");
554
- expect(conv.memory_reduced_through_message_id).toBe("msg-1");
555
- expect(conv.memory_last_reduced_at).toBe(NOW + 100);
556
- expect(conv.memory_dirty_tail_since_message_id).toBeNull();
557
-
558
- // No side-effects on brief-state tables
559
- expect(getAllTimeContexts()).toHaveLength(0);
560
- expect(getAllOpenLoops()).toHaveLength(0);
561
- });
562
- });
563
-
564
- describe("mixed operations in a single result", () => {
565
- test("creates, updates, and resolves across both tables atomically", () => {
566
- insertConversation("conv-1");
567
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
568
-
569
- // Pre-existing rows to update/resolve
570
- const raw = getSqlite();
571
- raw.run(
572
- `INSERT INTO time_contexts (id, scope_id, summary, source, active_from, active_until, created_at, updated_at)
573
- VALUES ('tc-existing', ?, 'Will be updated', 'conversation', ?, ?, ?, ?)`,
574
- [SCOPE, NOW, NOW + HOUR, NOW, NOW],
575
- );
576
- raw.run(
577
- `INSERT INTO time_contexts (id, scope_id, summary, source, active_from, active_until, created_at, updated_at)
578
- VALUES ('tc-stale', ?, 'Will be resolved', 'conversation', ?, ?, ?, ?)`,
579
- [SCOPE, NOW - HOUR, NOW, NOW - HOUR, NOW - HOUR],
580
- );
581
- raw.run(
582
- `INSERT INTO open_loops (id, scope_id, summary, status, source, created_at, updated_at)
583
- VALUES ('ol-existing', ?, 'Will be resolved', 'open', 'conversation', ?, ?)`,
584
- [SCOPE, NOW, NOW],
585
- );
586
-
587
- const result: ReducerResult = {
588
- ...makeEmptyResult(),
589
- timeContexts: [
590
- {
591
- action: "create",
592
- summary: "New context",
593
- source: "conversation",
594
- activeFrom: NOW,
595
- activeUntil: NOW + 2 * HOUR,
596
- },
597
- {
598
- action: "update",
599
- id: "tc-existing",
600
- summary: "Updated context",
601
- },
602
- { action: "resolve", id: "tc-stale" },
603
- ],
604
- openLoops: [
605
- {
606
- action: "create",
607
- summary: "New loop",
608
- source: "conversation",
609
- },
610
- { action: "resolve", id: "ol-existing", status: "resolved" },
611
- ],
612
- };
613
-
614
- applyReducerResult({
615
- result,
616
- conversationId: "conv-1",
617
- scopeId: SCOPE,
618
- reducedThroughMessageId: "msg-1",
619
- now: NOW + 200,
620
- });
621
-
622
- // Time contexts: 1 created + 1 updated (tc-stale deleted by resolve)
623
- const contexts = getAllTimeContexts();
624
- expect(contexts).toHaveLength(2);
625
- const updated = contexts.find((c) => c.id === "tc-existing");
626
- expect(updated?.summary).toBe("Updated context");
627
- const created = contexts.find((c) => c.id !== "tc-existing");
628
- expect(created?.summary).toBe("New context");
629
-
630
- // Open loops: 1 created + 1 resolved
631
- const loops = getAllOpenLoops();
632
- expect(loops).toHaveLength(2);
633
- const resolvedLoop = loops.find((l) => l.id === "ol-existing");
634
- expect(resolvedLoop?.status).toBe("resolved");
635
- const newLoop = loops.find((l) => l.id !== "ol-existing");
636
- expect(newLoop?.status).toBe("open");
637
- expect(newLoop?.summary).toBe("New loop");
638
- });
639
- });
640
-
641
- describe("archive candidates are ignored", () => {
642
- test("archive observations and episodes in result are not persisted", () => {
643
- insertConversation("conv-1");
644
- insertMessage({ id: "msg-1", conversationId: "conv-1", createdAt: NOW });
645
-
646
- const result: ReducerResult = {
647
- timeContexts: [],
648
- openLoops: [],
649
- archiveObservations: [{ content: "User likes coffee", role: "user" }],
650
- archiveEpisodes: [
651
- { title: "Coffee discussion", summary: "Talked about coffee" },
652
- ],
653
- };
654
-
655
- // Should not throw and should not attempt to write archive data
656
- applyReducerResult({
657
- result,
658
- conversationId: "conv-1",
659
- scopeId: SCOPE,
660
- reducedThroughMessageId: "msg-1",
661
- now: NOW,
662
- });
663
-
664
- // Checkpoint should still advance
665
- const conv = getRawConversation("conv-1");
666
- expect(conv.memory_reduced_through_message_id).toBe("msg-1");
667
- });
668
- });
669
- });
670
-
671
- describe("getActiveTimeContexts", () => {
672
- test("returns only non-expired time contexts for the scope", () => {
673
- const raw = getSqlite();
674
- // Active context (expires in the future)
675
- raw.run(
676
- `INSERT INTO time_contexts (id, scope_id, summary, source, active_from, active_until, created_at, updated_at)
677
- VALUES ('tc-active', ?, 'Active context', 'conversation', ?, ?, ?, ?)`,
678
- [SCOPE, NOW - HOUR, NOW + HOUR, NOW, NOW],
679
- );
680
- // Expired context
681
- raw.run(
682
- `INSERT INTO time_contexts (id, scope_id, summary, source, active_from, active_until, created_at, updated_at)
683
- VALUES ('tc-expired', ?, 'Expired context', 'conversation', ?, ?, ?, ?)`,
684
- [SCOPE, NOW - 2 * HOUR, NOW - HOUR, NOW, NOW],
685
- );
686
- // Different scope
687
- raw.run(
688
- `INSERT INTO time_contexts (id, scope_id, summary, source, active_from, active_until, created_at, updated_at)
689
- VALUES ('tc-other', 'other-scope', 'Other scope context', 'conversation', ?, ?, ?, ?)`,
690
- [NOW, NOW + HOUR, NOW, NOW],
691
- );
692
-
693
- const active = getActiveTimeContexts(SCOPE, NOW);
694
- expect(active).toHaveLength(1);
695
- expect(active[0].id).toBe("tc-active");
696
- expect(active[0].summary).toBe("Active context");
697
- });
698
- });
699
-
700
- describe("getActiveOpenLoops", () => {
701
- test("returns only open loops for the scope", () => {
702
- const raw = getSqlite();
703
- // Open loop
704
- raw.run(
705
- `INSERT INTO open_loops (id, scope_id, summary, status, source, created_at, updated_at)
706
- VALUES ('ol-open', ?, 'Open loop', 'open', 'conversation', ?, ?)`,
707
- [SCOPE, NOW, NOW],
708
- );
709
- // Resolved loop
710
- raw.run(
711
- `INSERT INTO open_loops (id, scope_id, summary, status, source, created_at, updated_at)
712
- VALUES ('ol-resolved', ?, 'Resolved loop', 'resolved', 'conversation', ?, ?)`,
713
- [SCOPE, NOW, NOW],
714
- );
715
- // Different scope
716
- raw.run(
717
- `INSERT INTO open_loops (id, scope_id, summary, status, source, created_at, updated_at)
718
- VALUES ('ol-other', 'other-scope', 'Other scope loop', 'open', 'conversation', ?, ?)`,
719
- [NOW, NOW],
720
- );
721
-
722
- const active = getActiveOpenLoops(SCOPE);
723
- expect(active).toHaveLength(1);
724
- expect(active[0].id).toBe("ol-open");
725
- expect(active[0].summary).toBe("Open loop");
726
- expect(active[0].status).toBe("open");
727
- });
728
- });