@vellumai/assistant 0.3.4 → 0.3.6

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 (506) hide show
  1. package/Dockerfile +2 -0
  2. package/README.md +88 -2
  3. package/eslint.config.mjs +31 -0
  4. package/package.json +1 -1
  5. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  6. package/scripts/ipc/generate-swift.ts +31 -2
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
  8. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  9. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
  10. package/src/__tests__/approval-message-composer.test.ts +253 -0
  11. package/src/__tests__/browser-manager.test.ts +1 -0
  12. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  13. package/src/__tests__/call-domain.test.ts +12 -2
  14. package/src/__tests__/call-orchestrator.test.ts +799 -249
  15. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  16. package/src/__tests__/call-recovery.test.ts +3 -0
  17. package/src/__tests__/call-routes-http.test.ts +32 -2
  18. package/src/__tests__/call-store.test.ts +3 -0
  19. package/src/__tests__/channel-approval-routes.test.ts +1277 -98
  20. package/src/__tests__/channel-approval.test.ts +37 -0
  21. package/src/__tests__/channel-approvals.test.ts +36 -50
  22. package/src/__tests__/channel-guardian.test.ts +630 -22
  23. package/src/__tests__/channel-readiness-service.test.ts +324 -0
  24. package/src/__tests__/checker.test.ts +14 -7
  25. package/src/__tests__/clarification-resolver.test.ts +44 -24
  26. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  27. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  28. package/src/__tests__/config-schema.test.ts +14 -8
  29. package/src/__tests__/context-window-manager.test.ts +30 -2
  30. package/src/__tests__/contradiction-checker.test.ts +20 -5
  31. package/src/__tests__/credential-security-invariants.test.ts +7 -2
  32. package/src/__tests__/daemon-lifecycle.test.ts +13 -12
  33. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  34. package/src/__tests__/dictation-mode-detection.test.ts +63 -0
  35. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  36. package/src/__tests__/entity-search.test.ts +615 -0
  37. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  38. package/src/__tests__/guardian-action-store.test.ts +123 -0
  39. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  40. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  41. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  42. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  43. package/src/__tests__/handlers-twilio-config.test.ts +533 -0
  44. package/src/__tests__/intent-routing.test.ts +2 -0
  45. package/src/__tests__/ipc-snapshot.test.ts +291 -1
  46. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  47. package/src/__tests__/messaging-send-tool.test.ts +65 -0
  48. package/src/__tests__/model-intents.test.ts +96 -0
  49. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  50. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  51. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  52. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  53. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  54. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  55. package/src/__tests__/qdrant-manager.test.ts +27 -20
  56. package/src/__tests__/relay-server.test.ts +779 -40
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
  58. package/src/__tests__/run-orchestrator.test.ts +42 -4
  59. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  60. package/src/__tests__/runtime-runs.test.ts +16 -0
  61. package/src/__tests__/schedule-store.test.ts +18 -4
  62. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  63. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  64. package/src/__tests__/session-agent-loop.test.ts +857 -0
  65. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  66. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  67. package/src/__tests__/session-profile-injection.test.ts +6 -0
  68. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  69. package/src/__tests__/session-queue.test.ts +6 -0
  70. package/src/__tests__/session-runtime-assembly.test.ts +321 -13
  71. package/src/__tests__/session-slash-known.test.ts +6 -0
  72. package/src/__tests__/session-slash-queue.test.ts +6 -0
  73. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  74. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  75. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  76. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  77. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  78. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  79. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  80. package/src/__tests__/skills.test.ts +2 -0
  81. package/src/__tests__/sms-messaging-provider.test.ts +126 -0
  82. package/src/__tests__/starter-task-flow.test.ts +2 -0
  83. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  84. package/src/__tests__/system-prompt.test.ts +2 -0
  85. package/src/__tests__/task-management-tools.test.ts +2 -2
  86. package/src/__tests__/task-runner.test.ts +14 -4
  87. package/src/__tests__/terminal-tools.test.ts +25 -19
  88. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  90. package/src/__tests__/tool-executor.test.ts +23 -24
  91. package/src/__tests__/trust-store.test.ts +3 -3
  92. package/src/__tests__/twilio-rest.test.ts +29 -0
  93. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  94. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  95. package/src/__tests__/twilio-routes.test.ts +167 -11
  96. package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
  97. package/src/__tests__/user-reference.test.ts +2 -0
  98. package/src/__tests__/voice-quality.test.ts +222 -0
  99. package/src/__tests__/web-search.test.ts +46 -30
  100. package/src/__tests__/work-item-output.test.ts +110 -0
  101. package/src/agent/loop.ts +1 -1
  102. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  103. package/src/amazon/client.ts +1418 -0
  104. package/src/amazon/request-extractor.ts +135 -0
  105. package/src/amazon/session.ts +109 -0
  106. package/src/autonomy/autonomy-store.ts +5 -5
  107. package/src/browser-extension-relay/client.ts +124 -0
  108. package/src/browser-extension-relay/protocol.ts +63 -0
  109. package/src/browser-extension-relay/server.ts +177 -0
  110. package/src/bundler/app-bundler.ts +3 -3
  111. package/src/bundler/bundle-signer.ts +1 -1
  112. package/src/bundler/signature-verifier.ts +1 -1
  113. package/src/calls/call-conversation-messages.ts +33 -0
  114. package/src/calls/call-domain.ts +114 -10
  115. package/src/calls/call-orchestrator.ts +268 -59
  116. package/src/calls/call-pointer-messages.ts +53 -0
  117. package/src/calls/call-recovery.ts +3 -8
  118. package/src/calls/call-store.ts +69 -87
  119. package/src/calls/elevenlabs-config.ts +3 -2
  120. package/src/calls/guardian-action-sweep.ts +105 -0
  121. package/src/calls/guardian-dispatch.ts +203 -0
  122. package/src/calls/guardian-question-copy.ts +133 -0
  123. package/src/calls/relay-server.ts +466 -8
  124. package/src/calls/speaker-identification.ts +1 -1
  125. package/src/calls/twilio-config.ts +22 -14
  126. package/src/calls/twilio-provider.ts +6 -4
  127. package/src/calls/twilio-rest.ts +308 -7
  128. package/src/calls/twilio-routes.ts +65 -12
  129. package/src/calls/types.ts +3 -1
  130. package/src/channels/types.ts +25 -0
  131. package/src/cli/amazon.ts +815 -0
  132. package/src/cli/config-commands.ts +2 -2
  133. package/src/cli/core-commands.ts +4 -3
  134. package/src/cli/influencer.ts +244 -0
  135. package/src/cli/map.ts +89 -6
  136. package/src/cli.ts +1 -1
  137. package/src/config/agent-schema.ts +171 -0
  138. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  139. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  140. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  141. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  142. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  143. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  144. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  145. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  146. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  147. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  148. package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
  149. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
  150. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
  151. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  152. package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
  153. package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
  154. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  155. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  156. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  157. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  158. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  159. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  160. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  161. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
  162. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  163. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
  164. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
  165. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
  166. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
  167. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
  168. package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
  169. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
  170. package/src/config/bundled-skills/messaging/SKILL.md +33 -8
  171. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  172. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  174. package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
  175. package/src/config/bundled-skills/twitter/SKILL.md +19 -3
  176. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  177. package/src/config/bundled-tool-registry.ts +310 -0
  178. package/src/config/calls-schema.ts +181 -0
  179. package/src/config/core-schema.ts +309 -0
  180. package/src/config/defaults.ts +28 -3
  181. package/src/config/env-registry.ts +162 -0
  182. package/src/config/env.ts +175 -0
  183. package/src/config/loader.ts +6 -6
  184. package/src/config/memory-schema.ts +528 -0
  185. package/src/config/sandbox-schema.ts +55 -0
  186. package/src/config/schema.ts +158 -1133
  187. package/src/config/skill-state.ts +1 -1
  188. package/src/config/skills-schema.ts +32 -0
  189. package/src/config/skills.ts +35 -24
  190. package/src/config/system-prompt.ts +131 -56
  191. package/src/config/templates/IDENTITY.md +2 -2
  192. package/src/config/templates/SOUL.md +1 -1
  193. package/src/config/types.ts +1 -0
  194. package/src/config/user-reference.ts +4 -9
  195. package/src/config/vellum-skills/catalog.json +6 -7
  196. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  197. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
  198. package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
  199. package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
  200. package/src/context/window-manager.ts +27 -7
  201. package/src/daemon/approval-generators.ts +186 -0
  202. package/src/daemon/approved-devices-store.ts +140 -0
  203. package/src/daemon/assistant-attachments.ts +1 -1
  204. package/src/daemon/classifier.ts +35 -32
  205. package/src/daemon/config-watcher.ts +1 -1
  206. package/src/daemon/daemon-control.ts +217 -0
  207. package/src/daemon/handlers/apps.ts +2 -3
  208. package/src/daemon/handlers/config-channels.ts +158 -0
  209. package/src/daemon/handlers/config-inbox.ts +540 -0
  210. package/src/daemon/handlers/config-ingress.ts +231 -0
  211. package/src/daemon/handlers/config-integrations.ts +258 -0
  212. package/src/daemon/handlers/config-model.ts +143 -0
  213. package/src/daemon/handlers/config-parental.ts +163 -0
  214. package/src/daemon/handlers/config-scheduling.ts +172 -0
  215. package/src/daemon/handlers/config-slack.ts +92 -0
  216. package/src/daemon/handlers/config-telegram.ts +301 -0
  217. package/src/daemon/handlers/config-tools.ts +177 -0
  218. package/src/daemon/handlers/config-trust.ts +104 -0
  219. package/src/daemon/handlers/config-twilio.ts +1080 -0
  220. package/src/daemon/handlers/config.ts +53 -1689
  221. package/src/daemon/handlers/diagnostics.ts +1 -1
  222. package/src/daemon/handlers/dictation.ts +180 -0
  223. package/src/daemon/handlers/documents.ts +18 -32
  224. package/src/daemon/handlers/identity.ts +14 -23
  225. package/src/daemon/handlers/index.ts +11 -0
  226. package/src/daemon/handlers/misc.ts +3 -5
  227. package/src/daemon/handlers/pairing.ts +98 -0
  228. package/src/daemon/handlers/sessions.ts +56 -5
  229. package/src/daemon/handlers/shared.ts +6 -1
  230. package/src/daemon/handlers/skills.ts +1 -1
  231. package/src/daemon/handlers/twitter-auth.ts +2 -0
  232. package/src/daemon/handlers/work-items.ts +17 -9
  233. package/src/daemon/handlers/workspace-files.ts +4 -3
  234. package/src/daemon/install-cli-launchers.ts +113 -0
  235. package/src/daemon/ipc-contract/apps.ts +356 -0
  236. package/src/daemon/ipc-contract/browser.ts +74 -0
  237. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  238. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  239. package/src/daemon/ipc-contract/documents.ts +74 -0
  240. package/src/daemon/ipc-contract/inbox.ts +209 -0
  241. package/src/daemon/ipc-contract/integrations.ts +284 -0
  242. package/src/daemon/ipc-contract/memory.ts +48 -0
  243. package/src/daemon/ipc-contract/messages.ts +211 -0
  244. package/src/daemon/ipc-contract/pairing.ts +45 -0
  245. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  246. package/src/daemon/ipc-contract/schedules.ts +97 -0
  247. package/src/daemon/ipc-contract/sessions.ts +315 -0
  248. package/src/daemon/ipc-contract/shared.ts +42 -0
  249. package/src/daemon/ipc-contract/skills.ts +120 -0
  250. package/src/daemon/ipc-contract/subagents.ts +58 -0
  251. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  252. package/src/daemon/ipc-contract/trust.ts +60 -0
  253. package/src/daemon/ipc-contract/work-items.ts +225 -0
  254. package/src/daemon/ipc-contract/workspace.ts +113 -0
  255. package/src/daemon/ipc-contract-inventory.json +70 -0
  256. package/src/daemon/ipc-contract-inventory.ts +55 -29
  257. package/src/daemon/ipc-contract.ts +229 -2426
  258. package/src/daemon/ipc-protocol.ts +1 -1
  259. package/src/daemon/ipc-validate.ts +7 -0
  260. package/src/daemon/lifecycle.ts +97 -377
  261. package/src/daemon/pairing-store.ts +177 -0
  262. package/src/daemon/providers-setup.ts +43 -0
  263. package/src/daemon/ride-shotgun-handler.ts +68 -3
  264. package/src/daemon/server.ts +66 -46
  265. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  266. package/src/daemon/session-agent-loop.ts +117 -275
  267. package/src/daemon/session-dynamic-profile.ts +1 -1
  268. package/src/daemon/session-history.ts +1 -1
  269. package/src/daemon/session-media-retry.ts +1 -1
  270. package/src/daemon/session-messaging.ts +37 -2
  271. package/src/daemon/session-notifiers.ts +5 -25
  272. package/src/daemon/session-process.ts +99 -59
  273. package/src/daemon/session-queue-manager.ts +96 -4
  274. package/src/daemon/session-runtime-assembly.ts +199 -10
  275. package/src/daemon/session-surfaces.ts +19 -4
  276. package/src/daemon/session-tool-setup.ts +30 -30
  277. package/src/daemon/session-workspace.ts +1 -1
  278. package/src/daemon/session.ts +35 -2
  279. package/src/daemon/shutdown-handlers.ts +122 -0
  280. package/src/daemon/trace-emitter.ts +1 -1
  281. package/src/daemon/watch-handler.ts +36 -33
  282. package/src/doordash/cart-queries.ts +787 -0
  283. package/src/doordash/client.ts +144 -127
  284. package/src/doordash/order-queries.ts +85 -0
  285. package/src/doordash/queries.ts +10 -1308
  286. package/src/doordash/search-queries.ts +203 -0
  287. package/src/doordash/session.ts +3 -2
  288. package/src/doordash/store-queries.ts +246 -0
  289. package/src/doordash/types.ts +367 -0
  290. package/src/email/providers/agentmail.ts +2 -1
  291. package/src/email/providers/index.ts +3 -2
  292. package/src/email/service.ts +3 -2
  293. package/src/errors.ts +43 -0
  294. package/src/home-base/prebuilt/seed.ts +1 -1
  295. package/src/hooks/cli.ts +6 -5
  296. package/src/hooks/config.ts +6 -8
  297. package/src/hooks/discovery.ts +6 -5
  298. package/src/hooks/manager.ts +4 -3
  299. package/src/hooks/runner.ts +2 -2
  300. package/src/hooks/templates.ts +5 -5
  301. package/src/inbound/public-ingress-urls.ts +6 -4
  302. package/src/index.ts +4 -2
  303. package/src/influencer/client.ts +1104 -0
  304. package/src/instrument.ts +4 -3
  305. package/src/logfire.ts +4 -3
  306. package/src/memory/admin.ts +25 -35
  307. package/src/memory/attachments-store.ts +4 -7
  308. package/src/memory/channel-delivery-store.ts +30 -1
  309. package/src/memory/channel-guardian-store.ts +202 -2
  310. package/src/memory/clarification-resolver.ts +37 -33
  311. package/src/memory/conflict-store.ts +67 -61
  312. package/src/memory/contradiction-checker.ts +141 -117
  313. package/src/memory/conversation-store.ts +335 -51
  314. package/src/memory/db-connection.ts +27 -4
  315. package/src/memory/db-init.ts +265 -4
  316. package/src/memory/db.ts +14 -1
  317. package/src/memory/embedding-backend.ts +27 -5
  318. package/src/memory/embedding-ollama.ts +2 -1
  319. package/src/memory/entity-extractor.ts +38 -35
  320. package/src/memory/guardian-action-store.ts +430 -0
  321. package/src/memory/inbox-escalation-projection.ts +59 -0
  322. package/src/memory/inbox-thread-store.ts +218 -0
  323. package/src/memory/ingress-invite-store.ts +338 -0
  324. package/src/memory/ingress-member-store.ts +350 -0
  325. package/src/memory/items-extractor.ts +91 -97
  326. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  327. package/src/memory/job-handlers/media-processing.ts +69 -0
  328. package/src/memory/job-handlers/summarization.ts +32 -26
  329. package/src/memory/job-utils.ts +3 -10
  330. package/src/memory/jobs-store.ts +8 -10
  331. package/src/memory/jobs-worker.ts +55 -36
  332. package/src/memory/media-store.ts +759 -0
  333. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  334. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  335. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  336. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  337. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  338. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  339. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  340. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  341. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  342. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  343. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  344. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  345. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  346. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  347. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  348. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  349. package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
  350. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  351. package/src/memory/migrations/index.ts +24 -0
  352. package/src/memory/migrations/registry.ts +79 -0
  353. package/src/memory/migrations/validate-migration-state.ts +69 -0
  354. package/src/memory/qdrant-manager.ts +49 -8
  355. package/src/memory/query-builder.ts +1 -1
  356. package/src/memory/raw-query.ts +119 -0
  357. package/src/memory/recall-cache.ts +4 -1
  358. package/src/memory/retriever.ts +165 -47
  359. package/src/memory/schema-migration.ts +25 -984
  360. package/src/memory/schema.ts +228 -7
  361. package/src/memory/search/entity.ts +205 -31
  362. package/src/memory/search/lexical.ts +81 -52
  363. package/src/memory/search/ranking.ts +27 -23
  364. package/src/memory/search/semantic.ts +157 -19
  365. package/src/memory/search/types.ts +24 -0
  366. package/src/memory/shared-app-links-store.ts +4 -5
  367. package/src/memory/validation.ts +19 -0
  368. package/src/messaging/draft-store.ts +5 -6
  369. package/src/messaging/provider-types.ts +2 -0
  370. package/src/messaging/providers/sms/adapter.ts +201 -0
  371. package/src/messaging/providers/sms/client.ts +93 -0
  372. package/src/messaging/providers/sms/types.ts +7 -0
  373. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  374. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  375. package/src/messaging/providers/whatsapp/client.ts +67 -0
  376. package/src/messaging/style-analyzer.ts +5 -4
  377. package/src/messaging/thread-summarizer.ts +61 -69
  378. package/src/messaging/triage-engine.ts +62 -71
  379. package/src/migrations/config-merge.ts +53 -0
  380. package/src/migrations/data-layout.ts +68 -0
  381. package/src/migrations/data-merge.ts +33 -0
  382. package/src/migrations/hooks-merge.ts +90 -0
  383. package/src/migrations/index.ts +6 -0
  384. package/src/migrations/log.ts +23 -0
  385. package/src/migrations/skills-merge.ts +33 -0
  386. package/src/migrations/workspace-layout.ts +79 -0
  387. package/src/permissions/checker.ts +133 -11
  388. package/src/permissions/prompter.ts +14 -0
  389. package/src/permissions/shell-identity.ts +31 -1
  390. package/src/permissions/trust-store.ts +21 -1
  391. package/src/providers/anthropic/client.ts +4 -4
  392. package/src/providers/failover.ts +2 -2
  393. package/src/providers/model-intents.ts +70 -0
  394. package/src/providers/ollama/client.ts +2 -1
  395. package/src/providers/provider-send-message.ts +176 -0
  396. package/src/providers/registry.ts +71 -30
  397. package/src/providers/retry.ts +35 -1
  398. package/src/providers/types.ts +12 -1
  399. package/src/runtime/approval-conversation-turn.ts +97 -0
  400. package/src/runtime/approval-message-composer.ts +253 -0
  401. package/src/runtime/channel-approval-parser.ts +36 -2
  402. package/src/runtime/channel-approvals.ts +11 -24
  403. package/src/runtime/channel-guardian-service.ts +88 -21
  404. package/src/runtime/channel-readiness-service.ts +418 -0
  405. package/src/runtime/channel-readiness-types.ts +35 -0
  406. package/src/runtime/channel-retry-sweep.ts +184 -0
  407. package/src/runtime/guardian-context-resolver.ts +108 -0
  408. package/src/runtime/http-server.ts +275 -717
  409. package/src/runtime/http-types.ts +59 -3
  410. package/src/runtime/middleware/auth.ts +116 -0
  411. package/src/runtime/middleware/error-handler.ts +33 -0
  412. package/src/runtime/middleware/twilio-validation.ts +127 -0
  413. package/src/runtime/routes/app-routes.ts +1 -1
  414. package/src/runtime/routes/call-routes.ts +51 -7
  415. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  416. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  417. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  418. package/src/runtime/routes/channel-route-shared.ts +144 -0
  419. package/src/runtime/routes/channel-routes.ts +32 -1588
  420. package/src/runtime/routes/conversation-routes.ts +50 -7
  421. package/src/runtime/routes/events-routes.ts +2 -2
  422. package/src/runtime/routes/identity-routes.ts +126 -0
  423. package/src/runtime/routes/pairing-routes.ts +143 -0
  424. package/src/runtime/routes/run-routes.ts +15 -1
  425. package/src/runtime/run-orchestrator.ts +86 -35
  426. package/src/schedule/schedule-store.ts +36 -32
  427. package/src/schedule/scheduler.ts +3 -3
  428. package/src/security/encrypted-store.ts +5 -7
  429. package/src/security/oauth2.ts +45 -15
  430. package/src/security/parental-control-store.ts +183 -0
  431. package/src/security/secret-allowlist.ts +4 -3
  432. package/src/security/secret-scanner.ts +5 -5
  433. package/src/security/secure-keys.ts +1 -1
  434. package/src/security/token-manager.ts +3 -2
  435. package/src/services/vercel-deploy.ts +6 -2
  436. package/src/skills/tool-manifest.ts +3 -3
  437. package/src/skills/vellum-catalog-remote.ts +75 -16
  438. package/src/slack/slack-webhook.ts +2 -1
  439. package/src/swarm/orchestrator.ts +92 -1
  440. package/src/swarm/router-planner.ts +6 -9
  441. package/src/swarm/worker-prompts.ts +9 -12
  442. package/src/tasks/task-compiler.ts +19 -28
  443. package/src/tasks/task-runner.ts +1 -1
  444. package/src/tools/assets/materialize.ts +2 -2
  445. package/src/tools/assets/search.ts +15 -14
  446. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  447. package/src/tools/browser/auto-navigate.ts +1 -0
  448. package/src/tools/browser/browser-execution.ts +10 -1
  449. package/src/tools/browser/browser-manager.ts +119 -4
  450. package/src/tools/browser/network-recorder.ts +5 -0
  451. package/src/tools/calls/call-start.ts +1 -0
  452. package/src/tools/credentials/broker.ts +11 -2
  453. package/src/tools/credentials/metadata-store.ts +18 -14
  454. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  455. package/src/tools/credentials/vault.ts +49 -23
  456. package/src/tools/execution-target.ts +11 -1
  457. package/src/tools/executor.ts +68 -9
  458. package/src/tools/host-terminal/cli-discover.ts +1 -1
  459. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  460. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  461. package/src/tools/network/script-proxy/server.ts +1 -1
  462. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  463. package/src/tools/network/web-fetch.ts +18 -2
  464. package/src/tools/network/web-search.ts +8 -4
  465. package/src/tools/reminder/reminder-store.ts +14 -15
  466. package/src/tools/schedule/create.ts +1 -0
  467. package/src/tools/schedule/list.ts +2 -1
  468. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  469. package/src/tools/skills/skill-script-runner.ts +24 -9
  470. package/src/tools/skills/skill-tool-factory.ts +1 -0
  471. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  472. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  473. package/src/tools/terminal/parser.ts +50 -0
  474. package/src/tools/types.ts +2 -0
  475. package/src/tools/watcher/delete.ts +6 -0
  476. package/src/tools/weather/service.ts +1 -1
  477. package/src/twitter/client.ts +190 -24
  478. package/src/twitter/router.ts +1 -1
  479. package/src/twitter/session.ts +4 -3
  480. package/src/util/clipboard.ts +1 -1
  481. package/src/util/errors.ts +65 -8
  482. package/src/util/fs.ts +40 -0
  483. package/src/util/json.ts +10 -0
  484. package/src/util/log-redact.ts +189 -0
  485. package/src/util/logger.ts +19 -17
  486. package/src/util/object.ts +3 -0
  487. package/src/util/platform.ts +105 -363
  488. package/src/util/pricing.ts +1 -1
  489. package/src/util/promise-guard.ts +1 -1
  490. package/src/util/retry.ts +19 -0
  491. package/src/util/row-mapper.ts +79 -0
  492. package/src/util/silently.ts +21 -0
  493. package/src/watcher/engine.ts +5 -1
  494. package/src/watcher/provider-types.ts +20 -0
  495. package/src/watcher/providers/github.ts +156 -0
  496. package/src/watcher/providers/gmail.ts +1 -0
  497. package/src/watcher/providers/google-calendar.ts +1 -0
  498. package/src/watcher/providers/linear.ts +460 -0
  499. package/src/watcher/providers/slack.ts +1 -0
  500. package/src/work-items/work-item-runner.ts +1 -1
  501. package/src/workspace/git-service.ts +1 -1
  502. package/src/workspace/provider-commit-message-generator.ts +51 -22
  503. package/src/__tests__/call-bridge.test.ts +0 -517
  504. package/src/__tests__/session-process-bridge.test.ts +0 -244
  505. package/src/calls/call-bridge.ts +0 -168
  506. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -1,12 +1,11 @@
1
- import Anthropic from '@anthropic-ai/sdk';
2
- import { getConfig } from '../config/loader.js';
1
+ import { getConfiguredProvider, createTimeout, extractToolUse, userMessage } from '../providers/provider-send-message.js';
3
2
  import { getLogger } from '../util/logger.js';
4
3
  import { truncate } from '../util/truncate.js';
5
4
  import type { ThreadMessage, ThreadSummary } from './types.js';
6
5
 
7
6
  const log = getLogger('thread-summarizer');
8
7
 
9
- const SUMMARIZATION_MODEL = 'claude-haiku-4-5-20251001';
8
+ const SUMMARIZATION_MODEL_INTENT = 'latency-optimized' as const;
10
9
  const SUMMARIZATION_TIMEOUT_MS = 20_000;
11
10
  const DEFAULT_MAX_TOKENS = 4000;
12
11
  const CHARS_PER_TOKEN = 4;
@@ -189,78 +188,71 @@ async function summarizeWithLLM(
189
188
  messages: ThreadMessage[],
190
189
  maxTokens: number,
191
190
  ): Promise<ThreadSummary> {
192
- const config = getConfig();
193
- const apiKey = config.apiKeys.anthropic ?? process.env.ANTHROPIC_API_KEY;
194
- if (!apiKey) {
195
- log.warn('No Anthropic API key available for thread summarization, returning basic summary');
191
+ const provider = getConfiguredProvider();
192
+ if (!provider) {
193
+ log.warn('Configured provider unavailable for thread summarization, returning basic summary');
196
194
  return buildFallbackSummary(messages);
197
195
  }
198
196
 
199
- const truncated = truncateMessages(messages, maxTokens);
200
- const transcript = formatTranscript(truncated);
197
+ const truncatedMsgs = truncateMessages(messages, maxTokens);
198
+ const transcript = formatTranscript(truncatedMsgs);
201
199
 
202
200
  try {
203
- const client = new Anthropic({ apiKey });
204
- const abortController = new AbortController();
205
- let timer: ReturnType<typeof setTimeout>;
206
- const apiCall = client.messages.create({
207
- model: SUMMARIZATION_MODEL,
208
- max_tokens: 1024,
209
- system: SYSTEM_PROMPT,
210
- tools: [STORE_SUMMARY_TOOL],
211
- tool_choice: { type: 'tool' as const, name: 'store_thread_summary' },
212
- messages: [{
213
- role: 'user' as const,
214
- content: `Summarize this conversation thread (${messages.length} messages):\n\n${transcript}`,
215
- }],
216
- }, { signal: abortController.signal });
217
-
218
- // Swallow the abort rejection that fires when the timeout wins the race
219
- apiCall.catch(() => {});
220
- const response = await Promise.race([
221
- apiCall.finally(() => clearTimeout(timer)),
222
- new Promise<never>((_, reject) => {
223
- timer = setTimeout(() => {
224
- abortController.abort();
225
- reject(new Error('Thread summarization LLM timeout'));
226
- }, SUMMARIZATION_TIMEOUT_MS);
227
- }),
228
- ]);
229
-
230
- const toolBlock = response.content.find((b) => b.type === 'tool_use');
231
- if (!toolBlock || toolBlock.type !== 'tool_use') {
232
- log.warn('No tool_use block in summarization response, returning fallback');
233
- return buildFallbackSummary(messages);
234
- }
201
+ const { signal, cleanup } = createTimeout(SUMMARIZATION_TIMEOUT_MS);
202
+
203
+ try {
204
+ const response = await provider.sendMessage(
205
+ [userMessage(`Summarize this conversation thread (${messages.length} messages):\n\n${transcript}`)],
206
+ [STORE_SUMMARY_TOOL],
207
+ SYSTEM_PROMPT,
208
+ {
209
+ config: {
210
+ modelIntent: SUMMARIZATION_MODEL_INTENT,
211
+ max_tokens: 1024,
212
+ tool_choice: { type: 'tool' as const, name: 'store_thread_summary' },
213
+ },
214
+ signal,
215
+ },
216
+ );
217
+ cleanup();
235
218
 
236
- const input = toolBlock.input as {
237
- summary?: string;
238
- participants?: Array<{ name: string; role?: string }>;
239
- openQuestions?: string[];
240
- lastAction?: string;
241
- sentiment?: string;
242
- };
243
-
244
- const validSentiments = new Set(['positive', 'neutral', 'negative', 'mixed']);
245
- const sentiment = validSentiments.has(input.sentiment ?? '')
246
- ? (input.sentiment as ThreadSummary['sentiment'])
247
- : 'neutral';
248
-
249
- return {
250
- summary: truncate(String(input.summary ?? ''), 2000, ''),
251
- participants: Array.isArray(input.participants)
252
- ? input.participants.map((p) => ({
253
- name: String(p.name),
254
- ...(p.role ? { role: String(p.role) } : {}),
255
- }))
256
- : extractParticipants(messages),
257
- openQuestions: Array.isArray(input.openQuestions)
258
- ? input.openQuestions.map((q) => String(q))
259
- : [],
260
- lastAction: truncate(String(input.lastAction ?? ''), 500, ''),
261
- sentiment,
262
- messageCount: messages.length,
263
- };
219
+ const toolBlock = extractToolUse(response);
220
+ if (!toolBlock) {
221
+ log.warn('No tool_use block in summarization response, returning fallback');
222
+ return buildFallbackSummary(messages);
223
+ }
224
+
225
+ const input = toolBlock.input as {
226
+ summary?: string;
227
+ participants?: Array<{ name: string; role?: string }>;
228
+ openQuestions?: string[];
229
+ lastAction?: string;
230
+ sentiment?: string;
231
+ };
232
+
233
+ const validSentiments = new Set(['positive', 'neutral', 'negative', 'mixed']);
234
+ const sentiment = validSentiments.has(input.sentiment ?? '')
235
+ ? (input.sentiment as ThreadSummary['sentiment'])
236
+ : 'neutral';
237
+
238
+ return {
239
+ summary: truncate(String(input.summary ?? ''), 2000, ''),
240
+ participants: Array.isArray(input.participants)
241
+ ? input.participants.map((p) => ({
242
+ name: String(p.name),
243
+ ...(p.role ? { role: String(p.role) } : {}),
244
+ }))
245
+ : extractParticipants(messages),
246
+ openQuestions: Array.isArray(input.openQuestions)
247
+ ? input.openQuestions.map((q) => String(q))
248
+ : [],
249
+ lastAction: truncate(String(input.lastAction ?? ''), 500, ''),
250
+ sentiment,
251
+ messageCount: messages.length,
252
+ };
253
+ } finally {
254
+ cleanup();
255
+ }
264
256
  } catch (err) {
265
257
  const message = err instanceof Error ? err.message : String(err);
266
258
  log.warn({ err: message }, 'Thread summarization LLM call failed, returning fallback');
@@ -2,16 +2,15 @@
2
2
  * Channel-agnostic message triage engine.
3
3
  *
4
4
  * Classifies an inbound message by combining sender context from the
5
- * contact graph, matching action playbooks, and an LLM call (Haiku)
5
+ * contact graph, matching action playbooks, and an LLM call
6
6
  * for final classification. Results are persisted to the triageResults
7
7
  * table for accuracy review.
8
8
  */
9
9
 
10
- import Anthropic from '@anthropic-ai/sdk';
10
+ import { getConfiguredProvider, createTimeout, extractToolUse, userMessage } from '../providers/provider-send-message.js';
11
11
  import { truncate } from '../util/truncate.js';
12
12
  import { v4 as uuid } from 'uuid';
13
13
  import { and, eq, isNull, desc } from 'drizzle-orm';
14
- import { getConfig } from '../config/loader.js';
15
14
  import { getLogger } from '../util/logger.js';
16
15
  import { getDb } from '../memory/db.js';
17
16
  import { memoryItems, triageResults } from '../memory/schema.js';
@@ -24,7 +23,7 @@ import { DEFAULT_TRIAGE_CATEGORIES } from './types.js';
24
23
 
25
24
  const log = getLogger('triage-engine');
26
25
 
27
- const TRIAGE_MODEL = 'claude-haiku-4-5-20251001';
26
+ const TRIAGE_MODEL_INTENT = 'latency-optimized' as const;
28
27
  const TRIAGE_CLASSIFICATION_TIMEOUT_MS = 15_000;
29
28
 
30
29
  // ── Playbook fetching ────────────────────────────────────────────────
@@ -189,10 +188,9 @@ export async function triageMessage(
189
188
  const playbookMatches = fetchMatchingPlaybooks(message.channel, scopeId);
190
189
 
191
190
  // Step 3: Classify with LLM
192
- const config = getConfig();
193
- const apiKey = config.apiKeys.anthropic ?? process.env.ANTHROPIC_API_KEY;
194
- if (!apiKey) {
195
- log.warn('No Anthropic API key available for triage classification, returning fallback');
191
+ const provider = getConfiguredProvider();
192
+ if (!provider) {
193
+ log.warn('Configured provider unavailable for triage classification, returning fallback');
196
194
  const result = buildFallbackResult();
197
195
  persistTriageResult(message, result, playbookMatches);
198
196
  return result;
@@ -200,7 +198,7 @@ export async function triageMessage(
200
198
 
201
199
  let result: TriageResult;
202
200
  try {
203
- result = await classifyWithLLM(message, contact, playbookMatches, apiKey);
201
+ result = await classifyWithLLM(message, contact, playbookMatches);
204
202
  } catch (err) {
205
203
  const errMsg = err instanceof Error ? err.message : String(err);
206
204
  log.warn({ err: errMsg }, 'Triage LLM call failed, returning fallback');
@@ -217,77 +215,70 @@ async function classifyWithLLM(
217
215
  message: InboundMessage,
218
216
  contact: ContactWithChannels | null,
219
217
  playbookMatches: PlaybookMatch[],
220
- apiKey: string,
221
218
  ): Promise<TriageResult> {
222
- const client = new Anthropic({ apiKey });
223
- const abortController = new AbortController();
224
- let timer: ReturnType<typeof setTimeout>;
219
+ const provider = getConfiguredProvider()!;
220
+ const { signal, cleanup } = createTimeout(TRIAGE_CLASSIFICATION_TIMEOUT_MS);
225
221
 
226
222
  const systemPrompt = buildSystemPrompt(contact, playbookMatches);
227
223
  const userPrompt = buildUserPrompt(message);
228
224
 
229
- const apiCall = client.messages.create({
230
- model: TRIAGE_MODEL,
231
- max_tokens: 1024,
232
- system: systemPrompt,
233
- tools: [STORE_TRIAGE_TOOL],
234
- tool_choice: { type: 'tool' as const, name: 'store_triage_result' },
235
- messages: [{
236
- role: 'user' as const,
237
- content: userPrompt,
238
- }],
239
- }, { signal: abortController.signal });
240
-
241
- // Swallow the abort rejection that fires when the timeout wins the race
242
- apiCall.catch(() => {});
243
- const response = await Promise.race([
244
- apiCall.finally(() => clearTimeout(timer)),
245
- new Promise<never>((_, reject) => {
246
- timer = setTimeout(() => {
247
- abortController.abort();
248
- reject(new Error('Triage classification LLM timeout'));
249
- }, TRIAGE_CLASSIFICATION_TIMEOUT_MS);
250
- }),
251
- ]);
252
-
253
- const toolBlock = response.content.find((b) => b.type === 'tool_use');
254
- if (!toolBlock || toolBlock.type !== 'tool_use') {
255
- log.warn('No tool_use block in triage response, returning fallback');
256
- return buildFallbackResult();
257
- }
258
-
259
- const input = toolBlock.input as {
260
- category?: string;
261
- confidence?: number;
262
- suggestedAction?: string;
263
- matchedPlaybookTriggers?: string[];
264
- };
225
+ try {
226
+ const response = await provider.sendMessage(
227
+ [userMessage(userPrompt)],
228
+ [STORE_TRIAGE_TOOL],
229
+ systemPrompt,
230
+ {
231
+ config: {
232
+ modelIntent: TRIAGE_MODEL_INTENT,
233
+ max_tokens: 1024,
234
+ tool_choice: { type: 'tool' as const, name: 'store_triage_result' },
235
+ },
236
+ signal,
237
+ },
238
+ );
239
+ cleanup();
265
240
 
266
- const matchedTriggers = new Set(
267
- Array.isArray(input.matchedPlaybookTriggers) ? input.matchedPlaybookTriggers : [],
268
- );
241
+ const toolBlock = extractToolUse(response);
242
+ if (!toolBlock) {
243
+ log.warn('No tool_use block in triage response, returning fallback');
244
+ return buildFallbackResult();
245
+ }
269
246
 
270
- // Map LLM-identified triggers back to the full playbook data
271
- const matchedPlaybooks = playbookMatches
272
- .filter(({ playbook }) => matchedTriggers.has(playbook.trigger))
273
- .map(({ playbook }) => ({
274
- trigger: playbook.trigger,
275
- action: playbook.action,
276
- autonomyLevel: playbook.autonomyLevel,
277
- }));
247
+ const input = toolBlock.input as {
248
+ category?: string;
249
+ confidence?: number;
250
+ suggestedAction?: string;
251
+ matchedPlaybookTriggers?: string[];
252
+ };
278
253
 
279
- const confidence = typeof input.confidence === 'number'
280
- ? Math.max(0, Math.min(1, input.confidence))
281
- : 0.5;
254
+ const matchedTriggers = new Set(
255
+ Array.isArray(input.matchedPlaybookTriggers) ? input.matchedPlaybookTriggers : [],
256
+ );
282
257
 
283
- return {
284
- category: typeof input.category === 'string' ? input.category : 'needs_response',
285
- confidence,
286
- suggestedAction: typeof input.suggestedAction === 'string'
287
- ? truncate(input.suggestedAction, 500, '')
288
- : 'Review manually',
289
- matchedPlaybooks,
290
- };
258
+ // Map LLM-identified triggers back to the full playbook data
259
+ const matchedPlaybooks = playbookMatches
260
+ .filter(({ playbook }) => matchedTriggers.has(playbook.trigger))
261
+ .map(({ playbook }) => ({
262
+ trigger: playbook.trigger,
263
+ action: playbook.action,
264
+ autonomyLevel: playbook.autonomyLevel,
265
+ }));
266
+
267
+ const confidence = typeof input.confidence === 'number'
268
+ ? Math.max(0, Math.min(1, input.confidence))
269
+ : 0.5;
270
+
271
+ return {
272
+ category: typeof input.category === 'string' ? input.category : 'needs_response',
273
+ confidence,
274
+ suggestedAction: typeof input.suggestedAction === 'string'
275
+ ? truncate(input.suggestedAction, 500, '')
276
+ : 'Review manually',
277
+ matchedPlaybooks,
278
+ };
279
+ } finally {
280
+ cleanup();
281
+ }
291
282
  }
292
283
 
293
284
  // ── Persistence ─────────────────────────────────────────────────────
@@ -0,0 +1,53 @@
1
+ import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
2
+ import { isPlainObject } from '../util/object.js';
3
+ import { migrationLog } from './log.js';
4
+
5
+ /**
6
+ * When migratePath skips config.json because the workspace copy already
7
+ * exists, the legacy root config may still contain keys (e.g. slackWebhookUrl)
8
+ * that were never written to the workspace config. This merges any missing
9
+ * top-level keys from the legacy file into the workspace file so they are
10
+ * not silently lost during upgrade.
11
+ */
12
+ export function mergeSkippedConfigKeys(legacyPath: string, workspacePath: string): void {
13
+ if (!existsSync(legacyPath) || !existsSync(workspacePath)) return;
14
+
15
+ let legacy: Record<string, unknown>;
16
+ let workspace: Record<string, unknown>;
17
+ try {
18
+ const legacyRaw = JSON.parse(readFileSync(legacyPath, 'utf-8'));
19
+ const workspaceRaw = JSON.parse(readFileSync(workspacePath, 'utf-8'));
20
+ if (!isPlainObject(legacyRaw) || !isPlainObject(workspaceRaw)) return;
21
+ legacy = legacyRaw;
22
+ workspace = workspaceRaw;
23
+ } catch {
24
+ return; // malformed JSON — skip silently
25
+ }
26
+
27
+ const merged: string[] = [];
28
+ for (const key of Object.keys(legacy)) {
29
+ if (!(key in workspace)) {
30
+ workspace[key] = legacy[key];
31
+ merged.push(key);
32
+ }
33
+ }
34
+
35
+ if (merged.length > 0) {
36
+ try {
37
+ writeFileSync(workspacePath, JSON.stringify(workspace, null, 2) + '\n');
38
+ // Remove merged keys from legacy config so they are not resurrected
39
+ // if a user later deletes them from the workspace config.
40
+ for (const key of merged) {
41
+ delete legacy[key];
42
+ }
43
+ if (Object.keys(legacy).length === 0) {
44
+ unlinkSync(legacyPath);
45
+ } else {
46
+ writeFileSync(legacyPath, JSON.stringify(legacy, null, 2) + '\n');
47
+ }
48
+ migrationLog('info', 'Merged legacy config keys into workspace config', { keys: merged });
49
+ } catch (err) {
50
+ migrationLog('warn', 'Failed to merge legacy config keys', { err: String(err), keys: merged });
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,68 @@
1
+ import { existsSync, mkdirSync, renameSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { getRootDir } from '../util/platform.js';
4
+ import { migrationLog } from './log.js';
5
+
6
+ /**
7
+ * Migrate files from the old flat ~/.vellum layout to the new structured
8
+ * layout with data/ and protected/ subdirectories.
9
+ *
10
+ * Idempotent: skips items that have already been migrated.
11
+ * Uses renameSync for atomic moves (same filesystem).
12
+ */
13
+ export function migrateToDataLayout(): void {
14
+ const root = getRootDir();
15
+ const data = join(root, 'data');
16
+
17
+ if (!existsSync(root)) return;
18
+
19
+ function migrateItem(oldPath: string, newPath: string): void {
20
+ if (!existsSync(oldPath)) return;
21
+ if (existsSync(newPath)) return;
22
+ try {
23
+ const newDir = dirname(newPath);
24
+ if (!existsSync(newDir)) {
25
+ mkdirSync(newDir, { recursive: true });
26
+ }
27
+ renameSync(oldPath, newPath);
28
+ migrationLog('info', 'Migrated path', { from: oldPath, to: newPath });
29
+ } catch (err) {
30
+ migrationLog('warn', 'Failed to migrate path', { err: String(err), from: oldPath, to: newPath });
31
+ }
32
+ }
33
+
34
+ // DB: ~/.vellum/data/assistant.db → ~/.vellum/data/db/assistant.db
35
+ migrateItem(join(data, 'assistant.db'), join(data, 'db', 'assistant.db'));
36
+ migrateItem(join(data, 'assistant.db-wal'), join(data, 'db', 'assistant.db-wal'));
37
+ migrateItem(join(data, 'assistant.db-shm'), join(data, 'db', 'assistant.db-shm'));
38
+
39
+ // Qdrant PID: ~/.vellum/qdrant.pid → ~/.vellum/data/qdrant/qdrant.pid
40
+ migrateItem(join(root, 'qdrant.pid'), join(data, 'qdrant', 'qdrant.pid'));
41
+
42
+ // Qdrant binary: ~/.vellum/bin/ → ~/.vellum/data/qdrant/bin/
43
+ migrateItem(join(root, 'bin'), join(data, 'qdrant', 'bin'));
44
+
45
+ // Logs: ~/.vellum/logs/ → ~/.vellum/data/logs/
46
+ migrateItem(join(root, 'logs'), join(data, 'logs'));
47
+
48
+ // Memory: ~/.vellum/memory/ → ~/.vellum/data/memory/
49
+ migrateItem(join(root, 'memory'), join(data, 'memory'));
50
+
51
+ // Apps: ~/.vellum/apps/ → ~/.vellum/data/apps/
52
+ migrateItem(join(root, 'apps'), join(data, 'apps'));
53
+
54
+ // Browser auth: ~/.vellum/browser-auth/ → ~/.vellum/data/browser-auth/
55
+ migrateItem(join(root, 'browser-auth'), join(data, 'browser-auth'));
56
+
57
+ // Browser profile: ~/.vellum/browser-profile/ → ~/.vellum/data/browser-profile/
58
+ migrateItem(join(root, 'browser-profile'), join(data, 'browser-profile'));
59
+
60
+ // History: ~/.vellum/history → ~/.vellum/data/history
61
+ migrateItem(join(root, 'history'), join(data, 'history'));
62
+
63
+ // Protected files: ~/.vellum/X → ~/.vellum/protected/X
64
+ const protectedDir = join(root, 'protected');
65
+ migrateItem(join(root, 'trust.json'), join(protectedDir, 'trust.json'));
66
+ migrateItem(join(root, 'keys.enc'), join(protectedDir, 'keys.enc'));
67
+ migrateItem(join(root, 'secret-allowlist.json'), join(protectedDir, 'secret-allowlist.json'));
68
+ }
@@ -0,0 +1,33 @@
1
+ import { existsSync, readdirSync, renameSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { migrationLog } from './log.js';
4
+
5
+ /**
6
+ * When migratePath skips the data directory because workspace/data already
7
+ * exists (e.g. the user's project had a data/ folder that was extracted from
8
+ * sandbox/fs), the legacy data directory may still contain internal state
9
+ * subdirectories (db/, logs/, sandbox/, etc.) that need to be preserved.
10
+ * This merges any missing entries from the legacy data path into workspace/data.
11
+ */
12
+ export function mergeLegacyDataEntries(legacyDir: string, workspaceDir: string): void {
13
+ if (!existsSync(legacyDir) || !existsSync(workspaceDir)) return;
14
+
15
+ let entries: import('node:fs').Dirent[];
16
+ try {
17
+ entries = readdirSync(legacyDir, { withFileTypes: true });
18
+ } catch {
19
+ return;
20
+ }
21
+
22
+ for (const entry of entries) {
23
+ const src = join(legacyDir, entry.name);
24
+ const dest = join(workspaceDir, entry.name);
25
+ if (existsSync(dest)) continue; // already present in workspace
26
+ try {
27
+ renameSync(src, dest);
28
+ migrationLog('info', 'Merged legacy data entry into workspace', { from: src, to: dest });
29
+ } catch (err) {
30
+ migrationLog('warn', 'Failed to merge legacy data entry', { err: String(err), from: src, to: dest });
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,90 @@
1
+ import { existsSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { isPlainObject } from '../util/object.js';
4
+ import { migrationLog } from './log.js';
5
+
6
+ /**
7
+ * Merge missing hook entries from a legacy hooks/config.json into the
8
+ * workspace hooks/config.json. Only adds hooks that don't already exist
9
+ * in the workspace config so user changes are never overwritten.
10
+ */
11
+ export function mergeHooksConfig(legacyPath: string, workspacePath: string): void {
12
+ let legacy: Record<string, unknown>;
13
+ let workspace: Record<string, unknown>;
14
+ try {
15
+ const legacyRaw = JSON.parse(readFileSync(legacyPath, 'utf-8'));
16
+ const workspaceRaw = JSON.parse(readFileSync(workspacePath, 'utf-8'));
17
+ if (!isPlainObject(legacyRaw) || !isPlainObject(workspaceRaw)) return;
18
+ legacy = legacyRaw;
19
+ workspace = workspaceRaw;
20
+ } catch {
21
+ return;
22
+ }
23
+
24
+ const legacyHooks = legacy.hooks;
25
+ const wsHooks = workspace.hooks;
26
+ if (!isPlainObject(legacyHooks) || !isPlainObject(wsHooks)) return;
27
+
28
+ const merged: string[] = [];
29
+ for (const hookName of Object.keys(legacyHooks)) {
30
+ if (!(hookName in wsHooks)) {
31
+ wsHooks[hookName] = legacyHooks[hookName];
32
+ merged.push(hookName);
33
+ }
34
+ }
35
+
36
+ if (merged.length > 0) {
37
+ try {
38
+ writeFileSync(workspacePath, JSON.stringify(workspace, null, 2) + '\n');
39
+ // Remove merged hooks from legacy config to prevent resurrection
40
+ for (const hookName of merged) {
41
+ delete legacyHooks[hookName];
42
+ }
43
+ if (Object.keys(legacyHooks).length === 0) {
44
+ unlinkSync(legacyPath);
45
+ } else {
46
+ writeFileSync(legacyPath, JSON.stringify(legacy, null, 2) + '\n');
47
+ }
48
+ migrationLog('info', 'Merged legacy hooks config entries into workspace', { hooks: merged });
49
+ } catch (err) {
50
+ migrationLog('warn', 'Failed to merge legacy hooks config', { err: String(err), hooks: merged });
51
+ }
52
+ }
53
+ }
54
+
55
+ /**
56
+ * When migratePath skips the hooks directory because the workspace copy
57
+ * already exists (e.g. pre-created by ensureDataDir), the legacy hooks
58
+ * directory may still contain individual hook files/subdirectories that
59
+ * were never moved. This merges any missing entries from the legacy
60
+ * path into the workspace hooks path so they are not silently lost.
61
+ */
62
+ export function mergeLegacyHooks(legacyDir: string, workspaceDir: string): void {
63
+ if (!existsSync(legacyDir) || !existsSync(workspaceDir)) return;
64
+
65
+ let entries: import('node:fs').Dirent[];
66
+ try {
67
+ entries = readdirSync(legacyDir, { withFileTypes: true });
68
+ } catch {
69
+ return;
70
+ }
71
+
72
+ for (const entry of entries) {
73
+ const src = join(legacyDir, entry.name);
74
+ const dest = join(workspaceDir, entry.name);
75
+ if (existsSync(dest)) {
76
+ // config.json needs a merge rather than a skip — the legacy file may
77
+ // contain hook enabled/settings entries that the workspace copy lacks.
78
+ if (entry.name === 'config.json') {
79
+ mergeHooksConfig(src, dest);
80
+ }
81
+ continue;
82
+ }
83
+ try {
84
+ renameSync(src, dest);
85
+ migrationLog('info', 'Merged legacy hook into workspace', { from: src, to: dest });
86
+ } catch (err) {
87
+ migrationLog('warn', 'Failed to merge legacy hook', { err: String(err), from: src, to: dest });
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,6 @@
1
+ export { migrateToDataLayout } from './data-layout.js';
2
+ export { migrateToWorkspaceLayout, migratePath } from './workspace-layout.js';
3
+ export { mergeSkippedConfigKeys } from './config-merge.js';
4
+ export { mergeLegacyHooks, mergeHooksConfig } from './hooks-merge.js';
5
+ export { mergeLegacySkills } from './skills-merge.js';
6
+ export { mergeLegacyDataEntries } from './data-merge.js';
@@ -0,0 +1,23 @@
1
+ import pino from 'pino';
2
+ import { logSerializers } from '../util/log-redact.js';
3
+
4
+ /**
5
+ * Standalone pino instance for migration code. This must NOT use getLogger()
6
+ * because that triggers ensureDataDir(), which pre-creates workspace
7
+ * destination directories and causes migration moves to no-op.
8
+ *
9
+ * Writes to stderr only — no log files that might not exist yet.
10
+ */
11
+ const migrationLogger: pino.Logger = pino(
12
+ { name: 'migration', level: 'info', serializers: logSerializers },
13
+ pino.destination(2),
14
+ );
15
+
16
+ export function migrationLog(level: 'info' | 'warn' | 'debug', msg: string, data?: Record<string, unknown>): void {
17
+ if (level === 'debug') return;
18
+ if (data) {
19
+ migrationLogger[level](data, msg);
20
+ } else {
21
+ migrationLogger[level](msg);
22
+ }
23
+ }
@@ -0,0 +1,33 @@
1
+ import { existsSync, readdirSync, renameSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { migrationLog } from './log.js';
4
+
5
+ /**
6
+ * When migratePath skips the skills directory because the workspace copy
7
+ * already exists (e.g. pre-created by ensureDataDir), the legacy skills
8
+ * directory may still contain individual skill subdirectories that were
9
+ * never moved. This merges any missing skill subdirectories from the
10
+ * legacy path into the workspace skills path so they are not stranded.
11
+ */
12
+ export function mergeLegacySkills(legacyDir: string, workspaceDir: string): void {
13
+ if (!existsSync(legacyDir) || !existsSync(workspaceDir)) return;
14
+
15
+ let entries: import('node:fs').Dirent[];
16
+ try {
17
+ entries = readdirSync(legacyDir, { withFileTypes: true });
18
+ } catch {
19
+ return;
20
+ }
21
+
22
+ for (const entry of entries) {
23
+ const src = join(legacyDir, entry.name);
24
+ const dest = join(workspaceDir, entry.name);
25
+ if (existsSync(dest)) continue; // already present in workspace
26
+ try {
27
+ renameSync(src, dest);
28
+ migrationLog('info', 'Merged legacy skill into workspace', { from: src, to: dest });
29
+ } catch (err) {
30
+ migrationLog('warn', 'Failed to merge legacy skill', { err: String(err), from: src, to: dest });
31
+ }
32
+ }
33
+ }