@vellumai/assistant 0.3.5 → 0.3.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 (487) hide show
  1. package/README.md +51 -0
  2. package/eslint.config.mjs +31 -0
  3. package/package.json +1 -1
  4. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  5. package/scripts/ipc/generate-swift.ts +18 -2
  6. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +338 -1
  7. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  8. package/src/__tests__/browser-manager.test.ts +1 -0
  9. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  10. package/src/__tests__/call-orchestrator.test.ts +752 -271
  11. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  12. package/src/__tests__/call-recovery.test.ts +3 -0
  13. package/src/__tests__/call-routes-http.test.ts +5 -0
  14. package/src/__tests__/call-store.test.ts +3 -0
  15. package/src/__tests__/channel-approval-routes.test.ts +1260 -85
  16. package/src/__tests__/channel-approval.test.ts +37 -0
  17. package/src/__tests__/channel-approvals.test.ts +4 -65
  18. package/src/__tests__/channel-guardian.test.ts +556 -0
  19. package/src/__tests__/channel-readiness-service.test.ts +74 -7
  20. package/src/__tests__/checker.test.ts +14 -7
  21. package/src/__tests__/clarification-resolver.test.ts +44 -24
  22. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  23. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  24. package/src/__tests__/config-schema.test.ts +12 -7
  25. package/src/__tests__/context-window-manager.test.ts +30 -2
  26. package/src/__tests__/contradiction-checker.test.ts +20 -5
  27. package/src/__tests__/credential-security-invariants.test.ts +6 -2
  28. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  29. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  30. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  31. package/src/__tests__/guardian-action-store.test.ts +123 -0
  32. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  33. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  34. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  35. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  36. package/src/__tests__/handlers-twilio-config.test.ts +126 -0
  37. package/src/__tests__/intent-routing.test.ts +2 -0
  38. package/src/__tests__/ipc-snapshot.test.ts +228 -1
  39. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  40. package/src/__tests__/model-intents.test.ts +96 -0
  41. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  42. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  43. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  44. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  45. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  46. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  47. package/src/__tests__/qdrant-manager.test.ts +27 -20
  48. package/src/__tests__/relay-server.test.ts +779 -40
  49. package/src/__tests__/run-orchestrator-assistant-events.test.ts +2 -0
  50. package/src/__tests__/run-orchestrator.test.ts +20 -4
  51. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  52. package/src/__tests__/runtime-runs.test.ts +16 -0
  53. package/src/__tests__/schedule-store.test.ts +18 -4
  54. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  55. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  56. package/src/__tests__/session-agent-loop.test.ts +857 -0
  57. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  58. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  59. package/src/__tests__/session-profile-injection.test.ts +6 -0
  60. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  61. package/src/__tests__/session-queue.test.ts +6 -0
  62. package/src/__tests__/session-runtime-assembly.test.ts +237 -13
  63. package/src/__tests__/session-slash-known.test.ts +6 -0
  64. package/src/__tests__/session-slash-queue.test.ts +6 -0
  65. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  66. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  67. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  68. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  69. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  70. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  71. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  72. package/src/__tests__/skills.test.ts +2 -0
  73. package/src/__tests__/sms-messaging-provider.test.ts +2 -1
  74. package/src/__tests__/starter-task-flow.test.ts +2 -0
  75. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  76. package/src/__tests__/system-prompt.test.ts +2 -0
  77. package/src/__tests__/task-management-tools.test.ts +2 -2
  78. package/src/__tests__/task-runner.test.ts +14 -4
  79. package/src/__tests__/terminal-tools.test.ts +25 -19
  80. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  81. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  82. package/src/__tests__/tool-executor.test.ts +23 -24
  83. package/src/__tests__/trust-store.test.ts +3 -3
  84. package/src/__tests__/twilio-rest.test.ts +29 -0
  85. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  86. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  87. package/src/__tests__/twilio-routes.test.ts +141 -21
  88. package/src/__tests__/user-reference.test.ts +2 -0
  89. package/src/__tests__/voice-quality.test.ts +222 -0
  90. package/src/__tests__/web-search.test.ts +45 -29
  91. package/src/agent/loop.ts +1 -1
  92. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  93. package/src/amazon/client.ts +1418 -0
  94. package/src/amazon/request-extractor.ts +135 -0
  95. package/src/amazon/session.ts +109 -0
  96. package/src/autonomy/autonomy-store.ts +5 -5
  97. package/src/browser-extension-relay/client.ts +124 -0
  98. package/src/browser-extension-relay/protocol.ts +63 -0
  99. package/src/browser-extension-relay/server.ts +177 -0
  100. package/src/bundler/app-bundler.ts +3 -3
  101. package/src/bundler/bundle-signer.ts +1 -1
  102. package/src/bundler/signature-verifier.ts +1 -1
  103. package/src/calls/call-conversation-messages.ts +33 -0
  104. package/src/calls/call-domain.ts +106 -5
  105. package/src/calls/call-orchestrator.ts +252 -54
  106. package/src/calls/call-pointer-messages.ts +53 -0
  107. package/src/calls/call-recovery.ts +3 -8
  108. package/src/calls/call-store.ts +69 -87
  109. package/src/calls/elevenlabs-config.ts +3 -2
  110. package/src/calls/guardian-action-sweep.ts +105 -0
  111. package/src/calls/guardian-dispatch.ts +203 -0
  112. package/src/calls/guardian-question-copy.ts +133 -0
  113. package/src/calls/relay-server.ts +466 -8
  114. package/src/calls/speaker-identification.ts +1 -1
  115. package/src/calls/twilio-config.ts +7 -5
  116. package/src/calls/twilio-provider.ts +6 -4
  117. package/src/calls/twilio-rest.ts +40 -15
  118. package/src/calls/twilio-routes.ts +60 -45
  119. package/src/calls/types.ts +3 -1
  120. package/src/channels/types.ts +25 -0
  121. package/src/cli/amazon.ts +815 -0
  122. package/src/cli/config-commands.ts +2 -2
  123. package/src/cli/core-commands.ts +4 -3
  124. package/src/cli/influencer.ts +244 -0
  125. package/src/cli/map.ts +89 -6
  126. package/src/cli.ts +1 -1
  127. package/src/config/agent-schema.ts +171 -0
  128. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  129. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  130. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  131. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  132. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  133. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  134. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  135. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  136. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  137. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  138. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  139. package/src/config/bundled-skills/media-processing/SKILL.md +72 -95
  140. package/src/config/bundled-skills/media-processing/TOOLS.json +57 -147
  141. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  142. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  143. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  144. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  145. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  146. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  147. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  148. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +7 -9
  149. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  150. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +88 -253
  151. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +22 -153
  152. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  153. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +28 -51
  154. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +35 -270
  155. package/src/config/bundled-skills/messaging/SKILL.md +12 -2
  156. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  157. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  158. package/src/config/bundled-skills/phone-calls/SKILL.md +86 -21
  159. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  160. package/src/config/bundled-tool-registry.ts +310 -0
  161. package/src/config/calls-schema.ts +181 -0
  162. package/src/config/core-schema.ts +309 -0
  163. package/src/config/defaults.ts +27 -3
  164. package/src/config/env-registry.ts +169 -0
  165. package/src/config/env.ts +175 -0
  166. package/src/config/loader.ts +6 -6
  167. package/src/config/memory-schema.ts +528 -0
  168. package/src/config/sandbox-schema.ts +55 -0
  169. package/src/config/schema.ts +157 -1138
  170. package/src/config/skill-state.ts +1 -1
  171. package/src/config/skills-schema.ts +32 -0
  172. package/src/config/skills.ts +35 -24
  173. package/src/config/system-prompt.ts +107 -56
  174. package/src/config/templates/SOUL.md +1 -1
  175. package/src/config/types.ts +1 -0
  176. package/src/config/user-reference.ts +4 -9
  177. package/src/config/vellum-skills/catalog.json +0 -7
  178. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  179. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +1 -0
  180. package/src/config/vellum-skills/sms-setup/SKILL.md +112 -14
  181. package/src/context/window-manager.ts +27 -7
  182. package/src/daemon/approval-generators.ts +186 -0
  183. package/src/daemon/approved-devices-store.ts +140 -0
  184. package/src/daemon/assistant-attachments.ts +1 -1
  185. package/src/daemon/classifier.ts +35 -32
  186. package/src/daemon/config-watcher.ts +1 -1
  187. package/src/daemon/daemon-control.ts +254 -0
  188. package/src/daemon/handlers/apps.ts +2 -3
  189. package/src/daemon/handlers/config-channels.ts +158 -0
  190. package/src/daemon/handlers/config-inbox.ts +540 -0
  191. package/src/daemon/handlers/config-ingress.ts +231 -0
  192. package/src/daemon/handlers/config-integrations.ts +258 -0
  193. package/src/daemon/handlers/config-model.ts +143 -0
  194. package/src/daemon/handlers/config-parental.ts +163 -0
  195. package/src/daemon/handlers/config-scheduling.ts +172 -0
  196. package/src/daemon/handlers/config-slack.ts +92 -0
  197. package/src/daemon/handlers/config-telegram.ts +301 -0
  198. package/src/daemon/handlers/config-tools.ts +177 -0
  199. package/src/daemon/handlers/config-trust.ts +104 -0
  200. package/src/daemon/handlers/config-twilio.ts +1080 -0
  201. package/src/daemon/handlers/config.ts +53 -2463
  202. package/src/daemon/handlers/diagnostics.ts +1 -1
  203. package/src/daemon/handlers/dictation.ts +4 -6
  204. package/src/daemon/handlers/documents.ts +18 -32
  205. package/src/daemon/handlers/index.ts +9 -0
  206. package/src/daemon/handlers/misc.ts +3 -5
  207. package/src/daemon/handlers/pairing.ts +98 -0
  208. package/src/daemon/handlers/sessions.ts +74 -5
  209. package/src/daemon/handlers/shared.ts +3 -1
  210. package/src/daemon/handlers/skills.ts +1 -1
  211. package/src/daemon/handlers/twitter-auth.ts +2 -0
  212. package/src/daemon/handlers/work-items.ts +2 -2
  213. package/src/daemon/handlers/workspace-files.ts +4 -3
  214. package/src/daemon/install-cli-launchers.ts +113 -0
  215. package/src/daemon/ipc-contract/apps.ts +356 -0
  216. package/src/daemon/ipc-contract/browser.ts +74 -0
  217. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  218. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  219. package/src/daemon/ipc-contract/documents.ts +74 -0
  220. package/src/daemon/ipc-contract/inbox.ts +209 -0
  221. package/src/daemon/ipc-contract/integrations.ts +284 -0
  222. package/src/daemon/ipc-contract/memory.ts +48 -0
  223. package/src/daemon/ipc-contract/messages.ts +211 -0
  224. package/src/daemon/ipc-contract/pairing.ts +45 -0
  225. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  226. package/src/daemon/ipc-contract/schedules.ts +97 -0
  227. package/src/daemon/ipc-contract/sessions.ts +321 -0
  228. package/src/daemon/ipc-contract/shared.ts +42 -0
  229. package/src/daemon/ipc-contract/skills.ts +120 -0
  230. package/src/daemon/ipc-contract/subagents.ts +58 -0
  231. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  232. package/src/daemon/ipc-contract/trust.ts +60 -0
  233. package/src/daemon/ipc-contract/work-items.ts +225 -0
  234. package/src/daemon/ipc-contract/workspace.ts +113 -0
  235. package/src/daemon/ipc-contract-inventory.json +62 -0
  236. package/src/daemon/ipc-contract-inventory.ts +55 -29
  237. package/src/daemon/ipc-contract.ts +227 -2527
  238. package/src/daemon/ipc-protocol.ts +1 -1
  239. package/src/daemon/ipc-validate.ts +7 -0
  240. package/src/daemon/lifecycle.ts +97 -379
  241. package/src/daemon/pairing-store.ts +177 -0
  242. package/src/daemon/providers-setup.ts +43 -0
  243. package/src/daemon/ride-shotgun-handler.ts +67 -2
  244. package/src/daemon/server.ts +60 -44
  245. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  246. package/src/daemon/session-agent-loop.ts +113 -275
  247. package/src/daemon/session-dynamic-profile.ts +1 -1
  248. package/src/daemon/session-history.ts +1 -1
  249. package/src/daemon/session-media-retry.ts +1 -1
  250. package/src/daemon/session-messaging.ts +37 -2
  251. package/src/daemon/session-notifiers.ts +5 -25
  252. package/src/daemon/session-process.ts +99 -59
  253. package/src/daemon/session-queue-manager.ts +98 -4
  254. package/src/daemon/session-runtime-assembly.ts +149 -15
  255. package/src/daemon/session-surfaces.ts +26 -4
  256. package/src/daemon/session-tool-setup.ts +28 -30
  257. package/src/daemon/session-workspace.ts +1 -1
  258. package/src/daemon/session.ts +24 -1
  259. package/src/daemon/shutdown-handlers.ts +122 -0
  260. package/src/daemon/trace-emitter.ts +1 -1
  261. package/src/daemon/watch-handler.ts +36 -33
  262. package/src/doordash/cart-queries.ts +787 -0
  263. package/src/doordash/client.ts +144 -127
  264. package/src/doordash/order-queries.ts +85 -0
  265. package/src/doordash/queries.ts +10 -1308
  266. package/src/doordash/search-queries.ts +203 -0
  267. package/src/doordash/session.ts +3 -2
  268. package/src/doordash/store-queries.ts +246 -0
  269. package/src/doordash/types.ts +367 -0
  270. package/src/email/providers/agentmail.ts +2 -1
  271. package/src/email/providers/index.ts +3 -2
  272. package/src/email/service.ts +3 -2
  273. package/src/errors.ts +43 -0
  274. package/src/home-base/prebuilt/seed.ts +1 -1
  275. package/src/hooks/cli.ts +6 -5
  276. package/src/hooks/config.ts +6 -8
  277. package/src/hooks/discovery.ts +6 -5
  278. package/src/hooks/manager.ts +4 -3
  279. package/src/hooks/runner.ts +2 -2
  280. package/src/hooks/templates.ts +5 -5
  281. package/src/inbound/public-ingress-urls.ts +3 -1
  282. package/src/index.ts +4 -2
  283. package/src/influencer/client.ts +1104 -0
  284. package/src/instrument.ts +4 -3
  285. package/src/logfire.ts +4 -3
  286. package/src/memory/admin.ts +25 -35
  287. package/src/memory/attachments-store.ts +4 -7
  288. package/src/memory/channel-delivery-store.ts +30 -1
  289. package/src/memory/channel-guardian-store.ts +200 -1
  290. package/src/memory/clarification-resolver.ts +37 -33
  291. package/src/memory/conflict-store.ts +67 -61
  292. package/src/memory/contradiction-checker.ts +141 -117
  293. package/src/memory/conversation-store.ts +335 -51
  294. package/src/memory/db-connection.ts +27 -4
  295. package/src/memory/db-init.ts +121 -4
  296. package/src/memory/db.ts +14 -1
  297. package/src/memory/embedding-backend.ts +27 -5
  298. package/src/memory/embedding-ollama.ts +2 -1
  299. package/src/memory/entity-extractor.ts +38 -35
  300. package/src/memory/guardian-action-store.ts +430 -0
  301. package/src/memory/inbox-escalation-projection.ts +59 -0
  302. package/src/memory/inbox-thread-store.ts +218 -0
  303. package/src/memory/ingress-invite-store.ts +338 -0
  304. package/src/memory/ingress-member-store.ts +350 -0
  305. package/src/memory/items-extractor.ts +91 -97
  306. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  307. package/src/memory/job-handlers/media-processing.ts +11 -42
  308. package/src/memory/job-handlers/summarization.ts +32 -26
  309. package/src/memory/job-utils.ts +3 -10
  310. package/src/memory/jobs-store.ts +6 -9
  311. package/src/memory/jobs-worker.ts +51 -36
  312. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  313. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  314. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  315. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  316. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  317. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  318. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  319. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  320. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  321. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  322. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  323. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  324. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  325. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  326. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  327. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  328. package/src/memory/migrations/017-memory-items-indexes.ts +12 -0
  329. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  330. package/src/memory/migrations/index.ts +24 -0
  331. package/src/memory/migrations/registry.ts +79 -0
  332. package/src/memory/migrations/validate-migration-state.ts +69 -0
  333. package/src/memory/qdrant-manager.ts +49 -8
  334. package/src/memory/query-builder.ts +1 -1
  335. package/src/memory/raw-query.ts +119 -0
  336. package/src/memory/recall-cache.ts +4 -1
  337. package/src/memory/retriever.ts +163 -47
  338. package/src/memory/schema-migration.ts +25 -984
  339. package/src/memory/schema.ts +130 -7
  340. package/src/memory/search/entity.ts +10 -19
  341. package/src/memory/search/lexical.ts +81 -52
  342. package/src/memory/search/ranking.ts +21 -22
  343. package/src/memory/search/semantic.ts +157 -19
  344. package/src/memory/shared-app-links-store.ts +4 -5
  345. package/src/memory/validation.ts +19 -0
  346. package/src/messaging/draft-store.ts +5 -6
  347. package/src/messaging/providers/sms/adapter.ts +3 -6
  348. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  349. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  350. package/src/messaging/providers/whatsapp/client.ts +67 -0
  351. package/src/messaging/style-analyzer.ts +5 -4
  352. package/src/messaging/thread-summarizer.ts +61 -69
  353. package/src/messaging/triage-engine.ts +62 -71
  354. package/src/migrations/config-merge.ts +53 -0
  355. package/src/migrations/data-layout.ts +68 -0
  356. package/src/migrations/data-merge.ts +33 -0
  357. package/src/migrations/hooks-merge.ts +90 -0
  358. package/src/migrations/index.ts +6 -0
  359. package/src/migrations/log.ts +23 -0
  360. package/src/migrations/skills-merge.ts +33 -0
  361. package/src/migrations/workspace-layout.ts +79 -0
  362. package/src/permissions/checker.ts +126 -11
  363. package/src/permissions/prompter.ts +14 -0
  364. package/src/permissions/shell-identity.ts +31 -1
  365. package/src/permissions/trust-store.ts +21 -1
  366. package/src/providers/anthropic/client.ts +4 -4
  367. package/src/providers/failover.ts +2 -2
  368. package/src/providers/model-intents.ts +70 -0
  369. package/src/providers/ollama/client.ts +2 -1
  370. package/src/providers/provider-send-message.ts +176 -0
  371. package/src/providers/registry.ts +71 -30
  372. package/src/providers/retry.ts +35 -1
  373. package/src/providers/types.ts +12 -1
  374. package/src/runtime/approval-conversation-turn.ts +97 -0
  375. package/src/runtime/approval-message-composer.ts +115 -5
  376. package/src/runtime/assistant-event-hub.ts +3 -1
  377. package/src/runtime/channel-approval-parser.ts +36 -2
  378. package/src/runtime/channel-approvals.ts +0 -21
  379. package/src/runtime/channel-guardian-service.ts +48 -7
  380. package/src/runtime/channel-readiness-service.ts +160 -34
  381. package/src/runtime/channel-readiness-types.ts +10 -4
  382. package/src/runtime/channel-retry-sweep.ts +184 -0
  383. package/src/runtime/guardian-context-resolver.ts +108 -0
  384. package/src/runtime/http-server.ts +289 -745
  385. package/src/runtime/http-types.ts +56 -3
  386. package/src/runtime/middleware/auth.ts +116 -0
  387. package/src/runtime/middleware/error-handler.ts +33 -0
  388. package/src/runtime/middleware/twilio-validation.ts +127 -0
  389. package/src/runtime/routes/app-routes.ts +1 -1
  390. package/src/runtime/routes/call-routes.ts +49 -6
  391. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  392. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  393. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  394. package/src/runtime/routes/channel-route-shared.ts +144 -0
  395. package/src/runtime/routes/channel-routes.ts +32 -1634
  396. package/src/runtime/routes/conversation-routes.ts +50 -7
  397. package/src/runtime/routes/events-routes.ts +2 -2
  398. package/src/runtime/routes/identity-routes.ts +126 -0
  399. package/src/runtime/routes/pairing-routes.ts +144 -0
  400. package/src/runtime/routes/run-routes.ts +15 -1
  401. package/src/runtime/run-orchestrator.ts +52 -34
  402. package/src/schedule/schedule-store.ts +36 -32
  403. package/src/schedule/scheduler.ts +3 -3
  404. package/src/security/encrypted-store.ts +5 -7
  405. package/src/security/oauth2.ts +45 -15
  406. package/src/security/parental-control-store.ts +183 -0
  407. package/src/security/secret-allowlist.ts +4 -3
  408. package/src/security/secret-scanner.ts +5 -5
  409. package/src/security/secure-keys.ts +1 -1
  410. package/src/security/token-manager.ts +3 -2
  411. package/src/services/vercel-deploy.ts +6 -2
  412. package/src/skills/tool-manifest.ts +3 -3
  413. package/src/skills/vellum-catalog-remote.ts +75 -16
  414. package/src/slack/slack-webhook.ts +2 -1
  415. package/src/swarm/orchestrator.ts +92 -1
  416. package/src/swarm/router-planner.ts +6 -9
  417. package/src/swarm/worker-prompts.ts +9 -12
  418. package/src/tasks/task-compiler.ts +19 -28
  419. package/src/tasks/task-runner.ts +1 -1
  420. package/src/tools/assets/search.ts +15 -14
  421. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  422. package/src/tools/browser/auto-navigate.ts +1 -0
  423. package/src/tools/browser/browser-execution.ts +13 -1
  424. package/src/tools/browser/browser-manager.ts +119 -4
  425. package/src/tools/browser/network-recorder.ts +5 -0
  426. package/src/tools/credentials/broker.ts +11 -2
  427. package/src/tools/credentials/metadata-store.ts +18 -14
  428. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  429. package/src/tools/credentials/vault.ts +49 -23
  430. package/src/tools/executor.ts +80 -18
  431. package/src/tools/host-terminal/cli-discover.ts +1 -1
  432. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  433. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  434. package/src/tools/network/script-proxy/server.ts +1 -1
  435. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  436. package/src/tools/network/web-fetch.ts +18 -2
  437. package/src/tools/network/web-search.ts +7 -3
  438. package/src/tools/reminder/reminder-store.ts +14 -15
  439. package/src/tools/schedule/create.ts +1 -0
  440. package/src/tools/schedule/list.ts +2 -1
  441. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  442. package/src/tools/skills/skill-script-runner.ts +24 -9
  443. package/src/tools/skills/skill-tool-factory.ts +1 -0
  444. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  445. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  446. package/src/tools/terminal/parser.ts +50 -0
  447. package/src/tools/watcher/delete.ts +6 -0
  448. package/src/tools/weather/service.ts +1 -1
  449. package/src/twitter/client.ts +190 -24
  450. package/src/twitter/session.ts +4 -3
  451. package/src/util/clipboard.ts +1 -1
  452. package/src/util/errors.ts +65 -8
  453. package/src/util/fs.ts +40 -0
  454. package/src/util/json.ts +10 -0
  455. package/src/util/log-redact.ts +189 -0
  456. package/src/util/logger.ts +25 -18
  457. package/src/util/object.ts +3 -0
  458. package/src/util/platform.ts +72 -365
  459. package/src/util/pricing.ts +1 -1
  460. package/src/util/promise-guard.ts +1 -1
  461. package/src/util/retry.ts +19 -0
  462. package/src/util/row-mapper.ts +79 -0
  463. package/src/util/silently.ts +21 -0
  464. package/src/watcher/engine.ts +5 -1
  465. package/src/watcher/provider-types.ts +20 -0
  466. package/src/watcher/providers/github.ts +156 -0
  467. package/src/watcher/providers/gmail.ts +1 -0
  468. package/src/watcher/providers/google-calendar.ts +1 -0
  469. package/src/watcher/providers/linear.ts +460 -0
  470. package/src/watcher/providers/slack.ts +1 -0
  471. package/src/work-items/work-item-runner.ts +1 -1
  472. package/src/workspace/git-service.ts +1 -1
  473. package/src/workspace/provider-commit-message-generator.ts +51 -22
  474. package/src/__tests__/call-bridge.test.ts +0 -517
  475. package/src/__tests__/session-process-bridge.test.ts +0 -244
  476. package/src/calls/call-bridge.ts +0 -168
  477. package/src/config/bundled-skills/media-processing/services/capability-registry.ts +0 -137
  478. package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +0 -280
  479. package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +0 -144
  480. package/src/config/bundled-skills/media-processing/services/feedback-store.ts +0 -136
  481. package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +0 -95
  482. package/src/config/bundled-skills/media-processing/services/timeline-service.ts +0 -267
  483. package/src/config/bundled-skills/media-processing/tools/detect-events.ts +0 -110
  484. package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +0 -235
  485. package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +0 -142
  486. package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +0 -150
  487. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -1,15 +1,16 @@
1
- import Anthropic from '@anthropic-ai/sdk';
2
1
  import { and, eq, sql } from 'drizzle-orm';
3
2
  import { v4 as uuid } from 'uuid';
4
3
  import { getConfig } from '../config/loader.js';
5
4
  import type { MemoryExtractionConfig } from '../config/types.js';
6
5
  import { getLogger } from '../util/logger.js';
7
6
  import { truncate } from '../util/truncate.js';
7
+ import { getConfiguredProvider, createTimeout, extractToolUse, userMessage } from '../providers/provider-send-message.js';
8
8
  import { computeMemoryFingerprint } from './fingerprint.js';
9
9
  import { enqueueMemoryJob } from './jobs-store.js';
10
10
  import { extractTextFromStoredMessageContent } from './message-content.js';
11
11
  import { getDb } from './db.js';
12
12
  import { memoryItemConflicts, memoryItems, memoryItemSources, messages } from './schema.js';
13
+ import { clampUnitInterval } from './validation.js';
13
14
 
14
15
  const log = getLogger('memory-items-extractor');
15
16
 
@@ -114,108 +115,105 @@ async function extractItemsWithLLM(
114
115
  extractionConfig: MemoryExtractionConfig,
115
116
  scopeId: string,
116
117
  ): Promise<ExtractedItem[]> {
117
- const config = getConfig();
118
- const apiKey = config.apiKeys.anthropic ?? process.env.ANTHROPIC_API_KEY;
119
- if (!apiKey) {
120
- log.debug('No Anthropic API key available for LLM extraction, falling back to pattern-based');
118
+ const provider = getConfiguredProvider();
119
+ if (!provider) {
120
+ log.debug('Configured provider unavailable for LLM extraction, falling back to pattern-based');
121
121
  return extractItemsPatternBased(text, scopeId);
122
122
  }
123
123
 
124
124
  try {
125
- const client = new Anthropic({ apiKey });
126
- const abortController = new AbortController();
127
- let timer: ReturnType<typeof setTimeout>;
128
- const apiCall = client.messages.create({
129
- model: extractionConfig.model,
130
- max_tokens: 1024,
131
- system: EXTRACTION_SYSTEM_PROMPT,
132
- tools: [{
133
- name: 'store_memory_items',
134
- description: 'Store extracted memory items from the message',
135
- input_schema: {
136
- type: 'object' as const,
137
- properties: {
138
- items: {
139
- type: 'array',
125
+ const { signal, cleanup } = createTimeout(15000);
126
+
127
+ try {
128
+ const response = await provider.sendMessage(
129
+ [userMessage(text)],
130
+ [{
131
+ name: 'store_memory_items',
132
+ description: 'Store extracted memory items from the message',
133
+ input_schema: {
134
+ type: 'object' as const,
135
+ properties: {
140
136
  items: {
141
- type: 'object',
142
- properties: {
143
- kind: {
144
- type: 'string',
145
- enum: [...VALID_KINDS],
146
- description: 'Category of memory item',
147
- },
148
- subject: {
149
- type: 'string',
150
- description: 'Short label (2-8 words) for what this is about',
151
- },
152
- statement: {
153
- type: 'string',
154
- description: 'Full factual statement to remember (1-2 sentences)',
155
- },
156
- confidence: {
157
- type: 'number',
158
- description: 'Confidence that this is accurate (0.0-1.0)',
159
- },
160
- importance: {
161
- type: 'number',
162
- description: 'How valuable this is to remember (0.0-1.0)',
137
+ type: 'array',
138
+ items: {
139
+ type: 'object',
140
+ properties: {
141
+ kind: {
142
+ type: 'string',
143
+ enum: [...VALID_KINDS],
144
+ description: 'Category of memory item',
145
+ },
146
+ subject: {
147
+ type: 'string',
148
+ description: 'Short label (2-8 words) for what this is about',
149
+ },
150
+ statement: {
151
+ type: 'string',
152
+ description: 'Full factual statement to remember (1-2 sentences)',
153
+ },
154
+ confidence: {
155
+ type: 'number',
156
+ description: 'Confidence that this is accurate (0.0-1.0)',
157
+ },
158
+ importance: {
159
+ type: 'number',
160
+ description: 'How valuable this is to remember (0.0-1.0)',
161
+ },
163
162
  },
163
+ required: ['kind', 'subject', 'statement', 'confidence', 'importance'],
164
164
  },
165
- required: ['kind', 'subject', 'statement', 'confidence', 'importance'],
166
165
  },
167
166
  },
167
+ required: ['items'],
168
+ },
169
+ }],
170
+ EXTRACTION_SYSTEM_PROMPT,
171
+ {
172
+ config: {
173
+ model: extractionConfig.model,
174
+ max_tokens: 1024,
175
+ tool_choice: { type: 'tool' as const, name: 'store_memory_items' },
168
176
  },
169
- required: ['items'],
177
+ signal,
170
178
  },
171
- }],
172
- tool_choice: { type: 'tool' as const, name: 'store_memory_items' },
173
- messages: [{ role: 'user' as const, content: text }],
174
- }, { signal: abortController.signal });
175
- // Swallow the abort rejection that fires when the timeout wins the race
176
- apiCall.catch(() => {});
177
- const response = await Promise.race([
178
- apiCall.finally(() => clearTimeout(timer)),
179
- new Promise<never>((_, reject) => {
180
- timer = setTimeout(() => {
181
- abortController.abort();
182
- reject(new Error('LLM extraction timeout'));
183
- }, 15000);
184
- }),
185
- ]);
186
-
187
- const toolBlock = response.content.find((b) => b.type === 'tool_use');
188
- if (!toolBlock || toolBlock.type !== 'tool_use') {
189
- log.warn('No tool_use block in LLM extraction response, falling back to pattern-based');
190
- return extractItemsPatternBased(text, scopeId);
179
+ );
180
+ cleanup();
181
+
182
+ const toolBlock = extractToolUse(response);
183
+ if (!toolBlock) {
184
+ log.warn('No tool_use block in LLM extraction response, falling back to pattern-based');
185
+ return extractItemsPatternBased(text, scopeId);
186
+ }
187
+
188
+ const input = toolBlock.input as { items?: LLMExtractedItem[] };
189
+ if (!Array.isArray(input.items)) {
190
+ log.warn('Invalid items in LLM extraction response, falling back to pattern-based');
191
+ return extractItemsPatternBased(text, scopeId);
192
+ }
193
+
194
+ const items: ExtractedItem[] = [];
195
+ for (const raw of input.items) {
196
+ if (!VALID_KINDS.has(raw.kind)) continue;
197
+ if (!raw.subject || !raw.statement) continue;
198
+ const subject = truncate(String(raw.subject), 80, '');
199
+ const statement = truncate(String(raw.statement), 500, '');
200
+ const confidence = clampUnitInterval(parseScore(raw.confidence, 0.5));
201
+ const importance = clampUnitInterval(parseScore(raw.importance, 0.5));
202
+ const fingerprint = computeMemoryFingerprint(scopeId, raw.kind, subject, statement);
203
+ items.push({
204
+ kind: raw.kind as MemoryItemKind,
205
+ subject,
206
+ statement,
207
+ confidence,
208
+ importance,
209
+ fingerprint,
210
+ });
211
+ }
212
+
213
+ return deduplicateItems(items);
214
+ } finally {
215
+ cleanup();
191
216
  }
192
-
193
- const input = toolBlock.input as { items?: LLMExtractedItem[] };
194
- if (!Array.isArray(input.items)) {
195
- log.warn('Invalid items in LLM extraction response, falling back to pattern-based');
196
- return extractItemsPatternBased(text, scopeId);
197
- }
198
-
199
- const items: ExtractedItem[] = [];
200
- for (const raw of input.items) {
201
- if (!VALID_KINDS.has(raw.kind)) continue;
202
- if (!raw.subject || !raw.statement) continue;
203
- const subject = truncate(String(raw.subject), 80, '');
204
- const statement = truncate(String(raw.statement), 500, '');
205
- const confidence = clamp(parseScore(raw.confidence, 0.5), 0, 1);
206
- const importance = clamp(parseScore(raw.importance, 0.5), 0, 1);
207
- const fingerprint = computeMemoryFingerprint(scopeId, raw.kind, subject, statement);
208
- items.push({
209
- kind: raw.kind as MemoryItemKind,
210
- subject,
211
- statement,
212
- confidence,
213
- importance,
214
- fingerprint,
215
- });
216
- }
217
-
218
- return deduplicateItems(items);
219
217
  } catch (err) {
220
218
  const message = err instanceof Error ? err.message : String(err);
221
219
  log.warn({ err: message }, 'LLM extraction failed, falling back to pattern-based');
@@ -287,8 +285,8 @@ export async function extractAndUpsertMemoryItemsForMessage(messageId: string, s
287
285
  db.update(memoryItems)
288
286
  .set({
289
287
  status: effectiveStatus,
290
- confidence: Math.max(existing.confidence, item.confidence),
291
- importance: Math.max(existing.importance ?? 0, item.importance),
288
+ confidence: clampUnitInterval(Math.max(existing.confidence, item.confidence)),
289
+ importance: clampUnitInterval(Math.max(existing.importance ?? 0, item.importance)),
292
290
  lastSeenAt: Math.max(existing.lastSeenAt, seenAt),
293
291
  verificationState: promotedState,
294
292
  })
@@ -441,10 +439,6 @@ function parseScore(value: unknown, fallback: number): number {
441
439
  return Number.isFinite(n) ? n : fallback;
442
440
  }
443
441
 
444
- function clamp(value: number, min: number, max: number): number {
445
- return Math.min(max, Math.max(min, value));
446
- }
447
-
448
442
  /** Returns true if the given memory item is the candidate in an unresolved conflict. */
449
443
  function hasPendingConflict(itemId: string): boolean {
450
444
  const db = getDb();
@@ -1,6 +1,6 @@
1
1
  import { eq } from 'drizzle-orm';
2
2
  import { getLogger } from '../../util/logger.js';
3
- import { getDb } from '../db.js';
3
+ import { getDb, rawExec } from '../db.js';
4
4
  import { enqueueMemoryJob, type MemoryJob } from '../jobs-store.js';
5
5
  import { asString, BackendUnavailableError } from '../job-utils.js';
6
6
  import { getQdrantClient } from '../qdrant-client.js';
@@ -10,8 +10,8 @@ const log = getLogger('memory-jobs-worker');
10
10
 
11
11
  export function rebuildIndexJob(): void {
12
12
  const db = getDb();
13
- db.run(/*sql*/ `DELETE FROM memory_segment_fts`);
14
- db.run(/*sql*/ `
13
+ rawExec(/*sql*/ `DELETE FROM memory_segment_fts`);
14
+ rawExec(/*sql*/ `
15
15
  INSERT INTO memory_segment_fts(segment_id, text)
16
16
  SELECT id, text FROM memory_segments
17
17
  `);
@@ -7,27 +7,12 @@ import {
7
7
  type PipelineStageName,
8
8
  type StageHandler,
9
9
  } from '../../config/bundled-skills/media-processing/services/processing-pipeline.js';
10
- import { extractKeyframesForAsset } from '../../config/bundled-skills/media-processing/tools/extract-keyframes.js';
11
- import { analyzeKeyframesForAsset } from '../../config/bundled-skills/media-processing/tools/analyze-keyframes.js';
12
- import { generateTimeline } from '../../config/bundled-skills/media-processing/services/timeline-service.js';
13
- import {
14
- detectEvents,
15
- type DetectionConfig,
16
- } from '../../config/bundled-skills/media-processing/services/event-detection-service.js';
10
+ import { preprocessForAsset } from '../../config/bundled-skills/media-processing/tools/extract-keyframes.js';
11
+ import { mapSegmentsForAsset } from '../../config/bundled-skills/media-processing/tools/analyze-keyframes.js';
12
+ import { reduceForAsset } from '../../config/bundled-skills/media-processing/tools/query-media-events.js';
17
13
 
18
14
  const log = getLogger('media-processing-job');
19
15
 
20
- const defaultDetectionConfig: DetectionConfig = {
21
- eventType: 'scene_change',
22
- rules: [
23
- {
24
- ruleType: 'segment_transition',
25
- params: { field: 'segmentType' },
26
- weight: 1.0,
27
- },
28
- ],
29
- };
30
-
31
16
  export async function mediaProcessingJob(job: MemoryJob): Promise<void> {
32
17
  const mediaAssetId = asString(job.payload.mediaAssetId);
33
18
  if (!mediaAssetId) {
@@ -50,31 +35,15 @@ export async function mediaProcessingJob(job: MemoryJob): Promise<void> {
50
35
  return;
51
36
  }
52
37
 
53
- // Build detection config, allowing optional eventType override from payload
54
- const eventType = asString(job.payload.eventType);
55
- const detectionConfig: DetectionConfig = eventType
56
- ? { ...defaultDetectionConfig, eventType }
57
- : defaultDetectionConfig;
58
-
59
38
  const handlers: Record<PipelineStageName, StageHandler> = {
60
- keyframe_extraction: {
61
- execute: (assetId, onProgress) =>
62
- extractKeyframesForAsset(assetId, 1, onProgress),
63
- },
64
- vision_analysis: {
65
- execute: (assetId, onProgress) =>
66
- analyzeKeyframesForAsset(assetId, undefined, undefined, onProgress),
67
- },
68
- timeline_generation: {
69
- execute: async (assetId, onProgress) => {
70
- generateTimeline(assetId, { onProgress });
71
- },
72
- },
73
- event_detection: {
74
- execute: async (assetId, onProgress) => {
75
- detectEvents(assetId, detectionConfig, { onProgress });
76
- },
77
- },
39
+ preprocess: { execute: async (assetId, onProgress) => { await preprocessForAsset(assetId, {}, onProgress); } },
40
+ map: { execute: async (assetId, onProgress) => { await mapSegmentsForAsset(assetId, {
41
+ systemPrompt: 'Describe what you see in these video frames. For each frame, note: subjects present, actions occurring, scene context, and any text visible.',
42
+ outputSchema: { type: 'object', properties: { frames: { type: 'array', items: { type: 'object', properties: { timestamp: { type: 'number' }, subjects: { type: 'array', items: { type: 'string' } }, actions: { type: 'array', items: { type: 'string' } }, scene: { type: 'string' }, text: { type: 'string' } } } } } }
43
+ }, onProgress); } },
44
+ reduce: { execute: async (assetId, onProgress) => { await reduceForAsset(assetId, {
45
+ systemPrompt: 'Summarize the video content based on the structured observations.',
46
+ }, onProgress); } },
78
47
  };
79
48
 
80
49
  const result = await runPipeline(mediaAssetId, handlers, {
@@ -1,9 +1,9 @@
1
- import Anthropic from '@anthropic-ai/sdk';
2
1
  import { and, desc, eq, gte, isNull, lt } from 'drizzle-orm';
3
2
  import { v4 as uuid } from 'uuid';
4
3
  import type { AssistantConfig } from '../../config/types.js';
5
4
  import { estimateTextTokens } from '../../context/token-estimator.js';
6
5
  import { getLogger } from '../../util/logger.js';
6
+ import { getConfiguredProvider, createTimeout, extractText, userMessage } from '../../providers/provider-send-message.js';
7
7
  import { getConversationMemoryScopeId } from '../conversation-store.js';
8
8
  import { getDb } from '../db.js';
9
9
  import { enqueueMemoryJob, type MemoryJob } from '../jobs-store.js';
@@ -230,9 +230,9 @@ async function summarizeWithLLM(
230
230
  return buildFallbackSummary(existingSummary, newContent, label);
231
231
  }
232
232
 
233
- const apiKey = config.apiKeys.anthropic ?? process.env.ANTHROPIC_API_KEY;
234
- if (!apiKey) {
235
- log.debug({ label }, 'No Anthropic API key available for summarization, using fallback');
233
+ const provider = getConfiguredProvider();
234
+ if (!provider) {
235
+ log.debug({ label }, 'Configured provider unavailable for summarization, using fallback');
236
236
  return buildFallbackSummary(existingSummary, newContent, label);
237
237
  }
238
238
 
@@ -247,30 +247,36 @@ async function summarizeWithLLM(
247
247
  userParts.push('### New Data', newContent);
248
248
 
249
249
  try {
250
- const client = new Anthropic({ apiKey });
251
- const response = await Promise.race([
252
- client.messages.create({
253
- model: summarizationConfig.model,
254
- max_tokens: SUMMARY_MAX_TOKENS,
255
- system: systemPrompt,
256
- messages: [{ role: 'user' as const, content: userParts.join('\n') }],
257
- }),
258
- new Promise<never>((_, reject) =>
259
- setTimeout(() => reject(new Error('Summarization LLM timeout')), SUMMARY_LLM_TIMEOUT_MS),
260
- ),
261
- ]);
262
-
263
- const textBlock = response.content.find((b) => b.type === 'text');
264
- if (textBlock && textBlock.type === 'text' && textBlock.text.trim().length > 0) {
265
- log.debug(
266
- { label, inputTokens: response.usage.input_tokens, outputTokens: response.usage.output_tokens },
267
- 'LLM summarization completed',
250
+ const { signal, cleanup } = createTimeout(SUMMARY_LLM_TIMEOUT_MS);
251
+ try {
252
+ const response = await provider.sendMessage(
253
+ [userMessage(userParts.join('\n'))],
254
+ undefined,
255
+ systemPrompt,
256
+ {
257
+ config: {
258
+ model: summarizationConfig.model,
259
+ max_tokens: SUMMARY_MAX_TOKENS,
260
+ },
261
+ signal,
262
+ },
268
263
  );
269
- return textBlock.text.trim();
270
- }
264
+ cleanup();
271
265
 
272
- log.warn({ label }, 'LLM summarization returned empty text, using fallback');
273
- return buildFallbackSummary(existingSummary, newContent, label);
266
+ const text = extractText(response);
267
+ if (text.length > 0) {
268
+ log.debug(
269
+ { label, inputTokens: response.usage.inputTokens, outputTokens: response.usage.outputTokens },
270
+ 'LLM summarization completed',
271
+ );
272
+ return text;
273
+ }
274
+
275
+ log.warn({ label }, 'LLM summarization returned empty text, using fallback');
276
+ return buildFallbackSummary(existingSummary, newContent, label);
277
+ } finally {
278
+ cleanup();
279
+ }
274
280
  } catch (err) {
275
281
  const message = err instanceof Error ? err.message : String(err);
276
282
  log.warn({ err: message, label }, 'LLM summarization failed, using fallback');
@@ -6,18 +6,11 @@ import { getDb } from './db.js';
6
6
  import { getQdrantClient } from './qdrant-client.js';
7
7
  import { memoryEmbeddings } from './schema.js';
8
8
  import type { AssistantConfig } from '../config/types.js';
9
+ import { BackendUnavailableError } from '../util/errors.js';
9
10
 
10
- const log = getLogger('memory-jobs-worker');
11
-
12
- // ── Sentinel error ─────────────────────────────────────────────────
11
+ export { BackendUnavailableError };
13
12
 
14
- /** Sentinel error: the embedding backend is not configured yet. */
15
- export class BackendUnavailableError extends Error {
16
- constructor(reason: string) {
17
- super(reason);
18
- this.name = 'BackendUnavailableError';
19
- }
20
- }
13
+ const log = getLogger('memory-jobs-worker');
21
14
 
22
15
  // ── Error classification for LLM / API errors ─────────────────────
23
16
 
@@ -1,6 +1,6 @@
1
1
  import { and, asc, eq, lte, notInArray, inArray } from 'drizzle-orm';
2
2
  import { v4 as uuid } from 'uuid';
3
- import { getDb } from './db.js';
3
+ import { getDb, rawGet, rawAll } from './db.js';
4
4
  import { memoryJobs } from './schema.js';
5
5
  import { truncate } from '../util/truncate.js';
6
6
 
@@ -110,9 +110,8 @@ export function enqueueResolvePendingConflictsForMessageJob(
110
110
  throw new Error('enqueueResolvePendingConflictsForMessageJob requires a non-empty messageId');
111
111
  }
112
112
  const normalizedScopeId = scopeId.trim() || 'default';
113
- // Dedup check always uses root db since tx doesn't expose $client
114
- const raw = (getDb() as unknown as { $client: { query: (q: string) => { get: (...params: unknown[]) => unknown } } }).$client;
115
- const existing = raw.query(`
113
+ // Dedup check always uses root db since tx doesn't expose raw client
114
+ const existing = rawGet<{ id: string }>(`
116
115
  SELECT id
117
116
  FROM memory_jobs
118
117
  WHERE type = 'resolve_pending_conflicts_for_message'
@@ -121,7 +120,7 @@ export function enqueueResolvePendingConflictsForMessageJob(
121
120
  AND COALESCE(json_extract(payload, '$.scopeId'), 'default') = ?
122
121
  ORDER BY created_at ASC
123
122
  LIMIT 1
124
- `).get(normalizedMessageId, normalizedScopeId) as { id: string } | null;
123
+ `, normalizedMessageId, normalizedScopeId);
125
124
  if (existing?.id) return existing.id;
126
125
 
127
126
  return enqueueMemoryJob('resolve_pending_conflicts_for_message', {
@@ -366,13 +365,11 @@ export function resetRunningJobsToPending(): number {
366
365
  }
367
366
 
368
367
  export function getMemoryJobCounts(): Record<string, number> {
369
- const db = getDb();
370
- const raw = (db as unknown as { $client: { query: (q: string) => { all: () => unknown[] } } }).$client;
371
- const rows = raw.query(`
368
+ const rows = rawAll<{ status: string; c: number }>(`
372
369
  SELECT status, COUNT(*) AS c
373
370
  FROM memory_jobs
374
371
  GROUP BY status
375
- `).all() as Array<{ status: string; c: number }>;
372
+ `);
376
373
  const counts: Record<string, number> = { pending: 0, running: 0, completed: 0, failed: 0 };
377
374
  for (const row of rows) {
378
375
  counts[row.status] = row.c;
@@ -1,7 +1,7 @@
1
1
  import type { AssistantConfig } from '../config/types.js';
2
2
  import { getConfig } from '../config/loader.js';
3
3
  import { getLogger } from '../util/logger.js';
4
- import { getDb } from './db.js';
4
+ import { rawRun } from './db.js';
5
5
  import {
6
6
  claimMemoryJobs,
7
7
  completeMemoryJob,
@@ -112,38 +112,56 @@ export async function runMemoryJobsOnce(
112
112
  let processed = 0;
113
113
  const typeGroups = [...jobsByType.values()];
114
114
 
115
- // Run type groups concurrently (up to workerConcurrency at a time).
116
- // Within each group, jobs are processed sequentially.
117
- for (let i = 0; i < typeGroups.length; i += concurrency) {
118
- const groupChunk = typeGroups.slice(i, i + concurrency);
119
- const groupResults = await Promise.allSettled(
120
- groupChunk.map(async (group) => {
121
- let groupProcessed = 0;
122
- for (const job of group) {
123
- try {
124
- await processJob(job, config);
125
- completeMemoryJob(job.id);
126
- bumpMemoryVersion();
127
- groupProcessed += 1;
128
- } catch (err) {
129
- try {
130
- handleJobError(job, err);
131
- } catch (handlerErr) {
132
- log.error({ err: handlerErr, jobId: job.id, type: job.type }, 'handleJobError itself threw, job left in running status');
133
- }
134
- }
115
+ // Run type groups concurrently using a task pool (up to workerConcurrency
116
+ // active at a time). Unlike the old wave approach, a new group starts as
117
+ // soon as any slot frees up no waiting for an entire wave to finish.
118
+ const processGroup = async (group: MemoryJob[]): Promise<number> => {
119
+ let groupProcessed = 0;
120
+ for (const job of group) {
121
+ try {
122
+ await processJob(job, config);
123
+ completeMemoryJob(job.id);
124
+ bumpMemoryVersion();
125
+ groupProcessed += 1;
126
+ } catch (err) {
127
+ try {
128
+ handleJobError(job, err);
129
+ } catch (handlerErr) {
130
+ log.error({ err: handlerErr, jobId: job.id, type: job.type }, 'handleJobError itself threw, job left in running status');
135
131
  }
136
- return groupProcessed;
137
- }),
138
- );
139
- for (const result of groupResults) {
132
+ }
133
+ }
134
+ return groupProcessed;
135
+ };
136
+
137
+ if (typeGroups.length <= concurrency) {
138
+ // Fast path: all groups fit within the concurrency limit
139
+ const results = await Promise.allSettled(typeGroups.map(processGroup));
140
+ for (const result of results) {
140
141
  if (result.status === 'fulfilled') {
141
142
  processed += result.value;
143
+ } else {
144
+ log.error({ err: result.reason }, 'Memory job group rejected unexpectedly — jobs in this batch may have been dropped');
142
145
  }
143
- // Errors within groups are already handled per-job above;
144
- // a rejected group promise would only come from an unexpected
145
- // error in the loop itself, which is unlikely.
146
146
  }
147
+ } else {
148
+ // Task pool: maintain `concurrency` in-flight groups at all times
149
+ let nextIdx = 0;
150
+
151
+ const startNext = (): Promise<void> | undefined => {
152
+ if (nextIdx >= typeGroups.length) return undefined;
153
+ const group = typeGroups[nextIdx++]!;
154
+ return processGroup(group).then(
155
+ (count) => { processed += count; },
156
+ (err) => { log.error({ err }, 'Memory job group rejected unexpectedly — jobs in this batch may have been dropped'); },
157
+ ).then(() => startNext());
158
+ };
159
+
160
+ const workers = Array.from(
161
+ { length: Math.min(concurrency, typeGroups.length) },
162
+ () => startNext()!,
163
+ );
164
+ await Promise.all(workers);
147
165
  }
148
166
  if (enableScheduledCleanup) {
149
167
  maybeEnqueueScheduledCleanupJobs(config);
@@ -296,16 +314,13 @@ export function sweepStaleItems(config: AssistantConfig): number {
296
314
  if (now - lastStaleSweepMs < STALE_SWEEP_INTERVAL_MS) return 0;
297
315
  lastStaleSweepMs = now;
298
316
 
299
- const db = getDb();
300
- const raw = (db as unknown as { $client: { query: (q: string) => { run: (...params: unknown[]) => { changes: number } } } }).$client;
301
-
302
317
  let totalMarked = 0;
303
318
  for (const [kind, maxAgeDays] of Object.entries(freshness.maxAgeDays)) {
304
319
  if (maxAgeDays <= 0) continue;
305
320
  // Mark invalid if: past 2x window, no access in the shield period, and not already invalid
306
321
  const cutoffMs = now - maxAgeDays * 2 * 86_400_000;
307
322
  const shieldCutoffMs = now - freshness.reinforcementShieldDays * 86_400_000;
308
- const result = raw.query(`
323
+ const changes = rawRun(`
309
324
  UPDATE memory_items
310
325
  SET invalid_at = ?
311
326
  WHERE kind = ?
@@ -313,10 +328,10 @@ export function sweepStaleItems(config: AssistantConfig): number {
313
328
  AND invalid_at IS NULL
314
329
  AND last_seen_at < ?
315
330
  AND (access_count = 0 OR COALESCE(last_used_at, 0) < ?)
316
- `).run(now, kind, cutoffMs, shieldCutoffMs);
317
- if (result.changes > 0) {
318
- log.info({ kind, marked: result.changes, cutoffMs }, 'Marked stale memory items as invalid');
319
- totalMarked += result.changes;
331
+ `, now, kind, cutoffMs, shieldCutoffMs);
332
+ if (changes > 0) {
333
+ log.info({ kind, marked: changes, cutoffMs }, 'Marked stale memory items as invalid');
334
+ totalMarked += changes;
320
335
  }
321
336
  }
322
337
  return totalMarked;
@@ -0,0 +1,45 @@
1
+ import { getSqliteFrom, type DrizzleDb } from '../db-connection.js';
2
+
3
+ /**
4
+ * One-shot migration: reconcile old deferral history into the new `deferrals` column.
5
+ *
6
+ * Before the `deferrals` column was added, `deferMemoryJob` incremented `attempts`.
7
+ * After the column is added with DEFAULT 0, those legacy jobs still carry the old
8
+ * attempt count (which was really a deferral count) while `deferrals` is 0. This
9
+ * moves the attempt count into `deferrals` and resets `attempts` to 0.
10
+ *
11
+ * This migration MUST run only once. On subsequent startups, post-migration jobs
12
+ * that genuinely failed via `failMemoryJob` (attempts > 0, deferrals = 0, non-null
13
+ * last_error) must NOT be touched — resetting their attempts would let them bypass
14
+ * the configured maxAttempts budget across restarts.
15
+ *
16
+ * We use a `memory_checkpoints` row to ensure the migration runs exactly once.
17
+ */
18
+ export function migrateJobDeferrals(database: DrizzleDb): void {
19
+ const raw = getSqliteFrom(database);
20
+ const checkpoint = raw.query(
21
+ `SELECT 1 FROM memory_checkpoints WHERE key = 'migration_job_deferrals'`
22
+ ).get();
23
+ if (checkpoint) return;
24
+
25
+ try {
26
+ raw.exec(/*sql*/ `
27
+ BEGIN;
28
+ UPDATE memory_jobs
29
+ SET deferrals = attempts,
30
+ attempts = 0,
31
+ last_error = NULL,
32
+ updated_at = ${Date.now()}
33
+ WHERE status = 'pending'
34
+ AND attempts > 0
35
+ AND deferrals = 0
36
+ AND type IN ('embed_segment', 'embed_item', 'embed_summary');
37
+ INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at)
38
+ VALUES ('migration_job_deferrals', '1', ${Date.now()});
39
+ COMMIT;
40
+ `);
41
+ } catch (e) {
42
+ try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
43
+ throw e;
44
+ }
45
+ }