@vellumai/assistant 0.5.16 → 0.6.0

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 (407) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +0 -3
  3. package/knip.json +2 -1
  4. package/openapi.yaml +660 -80
  5. package/package.json +1 -1
  6. package/src/__tests__/actor-token-service.test.ts +68 -0
  7. package/src/__tests__/agent-loop.test.ts +0 -32
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  9. package/src/__tests__/anthropic-provider.test.ts +57 -3
  10. package/src/__tests__/app-compiler.test.ts +120 -0
  11. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  12. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  13. package/src/__tests__/call-domain.test.ts +2 -6
  14. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  15. package/src/__tests__/call-recovery.test.ts +2 -6
  16. package/src/__tests__/call-routes-http.test.ts +2 -6
  17. package/src/__tests__/call-store.test.ts +2 -6
  18. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  19. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  20. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  21. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  22. package/src/__tests__/checker.test.ts +25 -3
  23. package/src/__tests__/clawhub.test.ts +54 -24
  24. package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
  25. package/src/__tests__/cli-memory.test.ts +74 -69
  26. package/src/__tests__/config-schema.test.ts +1 -1
  27. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  28. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  29. package/src/__tests__/contacts-tools.test.ts +31 -0
  30. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  31. package/src/__tests__/context-token-estimator.test.ts +175 -10
  32. package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
  33. package/src/__tests__/conversation-agent-loop.test.ts +9 -0
  34. package/src/__tests__/conversation-attachments.test.ts +2 -6
  35. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  36. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  37. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  38. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  39. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  40. package/src/__tests__/conversation-error.test.ts +33 -2
  41. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  42. package/src/__tests__/conversation-history-web-search.test.ts +5 -0
  43. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  44. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  45. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  46. package/src/__tests__/conversation-store.test.ts +2 -6
  47. package/src/__tests__/conversation-usage.test.ts +2 -6
  48. package/src/__tests__/conversation-wipe.test.ts +11 -408
  49. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  50. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  51. package/src/__tests__/credential-security-e2e.test.ts +2 -0
  52. package/src/__tests__/followup-tools.test.ts +2 -6
  53. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  54. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  55. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  56. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  57. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  58. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  59. package/src/__tests__/guardian-action-store.test.ts +2 -6
  60. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  61. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  62. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  63. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  64. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  65. package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
  66. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  67. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  68. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  69. package/src/__tests__/injection-block.test.ts +154 -0
  70. package/src/__tests__/install-meta.test.ts +506 -0
  71. package/src/__tests__/install-skill-routing.test.ts +292 -0
  72. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  73. package/src/__tests__/invite-routes-http.test.ts +2 -6
  74. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
  75. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  76. package/src/__tests__/llm-context-route-provider.test.ts +2 -6
  77. package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
  78. package/src/__tests__/llm-usage-store.test.ts +2 -6
  79. package/src/__tests__/log-export-workspace.test.ts +2 -6
  80. package/src/__tests__/managed-store.test.ts +38 -11
  81. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  82. package/src/__tests__/memory-recall-log-store.test.ts +2 -6
  83. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  84. package/src/__tests__/non-member-access-request.test.ts +2 -6
  85. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  86. package/src/__tests__/oauth-cli.test.ts +364 -2
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  88. package/src/__tests__/outlook-attachments.test.ts +301 -0
  89. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  90. package/src/__tests__/outlook-categories.test.ts +212 -0
  91. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  92. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  93. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  94. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  95. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  96. package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
  97. package/src/__tests__/outlook-trash.test.ts +77 -0
  98. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  99. package/src/__tests__/platform-callback-registration.test.ts +4 -4
  100. package/src/__tests__/playbook-execution.test.ts +76 -80
  101. package/src/__tests__/playbook-tools.test.ts +5 -7
  102. package/src/__tests__/provider-error-scenarios.test.ts +21 -0
  103. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  104. package/src/__tests__/registry.test.ts +2 -2
  105. package/src/__tests__/require-fresh-approval.test.ts +64 -2
  106. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  107. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  108. package/src/__tests__/schedule-store.test.ts +2 -6
  109. package/src/__tests__/schedule-tools.test.ts +2 -6
  110. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  111. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  112. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  113. package/src/__tests__/search-skills-unified.test.ts +421 -0
  114. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  115. package/src/__tests__/send-endpoint-busy.test.ts +2 -6
  116. package/src/__tests__/sequence-store.test.ts +2 -6
  117. package/src/__tests__/server-history-render.test.ts +2 -6
  118. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  119. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  120. package/src/__tests__/skill-load-feature-flag.test.ts +11 -11
  121. package/src/__tests__/skill-memory.test.ts +140 -98
  122. package/src/__tests__/skills-uninstall.test.ts +2 -2
  123. package/src/__tests__/skills.test.ts +1 -1
  124. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  125. package/src/__tests__/task-compiler.test.ts +2 -6
  126. package/src/__tests__/task-management-tools.test.ts +2 -6
  127. package/src/__tests__/task-memory-cleanup.test.ts +173 -229
  128. package/src/__tests__/task-runner.test.ts +2 -6
  129. package/src/__tests__/task-scheduler.test.ts +2 -6
  130. package/src/__tests__/test-preload.ts +3 -0
  131. package/src/__tests__/tool-approval-handler.test.ts +2 -6
  132. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
  133. package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
  134. package/src/__tests__/trust-store.test.ts +1 -1
  135. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
  136. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -6
  137. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
  138. package/src/__tests__/trusted-contact-verification.test.ts +2 -6
  139. package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
  140. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
  141. package/src/__tests__/usage-routes.test.ts +2 -6
  142. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  143. package/src/__tests__/voice-invite-redemption.test.ts +2 -6
  144. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
  145. package/src/__tests__/voice-session-bridge.test.ts +2 -6
  146. package/src/__tests__/volume-security-guard.test.ts +2 -0
  147. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  148. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
  149. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
  150. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  151. package/src/__tests__/workspace-policy.test.ts +1 -1
  152. package/src/agent/attachments.ts +7 -2
  153. package/src/agent/image-optimize.ts +165 -0
  154. package/src/agent/loop.ts +1 -15
  155. package/src/bundler/app-compiler.ts +179 -2
  156. package/src/bundler/package-resolver.ts +3 -5
  157. package/src/cli/__tests__/notifications.test.ts +1 -2
  158. package/src/cli/cli-memory.ts +67 -64
  159. package/src/cli/commands/avatar.ts +3 -3
  160. package/src/cli/commands/config.ts +26 -13
  161. package/src/cli/commands/doctor.ts +2 -2
  162. package/src/cli/commands/memory.ts +41 -55
  163. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  164. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  165. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  166. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  167. package/src/cli/commands/oauth/connect.ts +11 -6
  168. package/src/cli/commands/oauth/mode.ts +7 -0
  169. package/src/cli/commands/oauth/shared.ts +39 -3
  170. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  171. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  172. package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
  173. package/src/cli/commands/platform/index.ts +16 -16
  174. package/src/cli/commands/skills.ts +88 -16
  175. package/src/cli/commands/trust.ts +2 -2
  176. package/src/cli/lib/daemon-credential-client.ts +2 -3
  177. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  178. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  179. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  180. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  181. package/src/config/bundled-skills/gmail/SKILL.md +2 -10
  182. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  183. package/src/config/bundled-skills/messaging/SKILL.md +10 -18
  184. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  185. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  186. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  187. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  188. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  189. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  190. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  191. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  192. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  193. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  194. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  195. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  196. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  197. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  198. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  199. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  200. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  201. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  202. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  203. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  204. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  205. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  206. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  207. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  208. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  209. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  210. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  211. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  212. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  213. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  214. package/src/config/bundled-skills/slack/SKILL.md +1 -7
  215. package/src/config/bundled-tool-registry.ts +56 -4
  216. package/src/config/env-registry.ts +15 -8
  217. package/src/config/feature-flag-registry.json +21 -124
  218. package/src/config/schemas/platform.ts +8 -0
  219. package/src/config/schemas/timeouts.ts +1 -1
  220. package/src/config/skills.ts +18 -7
  221. package/src/context/token-estimator.ts +25 -18
  222. package/src/context/window-manager.ts +6 -2
  223. package/src/credential-execution/process-manager.ts +3 -1
  224. package/src/daemon/context-overflow-reducer.ts +46 -2
  225. package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
  226. package/src/daemon/conversation-agent-loop.ts +96 -61
  227. package/src/daemon/conversation-error.ts +31 -8
  228. package/src/daemon/conversation-lifecycle.ts +33 -0
  229. package/src/daemon/conversation-media-retry.ts +85 -7
  230. package/src/daemon/conversation-notifiers.ts +4 -1
  231. package/src/daemon/conversation-runtime-assembly.ts +5 -0
  232. package/src/daemon/conversation.ts +41 -2
  233. package/src/daemon/daemon-control.ts +8 -2
  234. package/src/daemon/handlers/shared.ts +22 -12
  235. package/src/daemon/handlers/skills.ts +416 -202
  236. package/src/daemon/lifecycle.ts +40 -1
  237. package/src/daemon/main.ts +5 -1
  238. package/src/daemon/message-types/conversations.ts +4 -1
  239. package/src/daemon/message-types/messages.ts +3 -1
  240. package/src/daemon/message-types/skills.ts +97 -36
  241. package/src/daemon/providers-setup.ts +5 -0
  242. package/src/daemon/server.ts +11 -2
  243. package/src/daemon/tool-side-effects.ts +27 -5
  244. package/src/heartbeat/heartbeat-service.ts +1 -0
  245. package/src/hooks/cli.ts +2 -2
  246. package/src/hooks/runner.ts +15 -38
  247. package/src/inbound/platform-callback-registration.ts +14 -14
  248. package/src/memory/admin.ts +11 -45
  249. package/src/memory/conversation-bootstrap.ts +2 -0
  250. package/src/memory/conversation-crud.ts +242 -348
  251. package/src/memory/conversation-group-migration.ts +157 -0
  252. package/src/memory/conversation-queries.ts +4 -2
  253. package/src/memory/db-init.ts +30 -3
  254. package/src/memory/embed.ts +73 -0
  255. package/src/memory/embedding-backend.ts +8 -14
  256. package/src/memory/embedding-runtime-manager.ts +12 -114
  257. package/src/memory/fingerprint.ts +2 -2
  258. package/src/memory/graph/bootstrap.ts +512 -0
  259. package/src/memory/graph/capability-seed.ts +297 -0
  260. package/src/memory/graph/consolidation.ts +691 -0
  261. package/src/memory/graph/conversation-graph-memory.ts +630 -0
  262. package/src/memory/graph/decay.test.ts +208 -0
  263. package/src/memory/graph/decay.ts +195 -0
  264. package/src/memory/graph/extraction-job.ts +69 -0
  265. package/src/memory/graph/extraction.test.ts +936 -0
  266. package/src/memory/graph/extraction.ts +1254 -0
  267. package/src/memory/graph/graph-search.ts +266 -0
  268. package/src/memory/graph/image-ref-utils.ts +29 -0
  269. package/src/memory/graph/injection.test.ts +513 -0
  270. package/src/memory/graph/injection.ts +439 -0
  271. package/src/memory/graph/inspect.ts +534 -0
  272. package/src/memory/graph/narrative.ts +267 -0
  273. package/src/memory/graph/pattern-scan.ts +269 -0
  274. package/src/memory/graph/retriever.ts +1008 -0
  275. package/src/memory/graph/scoring.test.ts +548 -0
  276. package/src/memory/graph/scoring.ts +232 -0
  277. package/src/memory/graph/serendipity.ts +65 -0
  278. package/src/memory/graph/store.test.ts +1050 -0
  279. package/src/memory/graph/store.ts +699 -0
  280. package/src/memory/graph/tool-handlers.ts +426 -0
  281. package/src/memory/graph/tools.ts +141 -0
  282. package/src/memory/graph/triggers.test.ts +487 -0
  283. package/src/memory/graph/triggers.ts +223 -0
  284. package/src/memory/graph/types.ts +271 -0
  285. package/src/memory/group-crud.ts +191 -0
  286. package/src/memory/indexer.ts +37 -19
  287. package/src/memory/job-handlers/cleanup.ts +0 -53
  288. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  289. package/src/memory/job-handlers/embedding.ts +5 -31
  290. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  291. package/src/memory/job-handlers/summarization.ts +32 -17
  292. package/src/memory/job-utils.ts +1 -1
  293. package/src/memory/jobs-store.ts +50 -70
  294. package/src/memory/jobs-worker.ts +147 -112
  295. package/src/memory/message-content.ts +1 -0
  296. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  297. package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
  298. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  299. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  300. package/src/memory/migrations/index.ts +4 -0
  301. package/src/memory/migrations/registry.ts +8 -0
  302. package/src/memory/qdrant-client.ts +44 -17
  303. package/src/memory/schema/index.ts +1 -0
  304. package/src/memory/schema/memory-graph.ts +139 -0
  305. package/src/memory/search/semantic.ts +47 -91
  306. package/src/memory/task-memory-cleanup.ts +28 -50
  307. package/src/messaging/providers/outlook/adapter.ts +8 -1
  308. package/src/messaging/providers/outlook/client.ts +299 -0
  309. package/src/messaging/providers/outlook/types.ts +118 -0
  310. package/src/notifications/adapters/macos.ts +1 -0
  311. package/src/notifications/copy-composer.ts +9 -0
  312. package/src/notifications/signal.ts +16 -0
  313. package/src/oauth/seed-providers.ts +2 -1
  314. package/src/permissions/checker.ts +24 -3
  315. package/src/permissions/defaults.ts +4 -4
  316. package/src/permissions/workspace-policy.ts +1 -1
  317. package/src/playbooks/playbook-compiler.ts +19 -18
  318. package/src/playbooks/types.ts +4 -3
  319. package/src/prompts/system-prompt.ts +3 -29
  320. package/src/providers/anthropic/client.ts +47 -19
  321. package/src/providers/gemini/client.ts +1 -1
  322. package/src/providers/openai/client.ts +1 -1
  323. package/src/providers/registry.ts +1 -1
  324. package/src/providers/retry.ts +19 -3
  325. package/src/runtime/actor-trust-resolver.ts +5 -1
  326. package/src/runtime/auth/route-policy.ts +7 -0
  327. package/src/runtime/guardian-reply-router.ts +5 -1
  328. package/src/runtime/http-server.ts +23 -3
  329. package/src/runtime/middleware/auth.ts +20 -0
  330. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  331. package/src/runtime/routes/attachment-routes.ts +106 -16
  332. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  333. package/src/runtime/routes/btw-routes.ts +8 -0
  334. package/src/runtime/routes/conversation-management-routes.ts +2 -0
  335. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  336. package/src/runtime/routes/debug-routes.ts +1 -1
  337. package/src/runtime/routes/global-search-routes.ts +21 -19
  338. package/src/runtime/routes/group-routes.ts +207 -0
  339. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  340. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  341. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  342. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  343. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  344. package/src/runtime/routes/memory-item-routes.test.ts +2 -14
  345. package/src/runtime/routes/memory-item-routes.ts +341 -388
  346. package/src/runtime/routes/schedule-routes.ts +2 -0
  347. package/src/runtime/routes/skills-routes.ts +103 -37
  348. package/src/runtime/routes/work-items-routes.test.ts +2 -6
  349. package/src/schedule/scheduler.ts +8 -1
  350. package/src/security/oauth2.ts +1 -1
  351. package/src/security/secure-keys.ts +4 -8
  352. package/src/shared/provider-env-vars.ts +19 -0
  353. package/src/skills/catalog-cache.ts +5 -0
  354. package/src/skills/catalog-install.ts +15 -14
  355. package/src/skills/clawhub.ts +134 -154
  356. package/src/skills/install-meta.ts +208 -0
  357. package/src/skills/managed-store.ts +27 -16
  358. package/src/skills/skill-memory.ts +152 -77
  359. package/src/skills/skillssh-registry.ts +19 -17
  360. package/src/tasks/task-runner.ts +3 -1
  361. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  362. package/src/tools/browser/runtime-check.ts +3 -1
  363. package/src/tools/memory/register.ts +63 -46
  364. package/src/tools/permission-checker.ts +7 -1
  365. package/src/tools/shared/filesystem/image-read.ts +22 -85
  366. package/src/tools/terminal/safe-env.ts +1 -0
  367. package/src/tools/tool-manifest.ts +3 -3
  368. package/src/util/browser.ts +25 -10
  369. package/src/util/bun-runtime.ts +172 -0
  370. package/src/watcher/providers/outlook-calendar.ts +343 -0
  371. package/src/watcher/providers/outlook.ts +198 -0
  372. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  373. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  374. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  375. package/src/workspace/migrations/registry.ts +6 -0
  376. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  377. package/src/__tests__/journal-context.test.ts +0 -268
  378. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  379. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  380. package/src/__tests__/memory-query-builder.test.ts +0 -59
  381. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  382. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  383. package/src/__tests__/memory-regressions.test.ts +0 -3696
  384. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  385. package/src/daemon/conversation-memory.ts +0 -207
  386. package/src/memory/conversation-starters-cadence.ts +0 -74
  387. package/src/memory/items-extractor.ts +0 -860
  388. package/src/memory/job-handlers/batch-extraction.ts +0 -753
  389. package/src/memory/job-handlers/extraction.ts +0 -40
  390. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
  391. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  392. package/src/memory/journal-memory.ts +0 -224
  393. package/src/memory/query-builder.ts +0 -47
  394. package/src/memory/query-expansion.ts +0 -83
  395. package/src/memory/retriever.test.ts +0 -1592
  396. package/src/memory/retriever.ts +0 -1331
  397. package/src/memory/search/formatting.test.ts +0 -140
  398. package/src/memory/search/formatting.ts +0 -262
  399. package/src/memory/search/mmr.ts +0 -139
  400. package/src/memory/search/ranking.ts +0 -15
  401. package/src/memory/search/staleness.ts +0 -40
  402. package/src/memory/search/tier-classifier.ts +0 -18
  403. package/src/memory/search/types.ts +0 -121
  404. package/src/prompts/journal-context.ts +0 -154
  405. package/src/tools/memory/definitions.ts +0 -69
  406. package/src/tools/memory/handlers.test.ts +0 -562
  407. package/src/tools/memory/handlers.ts +0 -434
@@ -35,7 +35,7 @@ The avatar system supports two modes:
35
35
  2. Custom image — an externally provided image file placed directly in the
36
36
  avatar directory. Custom images are not managed through this CLI.
37
37
 
38
- Files are stored in ~/.vellum/workspace/data/avatar/:
38
+ Files are stored in $VELLUM_WORKSPACE_DIR/data/avatar/:
39
39
  character-traits.json Current trait selection (bodyShape, eyeStyle, color)
40
40
  avatar-image.png Rendered PNG of the character
41
41
  character-ascii.txt ASCII art representation (best-effort; may not be written)
@@ -61,7 +61,7 @@ Generates an avatar image using AI based on the provided text description
61
61
  and saves it as the assistant's avatar PNG. This replaces any existing
62
62
  native character avatar — the character traits and ASCII files are removed.
63
63
 
64
- On success, writes avatar-image.png to ~/.vellum/workspace/data/avatar/
64
+ On success, writes avatar-image.png to $VELLUM_WORKSPACE_DIR/data/avatar/
65
65
  and removes character-traits.json and character-ascii.txt if they exist.
66
66
 
67
67
  Examples:
@@ -155,7 +155,7 @@ The --color flag sets the body fill color. Valid values:
155
155
  green, orange, pink, purple, teal, yellow
156
156
 
157
157
  On success, writes character-traits.json and avatar-image.png to
158
- ~/.vellum/workspace/data/avatar/. character-ascii.txt is written on a
158
+ $VELLUM_WORKSPACE_DIR/data/avatar/. character-ascii.txt is written on a
159
159
  best-effort basis and may be skipped if ASCII rendering fails.
160
160
 
161
161
  Examples:
@@ -10,6 +10,7 @@ import {
10
10
  import { AssistantConfigSchema } from "../../config/schema.js";
11
11
  import { getSchemaAtPath } from "../../config/schema-utils.js";
12
12
  import { log } from "../logger.js";
13
+ import { requirePlatformConnection } from "./oauth/shared.js";
13
14
 
14
15
  /**
15
16
  * Flatten a nested config object into dotted key paths.
@@ -34,6 +35,9 @@ function flattenConfig(
34
35
  return result;
35
36
  }
36
37
 
38
+ /** Matches config paths like `services.inference.mode`, `services.image-generation.mode`, etc. */
39
+ const SERVICE_MODE_PATH_RE = /^services\.[^.]+\.mode$/;
40
+
37
41
  export function registerConfigCommand(program: Command): void {
38
42
  const config = program.command("config").description("Manage configuration");
39
43
 
@@ -80,19 +84,28 @@ Examples:
80
84
  $ assistant config set services.inference.provider anthropic
81
85
  $ assistant config set calls.enabled true`,
82
86
  )
83
- .action((key: string, value: string) => {
84
- const raw = loadRawConfig();
85
- // Try to parse as JSON for booleans/numbers, fall back to string
86
- let parsed: unknown = value;
87
- try {
88
- parsed = JSON.parse(value);
89
- } catch {
90
- // keep as string
91
- }
92
- setNestedValue(raw, key, parsed);
93
- saveRawConfig(raw);
94
- log.info(`Set ${key} = ${JSON.stringify(parsed)}`);
95
- });
87
+ .action(
88
+ async (key: string, value: string, _opts: unknown, cmd: Command) => {
89
+ // Try to parse as JSON for booleans/numbers, fall back to string
90
+ let parsed: unknown = value;
91
+ try {
92
+ parsed = JSON.parse(value);
93
+ } catch {
94
+ // keep as string
95
+ }
96
+
97
+ // Require platform connection when setting a service mode to "managed"
98
+ if (SERVICE_MODE_PATH_RE.test(key) && parsed === "managed") {
99
+ const connected = await requirePlatformConnection(cmd);
100
+ if (!connected) return;
101
+ }
102
+
103
+ const raw = loadRawConfig();
104
+ setNestedValue(raw, key, parsed);
105
+ saveRawConfig(raw);
106
+ log.info(`Set ${key} = ${JSON.stringify(parsed)}`);
107
+ },
108
+ );
96
109
 
97
110
  config
98
111
  .command("get <key>")
@@ -39,7 +39,7 @@ Diagnostic checks performed:
39
39
  2. API key configured Checks for a valid provider API key in secure storage
40
40
  3. Assistant reachable HTTP health check against the assistant server
41
41
  4. Database exists/readable Opens the SQLite database and runs a test query
42
- 5. Directory structure Verifies required ~/.vellum/ directories exist
42
+ 5. Directory structure Verifies required workspace directories exist
43
43
  6. Disk space Ensures at least 100MB free on the data partition
44
44
  7. Log file size Warns if the log file exceeds 50MB
45
45
  8. Database integrity Runs SQLite PRAGMA integrity_check
@@ -129,7 +129,7 @@ Examples:
129
129
  fail("Database exists and readable", `not found at ${dbPath}`);
130
130
  }
131
131
 
132
- // 5. ~/.vellum/ directory structure (workspace layout)
132
+ // 5. Workspace directory structure
133
133
  const protectedDir = getProtectedDir();
134
134
  const dataDir = getDataDir();
135
135
  const workspaceDir = getWorkspaceDir();
@@ -36,7 +36,7 @@ Key concepts:
36
36
  Examples:
37
37
  $ assistant memory status
38
38
  $ assistant memory query "What is the project deadline?"
39
- $ assistant memory backfill`,
39
+ $ assistant memory backfill`
40
40
  );
41
41
 
42
42
  memory
@@ -52,15 +52,13 @@ Fields shown:
52
52
  degraded mode (e.g. missing embedding backend)
53
53
  embedding backend The provider/model pair used for vector embeddings (or "none")
54
54
  segments Total conversation segments indexed
55
- items Total distilled fact items stored
55
+ graph nodes Total memory graph nodes stored
56
56
  summaries Total compressed conversation summaries
57
57
  embeddings Total vector embeddings computed
58
- cleanup backlogs Number of superseded items pending cleanup
59
- cleanup throughput Number of cleanup operations completed in the last 24 hours
60
58
  jobs Status of background jobs (backfill, cleanup, rebuild-index)
61
59
 
62
60
  Examples:
63
- $ assistant memory status`,
61
+ $ assistant memory status`
64
62
  )
65
63
  .action(async () => {
66
64
  initializeDb();
@@ -74,15 +72,9 @@ Examples:
74
72
  log.info("Embedding backend: none");
75
73
  }
76
74
  log.info(`Segments: ${status.counts.segments.toLocaleString()}`);
77
- log.info(`Items: ${status.counts.items.toLocaleString()}`);
75
+ log.info(`Graph nodes: ${status.counts.graphNodes.toLocaleString()}`);
78
76
  log.info(`Summaries: ${status.counts.summaries.toLocaleString()}`);
79
77
  log.info(`Embeddings: ${status.counts.embeddings.toLocaleString()}`);
80
- log.info(
81
- `Cleanup backlog (superseded items): ${status.cleanup.supersededBacklog.toLocaleString()}`,
82
- );
83
- log.info(
84
- `Cleanup throughput 24h (superseded items): ${status.cleanup.supersededCompleted24h.toLocaleString()}`,
85
- );
86
78
  log.info("Jobs:");
87
79
  for (const [key, value] of Object.entries(status.jobs)) {
88
80
  log.info(` ${key}: ${value}`);
@@ -106,7 +98,7 @@ useful after bulk imports or if the incremental state has become inconsistent.
106
98
 
107
99
  Examples:
108
100
  $ assistant memory backfill
109
- $ assistant memory backfill --force`,
101
+ $ assistant memory backfill --force`
110
102
  )
111
103
  .action((opts: { force?: boolean }) => {
112
104
  initializeDb();
@@ -119,7 +111,7 @@ Examples:
119
111
  .description("Queue cleanup jobs for stale superseded items")
120
112
  .option(
121
113
  "--retention-ms <ms>",
122
- "Optional retention threshold in milliseconds",
114
+ "Optional retention threshold in milliseconds"
123
115
  )
124
116
  .addHelpText(
125
117
  "after",
@@ -133,19 +125,17 @@ default retention period is used.
133
125
 
134
126
  Examples:
135
127
  $ assistant memory cleanup
136
- $ assistant memory cleanup --retention-ms 86400000`,
128
+ $ assistant memory cleanup --retention-ms 86400000`
137
129
  )
138
130
  .action((opts: { retentionMs?: string }) => {
139
131
  initializeDb();
140
132
  const retentionMs = opts.retentionMs
141
133
  ? Number.parseInt(opts.retentionMs, 10)
142
134
  : undefined;
143
- const jobs = requestMemoryCleanup(
144
- Number.isFinite(retentionMs) ? retentionMs : undefined,
145
- );
146
- log.info(
147
- `Queued cleanup_stale_superseded_items job: ${jobs.staleSupersededItemsJobId}`,
135
+ requestMemoryCleanup(
136
+ Number.isFinite(retentionMs) ? retentionMs : undefined
148
137
  );
138
+ log.info("Memory cleanup requested (legacy items table dropped)");
149
139
  });
150
140
 
151
141
  memory
@@ -156,7 +146,7 @@ Examples:
156
146
  "after",
157
147
  `
158
148
  Removes segments shorter than the minimum character threshold from both
159
- SQLite and Qdrant. Short fragments (e.g. "Collar Touched") burn embedding
149
+ SQLite and Qdrant. Short fragments (e.g. "OK sounds good") burn embedding
160
150
  budget, retrieval slots, and injection tokens without adding value.
161
151
 
162
152
  New segments are already filtered at creation time. This command cleans up
@@ -164,19 +154,21 @@ existing short segments that were stored before the filter was added.
164
154
 
165
155
  Examples:
166
156
  $ assistant memory cleanup-segments
167
- $ assistant memory cleanup-segments --dry-run`,
157
+ $ assistant memory cleanup-segments --dry-run`
168
158
  )
169
159
  .action(async (opts: { dryRun?: boolean }) => {
170
160
  initializeDb();
171
161
  const result = await cleanupShortSegments({ dryRun: opts.dryRun });
172
162
  if (opts.dryRun) {
173
163
  log.info(
174
- `Dry run: ${result.dryRunCount} short segment(s) would be removed.`,
164
+ `Dry run: ${result.dryRunCount} short segment(s) would be removed.`
175
165
  );
176
166
  } else {
177
167
  log.info(`Removed ${result.removed} short segment(s).`);
178
168
  if (result.failed > 0) {
179
- log.warn(`${result.failed} segment(s) skipped — Qdrant deletion failed. Re-run when Qdrant is available.`);
169
+ log.warn(
170
+ `${result.failed} segment(s) skipped — Qdrant deletion failed. Re-run when Qdrant is available.`
171
+ );
180
172
  }
181
173
  }
182
174
  });
@@ -184,7 +176,7 @@ Examples:
184
176
  memory
185
177
  .command("query <text>")
186
178
  .description(
187
- "Run a memory recall query and print the injected memory payload",
179
+ "Run a memory recall query and print the injected memory payload"
188
180
  )
189
181
  .option("-c, --conversation <id>", "Optional conversation ID")
190
182
  .addHelpText(
@@ -205,7 +197,7 @@ context-aware recall. If omitted, the most recent conversation is used.
205
197
  Examples:
206
198
  $ assistant memory query "What is the project deadline?"
207
199
  $ assistant memory query "preferred communication style" --conversation conv_abc123
208
- $ assistant memory query "API rate limits"`,
200
+ $ assistant memory query "API rate limits"`
209
201
  )
210
202
  .action(async (text: string, opts?: { conversation?: string }) => {
211
203
  initializeDb();
@@ -215,18 +207,21 @@ Examples:
215
207
  conversationId = latest?.id ?? "";
216
208
  }
217
209
  const result = await queryMemory(text, conversationId ?? "");
218
- if (result.degraded) {
219
- log.info(`Memory degraded: ${result.reason ?? "unknown reason"}`);
220
- }
221
- log.info(`Semantic hits: ${result.semanticHits}`);
222
- log.info(`Merged count: ${result.mergedCount}`);
223
- log.info(`Injected tokens: ${result.injectedTokens}`);
224
- log.info(`Latency: ${result.latencyMs}ms`);
225
- if (result.injectedText.length > 0) {
210
+ log.info(`Results: ${result.results.length}`);
211
+ log.info(`Mode: ${result.mode}`);
212
+ if (result.results.length > 0) {
226
213
  log.info("");
227
- log.info(result.injectedText);
214
+ for (const r of result.results) {
215
+ log.info(
216
+ `[${r.type}] (confidence: ${r.confidence.toFixed(
217
+ 2
218
+ )}, score: ${r.score.toFixed(3)})`
219
+ );
220
+ log.info(r.content);
221
+ log.info("");
222
+ }
228
223
  } else {
229
- log.info("No memory injected.");
224
+ log.info("No results found.");
230
225
  }
231
226
  });
232
227
 
@@ -246,7 +241,7 @@ status" to monitor job progress.
246
241
 
247
242
  Examples:
248
243
  $ assistant memory rebuild-index
249
- $ assistant memory status`,
244
+ $ assistant memory status`
250
245
  )
251
246
  .action(() => {
252
247
  initializeDb();
@@ -257,18 +252,15 @@ Examples:
257
252
  memory
258
253
  .command("re-extract")
259
254
  .description(
260
- "Re-extract memories from conversations using the latest extraction prompt",
255
+ "Re-extract memories from conversations using the latest extraction prompt"
261
256
  )
262
257
  .option(
263
258
  "-c, --conversation <id>",
264
259
  "Target a specific conversation by ID (repeatable)",
265
260
  (val: string, prev: string[]) => [...prev, val],
266
- [] as string[],
267
- )
268
- .option(
269
- "-t, --top <n>",
270
- "Auto-select top N conversations by message count",
261
+ [] as string[]
271
262
  )
263
+ .option("-t, --top <n>", "Auto-select top N conversations by message count")
272
264
  .option("--dry-run", "Show what would be re-extracted without doing it")
273
265
  .addHelpText(
274
266
  "after",
@@ -289,14 +281,10 @@ background worker).
289
281
  Examples:
290
282
  $ assistant memory re-extract --top 20
291
283
  $ assistant memory re-extract --conversation conv_abc123
292
- $ assistant memory re-extract --top 10 --dry-run`,
284
+ $ assistant memory re-extract --top 10 --dry-run`
293
285
  )
294
286
  .action(
295
- (opts: {
296
- conversation?: string[];
297
- top?: string;
298
- dryRun?: boolean;
299
- }) => {
287
+ (opts: { conversation?: string[]; top?: string; dryRun?: boolean }) => {
300
288
  initializeDb();
301
289
 
302
290
  const targets = [];
@@ -333,7 +321,7 @@ Examples:
333
321
 
334
322
  if (targets.length === 0) {
335
323
  log.info(
336
- "No targets specified. Use --conversation <id> or --top <n>.",
324
+ "No targets specified. Use --conversation <id> or --top <n>."
337
325
  );
338
326
  return;
339
327
  }
@@ -342,9 +330,7 @@ Examples:
342
330
  log.info(`\nRe-extraction targets (${targets.length}):`);
343
331
  for (const t of targets) {
344
332
  const title = t.title ?? "(untitled)";
345
- log.info(
346
- ` ${t.conversationId} ${t.messageCount} msgs "${title}"`,
347
- );
333
+ log.info(` ${t.conversationId} ${t.messageCount} msgs "${title}"`);
348
334
  }
349
335
 
350
336
  if (opts.dryRun) {
@@ -354,8 +340,8 @@ Examples:
354
340
 
355
341
  const { jobIds } = requestReextract(targets);
356
342
  log.info(
357
- `\nQueued ${jobIds.length} re-extraction job(s). The assistant will process them in the background.`,
343
+ `\nQueued ${jobIds.length} re-extraction job(s). The assistant will process them in the background.`
358
344
  );
359
- },
345
+ }
360
346
  );
361
347
  }
@@ -88,7 +88,7 @@ mock.module("../../../../platform/client.js", () => ({
88
88
  }));
89
89
 
90
90
  mock.module("../../../../util/browser.js", () => ({
91
- openInBrowser: (url: string) => {
91
+ openInHostBrowser: async (url: string) => {
92
92
  mockOpenInBrowserCalls.push(url);
93
93
  },
94
94
  }));
@@ -127,7 +127,7 @@ mock.module("../shared.js", () => ({
127
127
  JSON.stringify({
128
128
  ok: false,
129
129
  error:
130
- "Platform prerequisites not met (not logged in or missing assistant ID)",
130
+ "Not connected to Vellum platform. Run `vellum platform connect` to connect first.",
131
131
  }) + "\n",
132
132
  );
133
133
  return null;
@@ -112,7 +112,7 @@ mock.module("../../../../platform/client.js", () => ({
112
112
  }));
113
113
 
114
114
  mock.module("../../../../util/browser.js", () => ({
115
- openInBrowser: () => {},
115
+ openInHostBrowser: async () => {},
116
116
  }));
117
117
 
118
118
  mock.module("../../../../util/logger.js", () => ({
@@ -162,7 +162,7 @@ mock.module("../shared.js", () => ({
162
162
  JSON.stringify({
163
163
  ok: false,
164
164
  error:
165
- "Platform prerequisites not met (not logged in or missing assistant ID)",
165
+ "Not connected to Vellum platform. Run `vellum platform connect` to connect first.",
166
166
  }) + "\n",
167
167
  );
168
168
  return null;
@@ -34,6 +34,10 @@ let mockSetNestedValueCalls: Array<{
34
34
 
35
35
  let mockConfigServices: Record<string, unknown> = {};
36
36
 
37
+ let mockRequirePlatformConnection: (
38
+ cmd: unknown,
39
+ ) => Promise<boolean> = async () => true;
40
+
37
41
  // ---------------------------------------------------------------------------
38
42
  // Mocks
39
43
  // ---------------------------------------------------------------------------
@@ -120,6 +124,8 @@ mock.module("../shared.js", () => ({
120
124
  isManagedMode: () => false,
121
125
  getManagedServiceConfigKey: (key: string) =>
122
126
  mockGetManagedServiceConfigKey(key),
127
+ requirePlatformConnection: (cmd: unknown) =>
128
+ mockRequirePlatformConnection(cmd),
123
129
  requirePlatformClient: async (_cmd: Command) => {
124
130
  if (
125
131
  !mockPlatformClientResult ||
@@ -130,7 +136,7 @@ mock.module("../shared.js", () => ({
130
136
  JSON.stringify({
131
137
  ok: false,
132
138
  error:
133
- "Platform prerequisites not met (not logged in or missing assistant ID)",
139
+ "Not connected to Vellum platform. Run `vellum platform connect` to connect first.",
134
140
  }) + "\n",
135
141
  );
136
142
  return null;
@@ -244,6 +250,7 @@ describe("assistant oauth mode", () => {
244
250
  mockSaveRawConfigCalls = [];
245
251
  mockSetNestedValueCalls = [];
246
252
  mockConfigServices = {};
253
+ mockRequirePlatformConnection = async () => true;
247
254
  process.exitCode = 0;
248
255
  });
249
256
 
@@ -71,7 +71,7 @@ mock.module("../../../../platform/client.js", () => ({
71
71
  }));
72
72
 
73
73
  mock.module("../../../../util/browser.js", () => ({
74
- openInBrowser: () => {},
74
+ openInHostBrowser: async () => {},
75
75
  }));
76
76
 
77
77
  mock.module("../../../../util/logger.js", () => ({
@@ -107,7 +107,7 @@ mock.module("../shared.js", () => ({
107
107
  JSON.stringify({
108
108
  ok: false,
109
109
  error:
110
- "Platform prerequisites not met (not logged in or missing assistant ID)",
110
+ "Not connected to Vellum platform. Run `vellum platform connect` to connect first.",
111
111
  }) + "\n",
112
112
  );
113
113
  return null;
@@ -9,7 +9,7 @@ import {
9
9
  getProvider,
10
10
  } from "../../../oauth/oauth-store.js";
11
11
  import { renderOAuthCompletionPage } from "../../../security/oauth-completion-page.js";
12
- import { openInBrowser } from "../../../util/browser.js";
12
+ import { openInHostBrowser } from "../../../util/browser.js";
13
13
  import { getSecureKeyViaDaemon } from "../../lib/daemon-credential-client.js";
14
14
  import { getCliLogger } from "../../logger.js";
15
15
  import { shouldOutputJson, writeOutput } from "../../output.js";
@@ -199,9 +199,14 @@ Examples:
199
199
 
200
200
  if (!response.ok) {
201
201
  const errorText = await response.text().catch(() => "");
202
- writeError(
203
- `Platform returned HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`,
204
- );
202
+ const baseMsg = `Platform returned HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`;
203
+ if (response.status === 401 || response.status === 403) {
204
+ writeError(
205
+ `${baseMsg}. Your platform session may have expired. Run \`vellum platform connect\` to reconnect.`,
206
+ );
207
+ } else {
208
+ writeError(baseMsg);
209
+ }
205
210
  return;
206
211
  }
207
212
 
@@ -229,7 +234,7 @@ Examples:
229
234
  }
230
235
  const snapshotIds = new Set(snapshotEntries.map((e) => e.id));
231
236
 
232
- openInBrowser(result.connect_url);
237
+ await openInHostBrowser(result.connect_url);
233
238
 
234
239
  if (!jsonMode) {
235
240
  log.info(
@@ -374,7 +379,7 @@ Examples:
374
379
  clientSecret,
375
380
  callbackTransport: opts.callbackTransport,
376
381
  isInteractive: opts.browser !== false,
377
- openUrl: opts.browser !== false ? openInBrowser : undefined,
382
+ openUrl: opts.browser !== false ? openInHostBrowser : undefined,
378
383
  ...(opts.scopes ? { requestedScopes: opts.scopes } : {}),
379
384
  });
380
385
 
@@ -17,6 +17,7 @@ import { shouldOutputJson, writeOutput } from "../../output.js";
17
17
  import {
18
18
  fetchActiveConnections,
19
19
  getManagedServiceConfigKey,
20
+ requirePlatformConnection,
20
21
  } from "./shared.js";
21
22
 
22
23
  /**
@@ -180,6 +181,12 @@ Examples:
180
181
  return;
181
182
  }
182
183
 
184
+ // Require platform connection when switching to managed mode
185
+ if (newMode === "managed") {
186
+ const connected = await requirePlatformConnection(cmd);
187
+ if (!connected) return;
188
+ }
189
+
183
190
  // Read current mode
184
191
  const services: Services = getConfig().services;
185
192
  const currentMode = services[managedKey as keyof Services].mode;
@@ -59,11 +59,20 @@ export async function requirePlatformClient(
59
59
  cmd: Command,
60
60
  ): Promise<VellumPlatformClient | null> {
61
61
  const client = await VellumPlatformClient.create();
62
- if (!client || !client.platformAssistantId) {
62
+ if (!client) {
63
63
  writeOutput(cmd, {
64
64
  ok: false,
65
65
  error:
66
- "Platform prerequisites not met (not logged in or missing assistant ID)",
66
+ "Not connected to Vellum platform. Run `vellum platform connect` to connect first.",
67
+ });
68
+ process.exitCode = 1;
69
+ return null;
70
+ }
71
+ if (!client.platformAssistantId) {
72
+ writeOutput(cmd, {
73
+ ok: false,
74
+ error:
75
+ "Connected to Vellum platform but no assistant ID is configured. Ensure the assistant is registered on the platform.",
67
76
  });
68
77
  process.exitCode = 1;
69
78
  return null;
@@ -71,6 +80,29 @@ export async function requirePlatformClient(
71
80
  return client;
72
81
  }
73
82
 
83
+ /**
84
+ * Verify that the user has connected to the Vellum platform (has stored
85
+ * credentials). Unlike `requirePlatformClient`, this does NOT require a
86
+ * platform assistant ID — it only checks that credentials exist.
87
+ *
88
+ * Writes an error and sets exitCode=1 when the user is not connected.
89
+ */
90
+ export async function requirePlatformConnection(
91
+ cmd: Command,
92
+ ): Promise<boolean> {
93
+ const client = await VellumPlatformClient.create();
94
+ if (!client) {
95
+ writeOutput(cmd, {
96
+ ok: false,
97
+ error:
98
+ "Not connected to Vellum platform. Run `vellum platform connect` to connect first.",
99
+ });
100
+ process.exitCode = 1;
101
+ return false;
102
+ }
103
+ return true;
104
+ }
105
+
74
106
  /**
75
107
  * Fetch active platform connections for a provider. Returns the parsed entries
76
108
  * or writes an error and returns null.
@@ -94,9 +126,13 @@ export async function fetchActiveConnections(
94
126
 
95
127
  if (!response.ok) {
96
128
  if (!options?.silent) {
129
+ const hint =
130
+ response.status === 401 || response.status === 403
131
+ ? `. Your platform session may have expired. Run \`vellum platform connect\` to reconnect.`
132
+ : "";
97
133
  writeOutput(cmd, {
98
134
  ok: false,
99
- error: `Platform returned HTTP ${response.status}`,
135
+ error: `Platform returned HTTP ${response.status}${hint}`,
100
136
  });
101
137
  process.exitCode = 1;
102
138
  }
@@ -30,7 +30,7 @@ mock.module("../../../lib/daemon-credential-client.js", () => ({
30
30
 
31
31
  mock.module("../../../../inbound/platform-callback-registration.js", () => ({
32
32
  resolvePlatformCallbackRegistrationContext: async () => ({
33
- containerized: false,
33
+ isPlatform: false,
34
34
  platformBaseUrl: "",
35
35
  assistantId: "",
36
36
  hasInternalApiKey: false,
@@ -36,7 +36,7 @@ mock.module("../../../../config/env-registry.js", () => ({
36
36
 
37
37
  mock.module("../../../../inbound/platform-callback-registration.js", () => ({
38
38
  resolvePlatformCallbackRegistrationContext: async () => ({
39
- containerized: false,
39
+ isPlatform: false,
40
40
  platformBaseUrl: "",
41
41
  assistantId: "",
42
42
  hasInternalApiKey: false,
@@ -11,7 +11,7 @@ let mockGetSecureKeyViaDaemon: (
11
11
  let mockResolvePlatformCallbackRegistrationContext: () => Promise<
12
12
  Record<string, unknown>
13
13
  > = async () => ({
14
- containerized: false,
14
+ isPlatform: false,
15
15
  platformBaseUrl: "",
16
16
  assistantId: "",
17
17
  hasInternalApiKey: false,
@@ -138,7 +138,7 @@ describe("assistant platform status", () => {
138
138
  beforeEach(() => {
139
139
  mockGetSecureKeyViaDaemon = async () => undefined;
140
140
  mockResolvePlatformCallbackRegistrationContext = async () => ({
141
- containerized: false,
141
+ isPlatform: false,
142
142
  platformBaseUrl: "",
143
143
  assistantId: "",
144
144
  hasInternalApiKey: false,
@@ -158,7 +158,7 @@ describe("assistant platform status", () => {
158
158
 
159
159
  // GIVEN a containerized environment with platform configuration
160
160
  mockResolvePlatformCallbackRegistrationContext = async () => ({
161
- containerized: true,
161
+ isPlatform: true,
162
162
  platformBaseUrl: "https://platform.vellum.ai",
163
163
  assistantId: "asst-abc-123",
164
164
  hasInternalApiKey: true,
@@ -191,7 +191,7 @@ describe("assistant platform status", () => {
191
191
 
192
192
  // AND the output contains the expected status fields
193
193
  const parsed = JSON.parse(stdout);
194
- expect(parsed.containerized).toBe(true);
194
+ expect(parsed.isPlatform).toBe(true);
195
195
  expect(parsed.baseUrl).toBe("https://platform.vellum.ai");
196
196
  expect(parsed.assistantId).toBe("asst-abc-123");
197
197
  expect(parsed.hasInternalApiKey).toBe(true);
@@ -211,7 +211,7 @@ describe("assistant platform status", () => {
211
211
 
212
212
  // GIVEN a disconnected environment with no stored credentials
213
213
  mockResolvePlatformCallbackRegistrationContext = async () => ({
214
- containerized: false,
214
+ isPlatform: false,
215
215
  platformBaseUrl: "",
216
216
  assistantId: "",
217
217
  hasInternalApiKey: false,