@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,18 +1,14 @@
1
- import { mkdirSync, existsSync, statSync, unlinkSync, renameSync, readFileSync, writeFileSync, readdirSync, chmodSync } from 'node:fs';
2
- import { join, dirname } from 'node:path';
1
+ import { mkdirSync, existsSync, statSync, unlinkSync, readFileSync, writeFileSync, chmodSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
- /**
5
- * Stderr-only logger for migration code. Using the pino logger during
6
- * migration is unsafe because pino initialization calls ensureDataDir(),
7
- * which pre-creates workspace destination directories and causes migration
8
- * moves to no-op.
9
- */
10
- function migrationLog(level: 'info' | 'warn' | 'debug', msg: string, data?: Record<string, unknown>): void {
11
- if (level === 'debug') return; // suppress debug-level migration noise
12
- const prefix = level === 'warn' ? 'WARN' : 'INFO';
13
- const extra = data ? ' ' + JSON.stringify(data) : '';
14
- process.stderr.write(`[migration] ${prefix}: ${msg}${extra}\n`);
15
- }
4
+ import {
5
+ getBaseDataDir,
6
+ getDaemonSocket,
7
+ getDaemonTcpPort,
8
+ getDaemonTcpEnabled,
9
+ getDaemonTcpHost,
10
+ getDaemonIosPairing,
11
+ } from '../config/env-registry.js';
16
12
 
17
13
  export function isMacOS(): boolean {
18
14
  return process.platform === 'darwin';
@@ -45,12 +41,75 @@ export function getClipboardCommand(): string | null {
45
41
  return null;
46
42
  }
47
43
 
44
+ /**
45
+ * Read and parse the lockfile, trying the primary path (~/.vellum.lock.json)
46
+ * first, then falling back to the legacy path (~/.vellum.lockfile.json).
47
+ * Respects BASE_DATA_DIR for non-standard home directories.
48
+ * Returns null if neither file exists or both are malformed.
49
+ */
50
+ export function readLockfile(): Record<string, unknown> | null {
51
+ const base = getBaseDataDir() || homedir();
52
+ const candidates = [
53
+ join(base, '.vellum.lock.json'),
54
+ join(base, '.vellum.lockfile.json'),
55
+ ];
56
+ for (const lockPath of candidates) {
57
+ if (!existsSync(lockPath)) continue;
58
+ try {
59
+ const raw = JSON.parse(readFileSync(lockPath, 'utf-8'));
60
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
61
+ return raw as Record<string, unknown>;
62
+ }
63
+ } catch {
64
+ // malformed JSON — try next
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+
70
+ /**
71
+ * Normalize an assistant ID to its canonical form for DB operations.
72
+ *
73
+ * The system uses "self" as the canonical single-tenant identifier
74
+ * (see migration 007-assistant-id-to-self). However, the desktop UI
75
+ * sends the real assistant ID (e.g., "vellum-true-eel") while the
76
+ * inbound call path resolves phone numbers to config keys (typically
77
+ * "self"). This function maps any known lockfile assistant ID to "self"
78
+ * so both sides use a consistent DB key.
79
+ */
80
+ export function normalizeAssistantId(assistantId: string): string {
81
+ if (assistantId === 'self') return 'self';
82
+
83
+ try {
84
+ const lockData = readLockfile();
85
+ const assistants = lockData?.assistants as Array<Record<string, unknown>> | undefined;
86
+ if (assistants) {
87
+ for (const entry of assistants) {
88
+ if (entry.assistantId === assistantId) return 'self';
89
+ }
90
+ }
91
+ } catch {
92
+ // lockfile unreadable — return as-is
93
+ }
94
+
95
+ return assistantId;
96
+ }
97
+
98
+ /**
99
+ * Write data to the primary lockfile (~/.vellum.lock.json).
100
+ * Respects BASE_DATA_DIR for non-standard home directories.
101
+ */
102
+ export function writeLockfile(data: Record<string, unknown>): void {
103
+ const base = getBaseDataDir() || homedir();
104
+ writeFileSync(join(base, '.vellum.lock.json'), JSON.stringify(data, null, 2) + '\n');
105
+ }
106
+
48
107
  /**
49
108
  * Returns the root ~/.vellum directory. User-facing files (config, prompt
50
109
  * files, skills) and runtime files (socket, PID) live here.
51
110
  */
52
111
  export function getRootDir(): string {
53
- return join(process.env.BASE_DATA_DIR?.trim() || homedir(), '.vellum');
112
+ return join(getBaseDataDir() || homedir(), '.vellum');
54
113
  }
55
114
 
56
115
  /**
@@ -91,7 +150,7 @@ export function getInterfacesDir(): string {
91
150
  }
92
151
 
93
152
  export function getSocketPath(): string {
94
- const override = process.env.VELLUM_DAEMON_SOCKET?.trim();
153
+ const override = getDaemonSocket();
95
154
  if (override) {
96
155
  return expandHomePath(override);
97
156
  }
@@ -107,12 +166,7 @@ export function getSessionTokenPath(): string {
107
166
  * Reads VELLUM_DAEMON_TCP_PORT env var; defaults to 8765.
108
167
  */
109
168
  export function getTCPPort(): number {
110
- const override = process.env.VELLUM_DAEMON_TCP_PORT?.trim();
111
- if (override) {
112
- const port = parseInt(override, 10);
113
- if (!isNaN(port) && port > 0 && port <= 65535) return port;
114
- }
115
- return 8765;
169
+ return getDaemonTcpPort();
116
170
  }
117
171
 
118
172
  /**
@@ -127,9 +181,8 @@ export function getTCPPort(): number {
127
181
  * The macOS CLI (AssistantCli) also sets the env var for bundled-binary deployments.
128
182
  */
129
183
  export function isTCPEnabled(): boolean {
130
- const override = process.env.VELLUM_DAEMON_TCP_ENABLED?.trim();
131
- if (override === 'true' || override === '1') return true;
132
- if (override === 'false' || override === '0') return false;
184
+ const envValue = getDaemonTcpEnabled();
185
+ if (envValue !== undefined) return envValue;
133
186
  return existsSync(join(getRootDir(), 'tcp-enabled'));
134
187
  }
135
188
 
@@ -141,7 +194,7 @@ export function isTCPEnabled(): boolean {
141
194
  * 3. Default: '127.0.0.1' (localhost only)
142
195
  */
143
196
  export function getTCPHost(): string {
144
- const override = process.env.VELLUM_DAEMON_TCP_HOST?.trim();
197
+ const override = getDaemonTcpHost();
145
198
  if (override) return override;
146
199
  if (isIOSPairingEnabled()) return '0.0.0.0';
147
200
  return '127.0.0.1';
@@ -162,9 +215,8 @@ export function getTCPHost(): string {
162
215
  * access without exposing the daemon to the LAN.
163
216
  */
164
217
  export function isIOSPairingEnabled(): boolean {
165
- const override = process.env.VELLUM_DAEMON_IOS_PAIRING?.trim();
166
- if (override === 'true' || override === '1') return true;
167
- if (override === 'false' || override === '0') return false;
218
+ const envValue = getDaemonIosPairing();
219
+ if (envValue !== undefined) return envValue;
168
220
  return existsSync(join(getRootDir(), 'ios-pairing-enabled'));
169
221
  }
170
222
 
@@ -172,6 +224,27 @@ export function getHttpTokenPath(): string {
172
224
  return join(getRootDir(), 'http-token');
173
225
  }
174
226
 
227
+ /**
228
+ * Returns the path to the platform API token file (~/.vellum/platform-token).
229
+ * This token is the X-Session-Token used to authenticate with the Vellum
230
+ * Platform API (e.g. assistant.vellum.ai).
231
+ */
232
+ export function getPlatformTokenPath(): string {
233
+ return join(getRootDir(), 'platform-token');
234
+ }
235
+
236
+ /**
237
+ * Read the platform API token from disk. Returns null if the file
238
+ * doesn't exist or can't be read.
239
+ */
240
+ export function readPlatformToken(): string | null {
241
+ try {
242
+ return readFileSync(getPlatformTokenPath(), 'utf-8').trim();
243
+ } catch {
244
+ return null;
245
+ }
246
+ }
247
+
175
248
  /**
176
249
  * Read the daemon session token from disk. Returns null if the file
177
250
  * doesn't exist or can't be read (daemon not running).
@@ -267,276 +340,9 @@ export function getWorkspacePromptPath(file: string): string {
267
340
  return join(getWorkspaceDir(), file);
268
341
  }
269
342
 
270
- /**
271
- * Idempotent move: relocates source to destination for migration.
272
- * - No-op if source is missing (already migrated or never existed).
273
- * - No-op if destination already exists (avoids clobbering).
274
- * - Creates destination parent directories as needed.
275
- * - Logs warning on failure instead of throwing.
276
- *
277
- * Exported for testing; not intended for general use outside migrations.
278
- */
279
- export function migratePath(source: string, destination: string): void {
280
- if (!existsSync(source)) return;
281
- if (existsSync(destination)) {
282
- migrationLog('debug', 'Migration skipped: destination already exists', { source, destination });
283
- return;
284
- }
285
- try {
286
- const destDir = dirname(destination);
287
- if (!existsSync(destDir)) {
288
- mkdirSync(destDir, { recursive: true });
289
- }
290
- renameSync(source, destination);
291
- migrationLog('info', 'Migrated path', { from: source, to: destination });
292
- } catch (err) {
293
- migrationLog('warn', 'Failed to migrate path', { err: String(err), from: source, to: destination });
294
- }
295
- }
296
-
297
- /**
298
- * When migratePath skips config.json because the workspace copy already
299
- * exists, the legacy root config may still contain keys (e.g. slackWebhookUrl)
300
- * that were never written to the workspace config. This merges any missing
301
- * top-level keys from the legacy file into the workspace file so they are
302
- * not silently lost during upgrade.
303
- */
304
- function isPlainObject(v: unknown): v is Record<string, unknown> {
305
- return v !== null && typeof v === 'object' && !Array.isArray(v);
306
- }
307
-
308
- function mergeSkippedConfigKeys(legacyPath: string, workspacePath: string): void {
309
- if (!existsSync(legacyPath) || !existsSync(workspacePath)) return;
310
-
311
- let legacy: Record<string, unknown>;
312
- let workspace: Record<string, unknown>;
313
- try {
314
- const legacyRaw = JSON.parse(readFileSync(legacyPath, 'utf-8'));
315
- const workspaceRaw = JSON.parse(readFileSync(workspacePath, 'utf-8'));
316
- if (!isPlainObject(legacyRaw) || !isPlainObject(workspaceRaw)) return;
317
- legacy = legacyRaw;
318
- workspace = workspaceRaw;
319
- } catch {
320
- return; // malformed JSON — skip silently
321
- }
322
-
323
- const merged: string[] = [];
324
- for (const key of Object.keys(legacy)) {
325
- if (!(key in workspace)) {
326
- workspace[key] = legacy[key];
327
- merged.push(key);
328
- }
329
- }
330
-
331
- if (merged.length > 0) {
332
- try {
333
- writeFileSync(workspacePath, JSON.stringify(workspace, null, 2) + '\n');
334
- // Remove merged keys from legacy config so they are not resurrected
335
- // if a user later deletes them from the workspace config.
336
- for (const key of merged) {
337
- delete legacy[key];
338
- }
339
- if (Object.keys(legacy).length === 0) {
340
- unlinkSync(legacyPath);
341
- } else {
342
- writeFileSync(legacyPath, JSON.stringify(legacy, null, 2) + '\n');
343
- }
344
- migrationLog('info', 'Merged legacy config keys into workspace config', { keys: merged });
345
- } catch (err) {
346
- migrationLog('warn', 'Failed to merge legacy config keys', { err: String(err), keys: merged });
347
- }
348
- }
349
- }
350
-
351
- /**
352
- * When migratePath skips the hooks directory because the workspace copy
353
- * already exists (e.g. pre-created by ensureDataDir), the legacy hooks
354
- * directory may still contain individual hook files/subdirectories that
355
- * were never moved. This merges any missing entries from the legacy
356
- * path into the workspace hooks path so they are not silently lost.
357
- */
358
- function mergeLegacyHooks(legacyDir: string, workspaceDir: string): void {
359
- if (!existsSync(legacyDir) || !existsSync(workspaceDir)) return;
360
-
361
- let entries: import('node:fs').Dirent[];
362
- try {
363
- entries = readdirSync(legacyDir, { withFileTypes: true });
364
- } catch {
365
- return;
366
- }
367
-
368
- for (const entry of entries) {
369
- const src = join(legacyDir, entry.name);
370
- const dest = join(workspaceDir, entry.name);
371
- if (existsSync(dest)) {
372
- // config.json needs a merge rather than a skip — the legacy file may
373
- // contain hook enabled/settings entries that the workspace copy lacks.
374
- if (entry.name === 'config.json') {
375
- mergeHooksConfig(src, dest);
376
- }
377
- continue;
378
- }
379
- try {
380
- renameSync(src, dest);
381
- migrationLog('info', 'Merged legacy hook into workspace', { from: src, to: dest });
382
- } catch (err) {
383
- migrationLog('warn', 'Failed to merge legacy hook', { err: String(err), from: src, to: dest });
384
- }
385
- }
386
- }
387
-
388
- /**
389
- * Merge missing hook entries from a legacy hooks/config.json into the
390
- * workspace hooks/config.json. Only adds hooks that don't already exist
391
- * in the workspace config so user changes are never overwritten.
392
- */
393
- function mergeHooksConfig(legacyPath: string, workspacePath: string): void {
394
- let legacy: Record<string, unknown>;
395
- let workspace: Record<string, unknown>;
396
- try {
397
- const legacyRaw = JSON.parse(readFileSync(legacyPath, 'utf-8'));
398
- const workspaceRaw = JSON.parse(readFileSync(workspacePath, 'utf-8'));
399
- if (!isPlainObject(legacyRaw) || !isPlainObject(workspaceRaw)) return;
400
- legacy = legacyRaw;
401
- workspace = workspaceRaw;
402
- } catch {
403
- return;
404
- }
405
-
406
- const legacyHooks = legacy.hooks;
407
- const wsHooks = workspace.hooks;
408
- if (!isPlainObject(legacyHooks) || !isPlainObject(wsHooks)) return;
409
-
410
- const merged: string[] = [];
411
- for (const hookName of Object.keys(legacyHooks)) {
412
- if (!(hookName in wsHooks)) {
413
- wsHooks[hookName] = legacyHooks[hookName];
414
- merged.push(hookName);
415
- }
416
- }
417
-
418
- if (merged.length > 0) {
419
- try {
420
- writeFileSync(workspacePath, JSON.stringify(workspace, null, 2) + '\n');
421
- // Remove merged hooks from legacy config to prevent resurrection
422
- for (const hookName of merged) {
423
- delete legacyHooks[hookName];
424
- }
425
- if (Object.keys(legacyHooks).length === 0) {
426
- unlinkSync(legacyPath);
427
- } else {
428
- writeFileSync(legacyPath, JSON.stringify(legacy, null, 2) + '\n');
429
- }
430
- migrationLog('info', 'Merged legacy hooks config entries into workspace', { hooks: merged });
431
- } catch (err) {
432
- migrationLog('warn', 'Failed to merge legacy hooks config', { err: String(err), hooks: merged });
433
- }
434
- }
435
- }
436
-
437
- /**
438
- * When migratePath skips the skills directory because the workspace copy
439
- * already exists (e.g. pre-created by ensureDataDir), the legacy skills
440
- * directory may still contain individual skill subdirectories that were
441
- * never moved. This merges any missing skill subdirectories from the
442
- * legacy path into the workspace skills path so they are not stranded.
443
- */
444
- function mergeLegacySkills(legacyDir: string, workspaceDir: string): void {
445
- if (!existsSync(legacyDir) || !existsSync(workspaceDir)) return;
446
-
447
- let entries: import('node:fs').Dirent[];
448
- try {
449
- entries = readdirSync(legacyDir, { withFileTypes: true });
450
- } catch {
451
- return;
452
- }
453
-
454
- for (const entry of entries) {
455
- const src = join(legacyDir, entry.name);
456
- const dest = join(workspaceDir, entry.name);
457
- if (existsSync(dest)) continue; // already present in workspace
458
- try {
459
- renameSync(src, dest);
460
- migrationLog('info', 'Merged legacy skill into workspace', { from: src, to: dest });
461
- } catch (err) {
462
- migrationLog('warn', 'Failed to merge legacy skill', { err: String(err), from: src, to: dest });
463
- }
464
- }
465
- }
466
-
467
- /**
468
- * When migratePath skips the data directory because workspace/data already
469
- * exists (e.g. the user's project had a data/ folder that was extracted from
470
- * sandbox/fs), the legacy data directory may still contain internal state
471
- * subdirectories (db/, logs/, sandbox/, etc.) that need to be preserved.
472
- * This merges any missing entries from the legacy data path into workspace/data.
473
- */
474
- function mergeLegacyDataEntries(legacyDir: string, workspaceDir: string): void {
475
- if (!existsSync(legacyDir) || !existsSync(workspaceDir)) return;
476
-
477
- let entries: import('node:fs').Dirent[];
478
- try {
479
- entries = readdirSync(legacyDir, { withFileTypes: true });
480
- } catch {
481
- return;
482
- }
483
-
484
- for (const entry of entries) {
485
- const src = join(legacyDir, entry.name);
486
- const dest = join(workspaceDir, entry.name);
487
- if (existsSync(dest)) continue; // already present in workspace
488
- try {
489
- renameSync(src, dest);
490
- migrationLog('info', 'Merged legacy data entry into workspace', { from: src, to: dest });
491
- } catch (err) {
492
- migrationLog('warn', 'Failed to merge legacy data entry', { err: String(err), from: src, to: dest });
493
- }
494
- }
495
- }
496
-
497
- /**
498
- * Migrate from the flat ~/.vellum layout to the workspace-based layout.
499
- *
500
- * Step (a) is special: if the workspace dir doesn't exist yet but the old
501
- * sandbox working dir (data/sandbox/fs) does, its contents are "extracted"
502
- * to become the new workspace root via rename. All subsequent moves then
503
- * land inside that workspace directory.
504
- *
505
- * Idempotent: safe to call on every startup — already-migrated items are
506
- * skipped, and a second run is a no-op.
507
- */
508
- export function migrateToWorkspaceLayout(): void {
509
- const root = getRootDir();
510
- if (!existsSync(root)) return;
511
-
512
- const ws = getWorkspaceDir();
513
-
514
- // (a) Extract data/sandbox/fs -> workspace (only when workspace doesn't exist yet)
515
- if (!existsSync(ws)) {
516
- const sandboxFs = join(root, 'data', 'sandbox', 'fs');
517
- if (existsSync(sandboxFs)) {
518
- try {
519
- renameSync(sandboxFs, ws);
520
- migrationLog('info', 'Extracted sandbox/fs as workspace root', { from: sandboxFs, to: ws });
521
- } catch (err) {
522
- migrationLog('warn', 'Failed to extract sandbox/fs', { err: String(err), from: sandboxFs, to: ws });
523
- }
524
- }
525
- }
526
-
527
- // (b)-(h) Move legacy root-level items into workspace
528
- migratePath(join(root, 'config.json'), join(ws, 'config.json'));
529
- mergeSkippedConfigKeys(join(root, 'config.json'), join(ws, 'config.json'));
530
- migratePath(join(root, 'data'), join(ws, 'data'));
531
- mergeLegacyDataEntries(join(root, 'data'), join(ws, 'data'));
532
- migratePath(join(root, 'hooks'), join(ws, 'hooks'));
533
- mergeLegacyHooks(join(root, 'hooks'), join(ws, 'hooks'));
534
- migratePath(join(root, 'IDENTITY.md'), join(ws, 'IDENTITY.md'));
535
- migratePath(join(root, 'skills'), join(ws, 'skills'));
536
- mergeLegacySkills(join(root, 'skills'), join(ws, 'skills'));
537
- migratePath(join(root, 'SOUL.md'), join(ws, 'SOUL.md'));
538
- migratePath(join(root, 'USER.md'), join(ws, 'USER.md'));
539
- }
343
+ // Re-export migration functions so existing consumers don't break.
344
+ export { migratePath, migrateToWorkspaceLayout } from '../migrations/workspace-layout.js';
345
+ export { migrateToDataLayout } from '../migrations/data-layout.js';
540
346
 
541
347
  export function ensureDataDir(): void {
542
348
  const root = getRootDir();
@@ -573,67 +379,3 @@ export function ensureDataDir(): void {
573
379
  // Non-fatal: some filesystems don't support Unix permissions
574
380
  }
575
381
  }
576
-
577
- /**
578
- * Migrate files from the old flat ~/.vellum layout to the new structured
579
- * layout with data/ and protected/ subdirectories.
580
- *
581
- * Idempotent: skips items that have already been migrated.
582
- * Uses renameSync for atomic moves (same filesystem).
583
- */
584
- export function migrateToDataLayout(): void {
585
- const root = getRootDir();
586
- const data = join(root, 'data');
587
-
588
- if (!existsSync(root)) return;
589
-
590
- function migrateItem(oldPath: string, newPath: string): void {
591
- if (!existsSync(oldPath)) return;
592
- if (existsSync(newPath)) return;
593
- try {
594
- const newDir = dirname(newPath);
595
- if (!existsSync(newDir)) {
596
- mkdirSync(newDir, { recursive: true });
597
- }
598
- renameSync(oldPath, newPath);
599
- migrationLog('info', 'Migrated path', { from: oldPath, to: newPath });
600
- } catch (err) {
601
- migrationLog('warn', 'Failed to migrate path', { err: String(err), from: oldPath, to: newPath });
602
- }
603
- }
604
-
605
- // DB: ~/.vellum/data/assistant.db → ~/.vellum/data/db/assistant.db
606
- migrateItem(join(data, 'assistant.db'), join(data, 'db', 'assistant.db'));
607
- migrateItem(join(data, 'assistant.db-wal'), join(data, 'db', 'assistant.db-wal'));
608
- migrateItem(join(data, 'assistant.db-shm'), join(data, 'db', 'assistant.db-shm'));
609
-
610
- // Qdrant PID: ~/.vellum/qdrant.pid → ~/.vellum/data/qdrant/qdrant.pid
611
- migrateItem(join(root, 'qdrant.pid'), join(data, 'qdrant', 'qdrant.pid'));
612
-
613
- // Qdrant binary: ~/.vellum/bin/ → ~/.vellum/data/qdrant/bin/
614
- migrateItem(join(root, 'bin'), join(data, 'qdrant', 'bin'));
615
-
616
- // Logs: ~/.vellum/logs/ → ~/.vellum/data/logs/
617
- migrateItem(join(root, 'logs'), join(data, 'logs'));
618
-
619
- // Memory: ~/.vellum/memory/ → ~/.vellum/data/memory/
620
- migrateItem(join(root, 'memory'), join(data, 'memory'));
621
-
622
- // Apps: ~/.vellum/apps/ → ~/.vellum/data/apps/
623
- migrateItem(join(root, 'apps'), join(data, 'apps'));
624
-
625
- // Browser auth: ~/.vellum/browser-auth/ → ~/.vellum/data/browser-auth/
626
- migrateItem(join(root, 'browser-auth'), join(data, 'browser-auth'));
627
-
628
- // Browser profile: ~/.vellum/browser-profile/ → ~/.vellum/data/browser-profile/
629
- migrateItem(join(root, 'browser-profile'), join(data, 'browser-profile'));
630
-
631
- // History: ~/.vellum/history → ~/.vellum/data/history
632
- migrateItem(join(root, 'history'), join(data, 'history'));
633
-
634
- // Protected files: ~/.vellum/X → ~/.vellum/protected/X
635
- const protectedDir = join(root, 'protected');
636
- migrateItem(join(root, 'trust.json'), join(protectedDir, 'trust.json'));
637
- migrateItem(join(root, 'keys.enc'), join(protectedDir, 'keys.enc'));
638
- migrateItem(join(root, 'secret-allowlist.json'), join(protectedDir, 'secret-allowlist.json'));
639
- }
@@ -143,7 +143,7 @@ export function estimateCost(
143
143
  provider: string,
144
144
  ): number {
145
145
  const result = resolvePricing(provider, model, inputTokens, outputTokens);
146
- if (result.pricingStatus === 'priced' && result.estimatedCostUsd !== null) {
146
+ if (result.pricingStatus === 'priced' && result.estimatedCostUsd != null) {
147
147
  return result.estimatedCostUsd;
148
148
  }
149
149
  return 0;
@@ -8,7 +8,7 @@ export class PromiseGuard<T> {
8
8
 
9
9
  /** Whether a promise is currently in-flight. */
10
10
  get active(): boolean {
11
- return this.promise !== null;
11
+ return this.promise != null;
12
12
  }
13
13
 
14
14
  /**
package/src/util/retry.ts CHANGED
@@ -95,4 +95,23 @@ export function sleep(ms: number): Promise<void> {
95
95
  return new Promise((resolve) => setTimeout(resolve, ms));
96
96
  }
97
97
 
98
+ /**
99
+ * Like `sleep` but resolves early when an AbortSignal fires.
100
+ * Resolves (not rejects) on abort so callers can check the signal
101
+ * themselves and decide what to do.
102
+ */
103
+ export function abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {
104
+ if (!signal) return sleep(ms);
105
+ if (signal.aborted) return Promise.resolve();
106
+ return new Promise((resolve) => {
107
+ const timer = setTimeout(onDone, ms);
108
+ signal.addEventListener('abort', onDone, { once: true });
109
+ function onDone() {
110
+ clearTimeout(timer);
111
+ signal!.removeEventListener('abort', onDone);
112
+ resolve();
113
+ }
114
+ });
115
+ }
116
+
98
117
  export { DEFAULT_MAX_RETRIES, DEFAULT_BASE_DELAY_MS };
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Generic DB row mapper — replaces repetitive parse* functions across store files.
3
+ *
4
+ * Each field in the schema is described by either a source column name (passthrough)
5
+ * or a transform descriptor. The mapper produces a function that converts a raw
6
+ * Drizzle row into a typed domain object.
7
+ */
8
+
9
+ // A field descriptor is either a key of the source row (passthrough) or a transform.
10
+ type FieldDescriptor<TRow, TOut> =
11
+ | (keyof TRow & string)
12
+ | { from: keyof TRow & string; transform: (value: TRow[keyof TRow]) => TOut };
13
+
14
+ // The schema maps each output field to a field descriptor.
15
+ type MapperSchema<TRow, TDomain> = {
16
+ [K in keyof TDomain]: FieldDescriptor<TRow, TDomain[K]>;
17
+ };
18
+
19
+ /**
20
+ * Create a row-to-domain mapper from a declarative schema.
21
+ *
22
+ * Usage:
23
+ * ```ts
24
+ * const parseReminder = createRowMapper<typeof reminders.$inferSelect, ReminderRow>({
25
+ * id: 'id',
26
+ * label: 'label',
27
+ * mode: { from: 'mode', transform: (v) => v as ReminderRow['mode'] },
28
+ * });
29
+ * ```
30
+ */
31
+ export function createRowMapper<TRow, TDomain>(
32
+ schema: MapperSchema<TRow, TDomain>,
33
+ ): (row: TRow) => TDomain {
34
+ const entries = Object.entries(schema) as Array<
35
+ [string, FieldDescriptor<TRow, unknown>]
36
+ >;
37
+
38
+ return (row: TRow): TDomain => {
39
+ const result = {} as Record<string, unknown>;
40
+ for (const [key, descriptor] of entries) {
41
+ if (typeof descriptor === 'string') {
42
+ result[key] = row[descriptor as keyof TRow];
43
+ } else {
44
+ const d = descriptor as { from: keyof TRow & string; transform: (value: TRow[keyof TRow]) => unknown };
45
+ result[key] = d.transform(row[d.from]);
46
+ }
47
+ }
48
+ return result as TDomain;
49
+ };
50
+ }
51
+
52
+ /** Convenience: cast a value to a narrower type (for string union columns). */
53
+ export function cast<T>() {
54
+ return (value: unknown) => value as T;
55
+ }
56
+
57
+ /** Convenience: parse a JSON string column with a fallback value on parse failure. */
58
+ export function parseJson<T>(fallback: T): (value: unknown) => T {
59
+ return (value: unknown): T => {
60
+ if (typeof value !== 'string' || !value) return fallback;
61
+ try {
62
+ return JSON.parse(value) as T;
63
+ } catch {
64
+ return fallback;
65
+ }
66
+ };
67
+ }
68
+
69
+ /** Convenience: parse a JSON string column, returning null on parse failure. */
70
+ export function parseJsonNullable<T>(): (value: unknown) => T | null {
71
+ return (value: unknown): T | null => {
72
+ if (typeof value !== 'string' || !value) return null;
73
+ try {
74
+ return JSON.parse(value) as T;
75
+ } catch {
76
+ return null;
77
+ }
78
+ };
79
+ }
@@ -0,0 +1,21 @@
1
+ import { getLogger } from './logger.js';
2
+
3
+ const log = getLogger('silently');
4
+
5
+ /**
6
+ * Attaches a `.catch()` to `promise` that emits a debug-level log instead of
7
+ * swallowing the rejection completely. Use this in place of bare
8
+ * `.catch(() => {})` when you need fire-and-forget semantics but still want
9
+ * visibility into unexpected errors during debugging.
10
+ *
11
+ * The original promise is returned unchanged so callers can still chain it.
12
+ *
13
+ * @example
14
+ * silentlyWithLog(stopSession(id), 'idle session cleanup');
15
+ */
16
+ export function silentlyWithLog<T>(promise: Promise<T>, context: string): Promise<T> {
17
+ promise.catch((err: unknown) => {
18
+ log.debug({ err, context }, 'Suppressed async error');
19
+ });
20
+ return promise;
21
+ }