@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
@@ -20,13 +20,7 @@ import {
20
20
  import { getConfig } from "../config/loader.js";
21
21
  import { onContactChange } from "../contacts/contact-events.js";
22
22
  import type { CesClient } from "../credential-execution/client.js";
23
- import { createCesClient } from "../credential-execution/client.js";
24
- import { isCesToolsEnabled } from "../credential-execution/feature-gates.js";
25
- import {
26
- type CesProcessManager,
27
- CesUnavailableError,
28
- createCesProcessManager,
29
- } from "../credential-execution/process-manager.js";
23
+ import type { CesProcessManager } from "../credential-execution/process-manager.js";
30
24
  import type { HeartbeatService } from "../heartbeat/heartbeat-service.js";
31
25
  import * as attachmentsStore from "../memory/attachments-store.js";
32
26
  import {
@@ -45,7 +39,6 @@ import {
45
39
  import { updateMetaFile } from "../memory/conversation-disk-view.js";
46
40
  import { getOrCreateConversation } from "../memory/conversation-key-store.js";
47
41
  import { buildSystemPrompt } from "../prompts/system-prompt.js";
48
- import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
49
42
  import { RateLimitProvider } from "../providers/ratelimit.js";
50
43
  import {
51
44
  getFailoverProvider,
@@ -57,13 +50,11 @@ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
57
50
  import { getSigningKeyFingerprint } from "../runtime/auth/token-service.js";
58
51
  import { bridgeConfirmationRequestToGuardian } from "../runtime/confirmation-request-guardian-bridge.js";
59
52
  import * as pendingInteractions from "../runtime/pending-interactions.js";
60
- import { checkIngressForSecrets } from "../security/secret-ingress.js";
61
53
  import { registerCancelCallback } from "../signals/cancel.js";
62
54
  import { registerConversationUndoCallback } from "../signals/conversation-undo.js";
63
55
  import { appendEventToStream } from "../signals/event-stream.js";
64
56
  import { registerUserMessageCallback } from "../signals/user-message.js";
65
57
  import { getSubagentManager } from "../subagent/index.js";
66
- import { IngressBlockedError } from "../util/errors.js";
67
58
  import { getLogger } from "../util/logger.js";
68
59
  import {
69
60
  getSandboxWorkingDir,
@@ -253,7 +244,6 @@ export class DaemonServer {
253
244
  private conversationOptions = new Map<string, ConversationCreateOptions>();
254
245
  private conversationCreating = new Map<string, Promise<Conversation>>();
255
246
  private sharedRequestTimestamps: number[] = [];
256
- private httpPort: number | undefined;
257
247
  private unsubscribeContactChange: (() => void) | null = null;
258
248
  private evictor: ConversationEvictor;
259
249
  private _hubChain: Promise<void> = Promise.resolve();
@@ -262,8 +252,8 @@ export class DaemonServer {
262
252
  private configWatcher = new ConfigWatcher();
263
253
 
264
254
  // CES (Credential Execution Service) — process-level singleton.
265
- // The CES sidecar accepts exactly one bootstrap connection, so we must
266
- // hold that connection at the server level rather than per-conversation.
255
+ // Lifecycle is managed by startCesProcess() in lifecycle.ts; the server
256
+ // receives the result via setCes().
267
257
  private cesProcessManager?: CesProcessManager;
268
258
  private cesClientPromise?: Promise<CesClient | undefined>;
269
259
  private cesInitAbortController?: AbortController;
@@ -274,6 +264,31 @@ export class DaemonServer {
274
264
  */
275
265
  assistantId: string = DAEMON_INTERNAL_ASSISTANT_ID;
276
266
 
267
+ /**
268
+ * Inject the CES client and process manager from the caller (lifecycle.ts).
269
+ * Must be called before start().
270
+ */
271
+ setCes(result: {
272
+ client: CesClient | undefined;
273
+ processManager: CesProcessManager | undefined;
274
+ clientPromise: Promise<CesClient | undefined> | undefined;
275
+ abortController: AbortController | undefined;
276
+ }): void {
277
+ this.cesClientRef = result.client;
278
+ this.cesProcessManager = result.processManager;
279
+ this.cesInitAbortController = result.abortController;
280
+
281
+ // Wrap the external promise so that cesClientRef stays in sync once the
282
+ // handshake completes — the async work runs in lifecycle.ts but the
283
+ // server needs the resolved client reference for getCesClient().
284
+ if (result.clientPromise) {
285
+ this.cesClientPromise = result.clientPromise.then((client) => {
286
+ this.cesClientRef = client;
287
+ return client;
288
+ });
289
+ }
290
+ }
291
+
277
292
  /**
278
293
  * Return the CES client reference (if available).
279
294
  * Used by routes that need to push updates to CES (e.g. secret-routes).
@@ -538,73 +553,6 @@ export class DaemonServer {
538
553
  this.broadcast({ type: "contacts_changed" });
539
554
  });
540
555
 
541
- // CES lifecycle — start the CES process and perform the RPC handshake
542
- // once at server level. The managed sidecar accepts exactly one bootstrap
543
- // connection, so this must be a process-level singleton.
544
- if (isCesToolsEnabled(config)) {
545
- const pm = createCesProcessManager({ assistantConfig: config });
546
- this.cesProcessManager = pm;
547
- const abortController = new AbortController();
548
- this.cesInitAbortController = abortController;
549
- this.cesClientPromise = (async () => {
550
- try {
551
- const transport = await pm.start();
552
- if (abortController.signal.aborted) {
553
- throw new Error("CES initialization aborted during shutdown");
554
- }
555
- const client = createCesClient(transport);
556
- this.cesClientRef = client;
557
- // Resolve the assistant API key so CES can use it for platform
558
- // credential materialisation. In managed mode the key is provisioned
559
- // after hatch and stored in the credential store — CES can't read
560
- // the env var, so we pass it via the handshake.
561
- const proxyCtx = await resolveManagedProxyContext();
562
- const { accepted, reason } = await client.handshake(
563
- proxyCtx.assistantApiKey
564
- ? { assistantApiKey: proxyCtx.assistantApiKey }
565
- : undefined,
566
- );
567
- if (abortController.signal.aborted) {
568
- client.close();
569
- throw new Error("CES initialization aborted during shutdown");
570
- }
571
- if (accepted) {
572
- log.info(
573
- "CES client initialized and handshake accepted (server-level)",
574
- );
575
- return client;
576
- }
577
- log.warn(
578
- { reason },
579
- "CES handshake rejected — CES tools will be unavailable",
580
- );
581
- client.close();
582
- this.cesClientRef = undefined;
583
- await pm.stop();
584
- // Reset so next session can retry initialization
585
- this.cesClientPromise = undefined;
586
- return undefined;
587
- } catch (err) {
588
- if (err instanceof CesUnavailableError) {
589
- log.info(
590
- { reason: err.message },
591
- "CES is not available — CES tools will be unavailable",
592
- );
593
- } else {
594
- log.warn(
595
- { error: err instanceof Error ? err.message : String(err) },
596
- "Failed to initialize CES client — CES tools will be unavailable",
597
- );
598
- }
599
- await pm.stop().catch(() => {});
600
- // Reset so next session can retry initialization
601
- this.cesClientRef = undefined;
602
- this.cesClientPromise = undefined;
603
- return undefined;
604
- }
605
- })();
606
- }
607
-
608
556
  log.info("DaemonServer started (HTTP-only mode)");
609
557
  }
610
558
 
@@ -656,11 +604,9 @@ export class DaemonServer {
656
604
 
657
605
  // ── Conversation management ──────────────────────────────────────────────
658
606
 
659
- setHttpPort(port: number): void {
660
- this.httpPort = port;
607
+ broadcastStatus(): void {
661
608
  this.broadcast({
662
- type: "daemon_status",
663
- httpPort: port,
609
+ type: "assistant_status",
664
610
  version: daemonVersion,
665
611
  keyFingerprint: getSigningKeyFingerprint(),
666
612
  });
@@ -884,14 +830,6 @@ export class DaemonServer {
884
830
  filePath?: string;
885
831
  }[];
886
832
  }> {
887
- const ingressCheck = checkIngressForSecrets(content);
888
- if (ingressCheck.blocked) {
889
- throw new IngressBlockedError(
890
- ingressCheck.userNotice!,
891
- ingressCheck.detectedTypes,
892
- );
893
- }
894
-
895
833
  const conversation = await this.getOrCreateConversation(
896
834
  conversationId,
897
835
  options,
@@ -25,7 +25,8 @@ export interface ToolDomainEvents {
25
25
  | "always_allow_high_risk"
26
26
  | "deny"
27
27
  | "always_deny"
28
- | "temporary_override";
28
+ | "temporary_override"
29
+ | "dangerously_skip_permissions";
29
30
  riskLevel: string;
30
31
  decidedAtMs: number;
31
32
  };
@@ -1,4 +1,4 @@
1
- import { and, desc, eq, lte, or } from "drizzle-orm";
1
+ import { and, asc, eq, lte, or, sql } from "drizzle-orm";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
4
  import { getDb } from "../memory/db.js";
@@ -217,7 +217,10 @@ export function getPendingAndOverdueFollowUps(): BriefFollowUp[] {
217
217
  eq(followups.status, "nudged"),
218
218
  ),
219
219
  )
220
- .orderBy(desc(followups.expectedResponseBy))
220
+ .orderBy(
221
+ sql`CASE WHEN ${followups.expectedResponseBy} IS NULL THEN 1 ELSE 0 END`,
222
+ asc(followups.expectedResponseBy),
223
+ )
221
224
  .all();
222
225
 
223
226
  return rows as BriefFollowUp[];
@@ -2,7 +2,7 @@
2
2
  * Platform callback route registration for containerized deployments.
3
3
  *
4
4
  * When the assistant daemon runs inside a container (IS_CONTAINERIZED=true)
5
- * with a configured PLATFORM_BASE_URL and PLATFORM_ASSISTANT_ID, external
5
+ * with a configured VELLUM_PLATFORM_URL and PLATFORM_ASSISTANT_ID, external
6
6
  * service callbacks (Twilio webhooks, OAuth redirects, Telegram webhooks, etc.)
7
7
  * must route through the platform's gateway proxy instead of hitting the
8
8
  * assistant directly.
@@ -12,7 +12,7 @@
12
12
  * webhooks to the correct containerized assistant instance.
13
13
  *
14
14
  * The platform endpoint is:
15
- * POST {PLATFORM_BASE_URL}/v1/internal/gateway/callback-routes/register/
15
+ * POST {VELLUM_PLATFORM_URL}/v1/internal/gateway/callback-routes/register/
16
16
  *
17
17
  * It accepts { assistant_id, callback_path, type } and returns a stable
18
18
  * callback_url that external services should use.
@@ -30,7 +30,7 @@ const log = getLogger("platform-callback-registration");
30
30
 
31
31
  /**
32
32
  * Whether the daemon should register callback routes with the platform.
33
- * True when IS_CONTAINERIZED, PLATFORM_BASE_URL, and PLATFORM_ASSISTANT_ID
33
+ * True when IS_CONTAINERIZED, VELLUM_PLATFORM_URL, and PLATFORM_ASSISTANT_ID
34
34
  * are all set. Intentionally does **not** require the managed proxy API key
35
35
  * so that callback-only flows (OAuth transport, Telegram/Twilio callback
36
36
  * registration) work during partial bootstrap before the key is injected.
package/src/instrument.ts CHANGED
@@ -39,14 +39,17 @@ function redactObject(obj: unknown): unknown {
39
39
  }
40
40
 
41
41
  /**
42
- * Call after dotenv has loaded so SENTRY_DSN is available.
43
- * Always initializes Sentry to capture early startup crashes. If the user
44
- * later opts out via the sendDiagnostics config key (or VELLUM_DEV=1),
45
- * call closeSentry() after config is loaded to stop future event capturing.
42
+ * Call after dotenv has loaded so SENTRY_DSN_ASSISTANT is available.
43
+ * Initializes Sentry when the DSN is set; no-ops when empty/unset so
44
+ * local dev builds don't send crash reports. If the user later opts out
45
+ * via the sendDiagnostics config key (or VELLUM_DEV=1), call closeSentry()
46
+ * after config is loaded to stop future event capturing.
46
47
  */
47
48
  export function initSentry(): void {
49
+ const dsn = getSentryDsn();
50
+ if (!dsn) return;
48
51
  Sentry.init({
49
- dsn: getSentryDsn(),
52
+ dsn,
50
53
  release: `vellum-assistant@${APP_VERSION}`,
51
54
  dist: COMMIT_SHA,
52
55
  environment: APP_VERSION === "0.0.0-dev" ? "development" : "production",
@@ -47,18 +47,13 @@ import {
47
47
  conversations,
48
48
  conversationStarters,
49
49
  llmRequestLogs,
50
- memoryChunks,
51
50
  memoryEmbeddings,
52
- memoryEpisodes,
53
51
  memoryItems,
54
52
  memoryItemSources,
55
- memoryObservations,
56
53
  memorySegments,
57
54
  memorySummaries,
58
55
  messageAttachments,
59
56
  messages,
60
- openLoops,
61
- timeContexts,
62
57
  toolInvocations,
63
58
  } from "./schema.js";
64
59
  import { cancelPendingJobsForConversation } from "./task-memory-cleanup.js";
@@ -177,9 +172,6 @@ export interface ConversationRow {
177
172
  forkParentMessageId: string | null;
178
173
  isAutoTitle: number;
179
174
  scheduleJobId: string | null;
180
- memoryReducedThroughMessageId: string | null;
181
- memoryDirtyTailSinceMessageId: string | null;
182
- memoryLastReducedAt: number | null;
183
175
  }
184
176
 
185
177
  export const parseConversation = createRowMapper<
@@ -205,9 +197,6 @@ export const parseConversation = createRowMapper<
205
197
  forkParentMessageId: "forkParentMessageId",
206
198
  isAutoTitle: "isAutoTitle",
207
199
  scheduleJobId: "scheduleJobId",
208
- memoryReducedThroughMessageId: "memoryReducedThroughMessageId",
209
- memoryDirtyTailSinceMessageId: "memoryDirtyTailSinceMessageId",
210
- memoryLastReducedAt: "memoryLastReducedAt",
211
200
  });
212
201
 
213
202
  export interface MessageRow {
@@ -555,9 +544,6 @@ export function deleteConversation(id: string): DeletedMemoryIds {
555
544
  segmentIds: [],
556
545
  orphanedItemIds: [],
557
546
  deletedSummaryIds: [],
558
- deletedObservationIds: [],
559
- deletedChunkIds: [],
560
- deletedEpisodeIds: [],
561
547
  };
562
548
 
563
549
  // Capture createdAt before the transaction deletes the row — needed to
@@ -711,75 +697,6 @@ export function deleteConversation(id: string): DeletedMemoryIds {
711
697
  tx.delete(conversationStarters)
712
698
  .where(eq(conversationStarters.scopeId, memoryScopeId))
713
699
  .run();
714
-
715
- // Sweep brief-state tables scoped to this private conversation.
716
- tx.delete(timeContexts)
717
- .where(eq(timeContexts.scopeId, memoryScopeId))
718
- .run();
719
- tx.delete(openLoops).where(eq(openLoops.scopeId, memoryScopeId)).run();
720
- }
721
-
722
- // Collect archive table IDs before the cascade delete removes them.
723
- // Observations and episodes reference conversations with ON DELETE CASCADE,
724
- // and chunks cascade from observations.
725
- const observationRows = tx
726
- .select({ id: memoryObservations.id })
727
- .from(memoryObservations)
728
- .where(eq(memoryObservations.conversationId, id))
729
- .all();
730
- const observationIds = observationRows.map((r) => r.id);
731
-
732
- if (observationIds.length > 0) {
733
- // Collect chunk IDs before observations cascade-delete them.
734
- const chunkRows = tx
735
- .select({ id: memoryChunks.id })
736
- .from(memoryChunks)
737
- .where(inArray(memoryChunks.observationId, observationIds))
738
- .all();
739
- const chunkIds = chunkRows.map((r) => r.id);
740
-
741
- // Clean up embeddings for chunks.
742
- if (chunkIds.length > 0) {
743
- tx.delete(memoryEmbeddings)
744
- .where(
745
- and(
746
- eq(memoryEmbeddings.targetType, "chunk"),
747
- inArray(memoryEmbeddings.targetId, chunkIds),
748
- ),
749
- )
750
- .run();
751
- result.deletedChunkIds.push(...chunkIds);
752
- }
753
-
754
- // Clean up embeddings for observations.
755
- tx.delete(memoryEmbeddings)
756
- .where(
757
- and(
758
- eq(memoryEmbeddings.targetType, "observation"),
759
- inArray(memoryEmbeddings.targetId, observationIds),
760
- ),
761
- )
762
- .run();
763
- result.deletedObservationIds.push(...observationIds);
764
- }
765
-
766
- const episodeRows = tx
767
- .select({ id: memoryEpisodes.id })
768
- .from(memoryEpisodes)
769
- .where(eq(memoryEpisodes.conversationId, id))
770
- .all();
771
- const episodeIds = episodeRows.map((r) => r.id);
772
-
773
- if (episodeIds.length > 0) {
774
- tx.delete(memoryEmbeddings)
775
- .where(
776
- and(
777
- eq(memoryEmbeddings.targetType, "episode"),
778
- inArray(memoryEmbeddings.targetId, episodeIds),
779
- ),
780
- )
781
- .run();
782
- result.deletedEpisodeIds.push(...episodeIds);
783
700
  }
784
701
 
785
702
  tx.delete(conversations).where(eq(conversations.id, id)).run();
@@ -1005,9 +922,6 @@ export function purgePrivateConversations(): {
1005
922
  segmentIds: [],
1006
923
  orphanedItemIds: [],
1007
924
  deletedSummaryIds: [],
1008
- deletedObservationIds: [],
1009
- deletedChunkIds: [],
1010
- deletedEpisodeIds: [],
1011
925
  },
1012
926
  };
1013
927
  }
@@ -1015,18 +929,12 @@ export function purgePrivateConversations(): {
1015
929
  const allSegmentIds: string[] = [];
1016
930
  const allOrphanedItemIds: string[] = [];
1017
931
  const allDeletedSummaryIds: string[] = [];
1018
- const allDeletedObservationIds: string[] = [];
1019
- const allDeletedChunkIds: string[] = [];
1020
- const allDeletedEpisodeIds: string[] = [];
1021
932
 
1022
933
  for (const conv of privateConvs) {
1023
934
  const deleted = deleteConversation(conv.id);
1024
935
  allSegmentIds.push(...deleted.segmentIds);
1025
936
  allOrphanedItemIds.push(...deleted.orphanedItemIds);
1026
937
  allDeletedSummaryIds.push(...deleted.deletedSummaryIds);
1027
- allDeletedObservationIds.push(...deleted.deletedObservationIds);
1028
- allDeletedChunkIds.push(...deleted.deletedChunkIds);
1029
- allDeletedEpisodeIds.push(...deleted.deletedEpisodeIds);
1030
938
  }
1031
939
 
1032
940
  return {
@@ -1035,9 +943,6 @@ export function purgePrivateConversations(): {
1035
943
  segmentIds: allSegmentIds,
1036
944
  orphanedItemIds: allOrphanedItemIds,
1037
945
  deletedSummaryIds: allDeletedSummaryIds,
1038
- deletedObservationIds: allDeletedObservationIds,
1039
- deletedChunkIds: allDeletedChunkIds,
1040
- deletedEpisodeIds: allDeletedEpisodeIds,
1041
946
  },
1042
947
  };
1043
948
  }
@@ -1120,13 +1025,6 @@ export async function addMessage(
1120
1025
  throw err;
1121
1026
  }
1122
1027
  }
1123
-
1124
- // Mark the conversation dirty for delayed memory reduction. This runs
1125
- // after the insert transaction succeeds so the reducer knows which
1126
- // conversations have unprocessed messages. The helper preserves the
1127
- // earliest unreduced boundary (no-op when already dirty).
1128
- markConversationMemoryDirty(conversationId, messageId);
1129
-
1130
1028
  const message = {
1131
1029
  id: messageId,
1132
1030
  conversationId,
@@ -1431,9 +1329,6 @@ export interface DeletedMemoryIds {
1431
1329
  segmentIds: string[];
1432
1330
  orphanedItemIds: string[];
1433
1331
  deletedSummaryIds: string[];
1434
- deletedObservationIds: string[];
1435
- deletedChunkIds: string[];
1436
- deletedEpisodeIds: string[];
1437
1332
  }
1438
1333
 
1439
1334
  export interface WipeConversationResult extends DeletedMemoryIds {
@@ -1507,9 +1402,6 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
1507
1402
  segmentIds: [],
1508
1403
  orphanedItemIds: [],
1509
1404
  deletedSummaryIds: [],
1510
- deletedObservationIds: [],
1511
- deletedChunkIds: [],
1512
- deletedEpisodeIds: [],
1513
1405
  };
1514
1406
 
1515
1407
  // Collect attachment IDs linked to this message before cascade-delete
@@ -1598,134 +1490,6 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
1598
1490
  return result;
1599
1491
  }
1600
1492
 
1601
- /**
1602
- * Mark a conversation as having unreduced messages starting from the given
1603
- * message. Sets `memoryDirtyTailSinceMessageId` only when it is currently
1604
- * null so the earliest unreduced boundary is preserved across multiple
1605
- * messages — later messages must not clobber the original dirty marker.
1606
- *
1607
- * Also upserts a pending `reduce_conversation_memory` job scheduled at
1608
- * `now + idleDelayMs`. If a pending job for this conversation already exists,
1609
- * its `runAfter` is pushed forward (rescheduled) so the reducer waits for
1610
- * the full idle window after the *latest* message — avoiding premature runs
1611
- * while the user is still actively typing.
1612
- */
1613
- export function markConversationMemoryDirty(
1614
- conversationId: string,
1615
- messageId: string,
1616
- ): void {
1617
- const db = getDb();
1618
- db.update(conversations)
1619
- .set({ memoryDirtyTailSinceMessageId: messageId })
1620
- .where(
1621
- and(
1622
- eq(conversations.id, conversationId),
1623
- isNull(conversations.memoryDirtyTailSinceMessageId),
1624
- ),
1625
- )
1626
- .run();
1627
-
1628
- // Schedule (or reschedule) a deferred reducer job for this conversation.
1629
- scheduleReducerJob(conversationId);
1630
- }
1631
-
1632
- /**
1633
- * Upsert a pending `reduce_conversation_memory` job for the given
1634
- * conversation, scheduled `idleDelayMs` from now. If one already exists in
1635
- * pending state, its `runAfter` is pushed forward to restart the idle timer.
1636
- * This ensures exactly one pending reducer job per conversation — new
1637
- * messages reschedule rather than duplicate.
1638
- */
1639
- export function scheduleReducerJob(
1640
- conversationId: string,
1641
- runAfter?: number,
1642
- ): void {
1643
- const idleDelayMs = getReducerIdleDelayMs();
1644
- const scheduledAt = runAfter ?? Date.now() + idleDelayMs;
1645
-
1646
- const existing = rawGet<{ id: string; status: string }>(
1647
- `SELECT id, status FROM memory_jobs
1648
- WHERE type = 'reduce_conversation_memory'
1649
- AND json_extract(payload, '$.conversationId') = ?
1650
- AND status = 'pending'
1651
- LIMIT 1`,
1652
- conversationId,
1653
- );
1654
-
1655
- if (existing) {
1656
- // Reschedule: push runAfter forward so the idle timer resets.
1657
- rawRun(
1658
- `UPDATE memory_jobs SET run_after = ?, updated_at = ? WHERE id = ?`,
1659
- scheduledAt,
1660
- Date.now(),
1661
- existing.id,
1662
- );
1663
- } else {
1664
- enqueueMemoryJob(
1665
- "reduce_conversation_memory",
1666
- { conversationId },
1667
- scheduledAt,
1668
- );
1669
- }
1670
- }
1671
-
1672
- /**
1673
- * Startup sweep: find conversations that are marked dirty and whose tail
1674
- * message is already older than the idle delay. For these conversations the
1675
- * reducer should have run but didn't (daemon was down). Enqueue immediate
1676
- * reducer jobs for each so they are processed on the next worker tick.
1677
- *
1678
- * Conversations whose tail is still within the idle window are skipped —
1679
- * the normal `markConversationMemoryDirty` path will schedule them when
1680
- * new messages arrive (or on the next conversation interaction).
1681
- *
1682
- * Returns the number of jobs enqueued.
1683
- */
1684
- export function sweepStaleReducerJobs(): number {
1685
- const idleDelayMs = getReducerIdleDelayMs();
1686
- const cutoff = Date.now() - idleDelayMs;
1687
-
1688
- // Find dirty conversations whose latest message is older than the idle
1689
- // window AND that don't already have a pending reducer job.
1690
- const stale = rawAll<{ conversationId: string }>(
1691
- `SELECT c.id AS conversationId
1692
- FROM conversations c
1693
- WHERE c.memory_dirty_tail_since_message_id IS NOT NULL
1694
- AND NOT EXISTS (
1695
- SELECT 1 FROM memory_jobs mj
1696
- WHERE mj.type = 'reduce_conversation_memory'
1697
- AND json_extract(mj.payload, '$.conversationId') = c.id
1698
- AND mj.status IN ('pending', 'running')
1699
- )
1700
- AND (
1701
- SELECT MAX(m.created_at) FROM messages m
1702
- WHERE m.conversation_id = c.id
1703
- ) <= ?`,
1704
- cutoff,
1705
- );
1706
-
1707
- for (const { conversationId } of stale) {
1708
- enqueueMemoryJob("reduce_conversation_memory", { conversationId });
1709
- }
1710
-
1711
- return stale.length;
1712
- }
1713
-
1714
- function getReducerIdleDelayMs(): number {
1715
- // Some test suites mock getConfig() with partial objects; fall back to the
1716
- // schema default so reducer scheduling stays stable outside full config load.
1717
- const config = getConfig() as {
1718
- memory?: {
1719
- simplified?: {
1720
- reducer?: {
1721
- idleDelayMs?: number;
1722
- };
1723
- };
1724
- };
1725
- };
1726
- return config.memory?.simplified?.reducer?.idleDelayMs ?? 30_000;
1727
- }
1728
-
1729
1493
  export function setConversationOriginChannelIfUnset(
1730
1494
  conversationId: string,
1731
1495
  channel: ChannelId,