@vellumai/assistant 0.5.15 → 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 (503) hide show
  1. package/ARCHITECTURE.md +3 -3
  2. package/Dockerfile +0 -3
  3. package/docs/architecture/integrations.md +15 -14
  4. package/knip.json +4 -1
  5. package/openapi.yaml +670 -122
  6. package/package.json +1 -1
  7. package/src/__tests__/actor-token-service.test.ts +68 -0
  8. package/src/__tests__/agent-loop.test.ts +0 -32
  9. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  10. package/src/__tests__/anthropic-provider.test.ts +57 -3
  11. package/src/__tests__/app-compiler.test.ts +120 -0
  12. package/src/__tests__/assistant-feature-flags-integration.test.ts +5 -377
  13. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  14. package/src/__tests__/call-domain.test.ts +2 -6
  15. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  16. package/src/__tests__/call-recovery.test.ts +2 -6
  17. package/src/__tests__/call-routes-http.test.ts +2 -6
  18. package/src/__tests__/call-store.test.ts +2 -6
  19. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  20. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  21. package/src/__tests__/ces-rpc-credential-backend.test.ts +4 -1
  22. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  23. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  24. package/src/__tests__/checker.test.ts +84 -3
  25. package/src/__tests__/clawhub.test.ts +54 -24
  26. package/src/__tests__/cli-command-risk-guard.test.ts +108 -6
  27. package/src/__tests__/cli-memory.test.ts +377 -0
  28. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +12 -2
  29. package/src/__tests__/config-schema.test.ts +1 -3
  30. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  31. package/src/__tests__/config-watcher-feature-flags.test.ts +211 -0
  32. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  33. package/src/__tests__/contacts-tools.test.ts +31 -0
  34. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  35. package/src/__tests__/context-token-estimator.test.ts +175 -10
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
  37. package/src/__tests__/conversation-agent-loop.test.ts +9 -0
  38. package/src/__tests__/conversation-attachments.test.ts +2 -6
  39. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  40. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  41. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  42. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  43. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  44. package/src/__tests__/conversation-error.test.ts +33 -2
  45. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  46. package/src/__tests__/conversation-history-web-search.test.ts +5 -0
  47. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  48. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  49. package/src/__tests__/conversation-runtime-assembly.test.ts +7 -4
  50. package/src/__tests__/conversation-slash-commands.test.ts +2 -6
  51. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  52. package/src/__tests__/conversation-store.test.ts +2 -6
  53. package/src/__tests__/conversation-usage.test.ts +3 -6
  54. package/src/__tests__/conversation-wipe.test.ts +11 -408
  55. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  56. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  57. package/src/__tests__/credential-security-e2e.test.ts +6 -1
  58. package/src/__tests__/docker-signing-key-bootstrap.test.ts +7 -73
  59. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -7
  60. package/src/__tests__/followup-tools.test.ts +2 -6
  61. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  62. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  63. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  64. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  65. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  66. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  67. package/src/__tests__/guardian-action-store.test.ts +2 -6
  68. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  69. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  70. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  71. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  72. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  73. package/src/__tests__/guardian-routing-invariants.test.ts +343 -6
  74. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  75. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  76. package/src/__tests__/heartbeat-service.test.ts +1 -3
  77. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  78. package/src/__tests__/injection-block.test.ts +154 -0
  79. package/src/__tests__/install-meta.test.ts +506 -0
  80. package/src/__tests__/install-skill-routing.test.ts +292 -0
  81. package/src/__tests__/intent-routing.test.ts +6 -18
  82. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  83. package/src/__tests__/invite-routes-http.test.ts +2 -6
  84. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
  85. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  86. package/src/__tests__/llm-context-route-provider.test.ts +2 -6
  87. package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
  88. package/src/__tests__/llm-usage-store.test.ts +2 -6
  89. package/src/__tests__/log-export-workspace.test.ts +4 -34
  90. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -37
  91. package/src/__tests__/managed-store.test.ts +40 -21
  92. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  93. package/src/__tests__/memory-recall-log-store.test.ts +2 -6
  94. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  95. package/src/__tests__/messaging-send-tool.test.ts +6 -6
  96. package/src/__tests__/migration-cross-version-compatibility.test.ts +1 -29
  97. package/src/__tests__/migration-export-http.test.ts +3 -34
  98. package/src/__tests__/migration-import-commit-http.test.ts +1 -29
  99. package/src/__tests__/migration-import-preflight-http.test.ts +3 -34
  100. package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +2 -1
  101. package/src/__tests__/non-member-access-request.test.ts +2 -6
  102. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  103. package/src/__tests__/oauth-apps-routes.test.ts +120 -10
  104. package/src/__tests__/oauth-cli.test.ts +364 -2
  105. package/src/__tests__/oauth-connect-orchestrator.test.ts +709 -0
  106. package/src/__tests__/oauth-provider-serializer.test.ts +2 -1
  107. package/src/__tests__/oauth-provider-visibility.test.ts +149 -0
  108. package/src/__tests__/oauth-providers-routes.test.ts +5 -2
  109. package/src/__tests__/oauth-store.test.ts +0 -5
  110. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  111. package/src/__tests__/outlook-attachments.test.ts +301 -0
  112. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  113. package/src/__tests__/outlook-categories.test.ts +212 -0
  114. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  115. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  116. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  117. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  118. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  119. package/src/__tests__/outlook-messaging-provider.test.ts +1071 -0
  120. package/src/__tests__/outlook-trash.test.ts +77 -0
  121. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  122. package/src/__tests__/path-policy.test.ts +2 -17
  123. package/src/__tests__/permission-types.test.ts +0 -1
  124. package/src/__tests__/platform-callback-registration.test.ts +7 -11
  125. package/src/__tests__/playbook-execution.test.ts +76 -80
  126. package/src/__tests__/playbook-tools.test.ts +5 -7
  127. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  128. package/src/__tests__/provider-error-scenarios.test.ts +21 -2
  129. package/src/__tests__/qdrant-manager.test.ts +68 -21
  130. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  131. package/src/__tests__/registry.test.ts +2 -2
  132. package/src/__tests__/require-fresh-approval.test.ts +64 -3
  133. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  134. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  135. package/src/__tests__/sandbox-diagnostics.test.ts +20 -29
  136. package/src/__tests__/scaffold-managed-skill-tool.test.ts +2 -10
  137. package/src/__tests__/schedule-store.test.ts +2 -6
  138. package/src/__tests__/schedule-tools.test.ts +2 -6
  139. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  140. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  141. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  142. package/src/__tests__/search-skills-unified.test.ts +421 -0
  143. package/src/__tests__/secret-allowlist.test.ts +20 -35
  144. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  145. package/src/__tests__/send-endpoint-busy.test.ts +2 -6
  146. package/src/__tests__/sequence-store.test.ts +2 -6
  147. package/src/__tests__/server-history-render.test.ts +2 -6
  148. package/src/__tests__/shell-credential-ref.test.ts +0 -5
  149. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  150. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  151. package/src/__tests__/skill-load-feature-flag.test.ts +13 -54
  152. package/src/__tests__/skill-load-inline-command.test.ts +3 -65
  153. package/src/__tests__/skill-load-inline-includes.test.ts +3 -65
  154. package/src/__tests__/skill-load-tool.test.ts +3 -67
  155. package/src/__tests__/skill-memory.test.ts +480 -195
  156. package/src/__tests__/skills-uninstall.test.ts +2 -2
  157. package/src/__tests__/skills.test.ts +23 -50
  158. package/src/__tests__/slack-channel-config.test.ts +2 -21
  159. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  160. package/src/__tests__/starter-bundle.test.ts +2 -8
  161. package/src/__tests__/stt-hints.test.ts +7 -2
  162. package/src/__tests__/system-prompt.test.ts +25 -45
  163. package/src/__tests__/task-compiler.test.ts +2 -27
  164. package/src/__tests__/task-management-tools.test.ts +2 -27
  165. package/src/__tests__/task-memory-cleanup.test.ts +173 -250
  166. package/src/__tests__/task-runner.test.ts +2 -27
  167. package/src/__tests__/task-scheduler.test.ts +2 -27
  168. package/src/__tests__/terminal-tools.test.ts +1 -17
  169. package/src/__tests__/test-preload.ts +3 -0
  170. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +0 -79
  171. package/src/__tests__/tool-approval-handler.test.ts +4 -27
  172. package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -11
  173. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -25
  174. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  175. package/src/__tests__/tool-executor.test.ts +0 -1
  176. package/src/__tests__/tool-grant-request-escalation.test.ts +4 -27
  177. package/src/__tests__/tool-preview-lifecycle.test.ts +0 -20
  178. package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
  179. package/src/__tests__/trust-store.test.ts +10 -42
  180. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -30
  181. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +3 -27
  182. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -28
  183. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -28
  184. package/src/__tests__/trusted-contact-verification.test.ts +2 -28
  185. package/src/__tests__/turn-boundary-resolution.test.ts +2 -34
  186. package/src/__tests__/twilio-provider.test.ts +0 -16
  187. package/src/__tests__/twilio-routes-twiml.test.ts +7 -12
  188. package/src/__tests__/twilio-routes.test.ts +0 -24
  189. package/src/__tests__/update-bulletin.test.ts +17 -89
  190. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -26
  191. package/src/__tests__/usage-routes.test.ts +2 -27
  192. package/src/__tests__/user-reference.test.ts +1 -5
  193. package/src/__tests__/vbundle-pax-and-symlink.test.ts +4 -34
  194. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +2 -53
  195. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  196. package/src/__tests__/voice-invite-redemption.test.ts +2 -27
  197. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -30
  198. package/src/__tests__/voice-session-bridge.test.ts +2 -27
  199. package/src/__tests__/volume-security-guard.test.ts +2 -0
  200. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  201. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -29
  202. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +2 -2
  203. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +4 -29
  204. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  205. package/src/__tests__/workspace-migration-down-functions.test.ts +0 -6
  206. package/src/__tests__/workspace-policy.test.ts +1 -1
  207. package/src/acp/client-handler.ts +1 -2
  208. package/src/agent/attachments.ts +7 -2
  209. package/src/agent/image-optimize.ts +165 -0
  210. package/src/agent/loop.ts +1 -15
  211. package/src/bundler/app-compiler.ts +179 -2
  212. package/src/bundler/package-resolver.ts +3 -5
  213. package/src/cli/__tests__/notifications.test.ts +1 -24
  214. package/src/cli/cli-memory.ts +179 -0
  215. package/src/cli/commands/avatar.ts +3 -3
  216. package/src/cli/commands/config.ts +26 -13
  217. package/src/cli/commands/doctor.ts +2 -2
  218. package/src/cli/commands/memory.ts +41 -55
  219. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  220. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  221. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  222. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
  223. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  224. package/src/cli/commands/oauth/connect.ts +26 -6
  225. package/src/cli/commands/oauth/mode.ts +7 -0
  226. package/src/cli/commands/oauth/providers.ts +49 -42
  227. package/src/cli/commands/oauth/shared.ts +39 -3
  228. package/src/cli/commands/platform/__tests__/connect.test.ts +3 -49
  229. package/src/cli/commands/platform/__tests__/disconnect.test.ts +3 -49
  230. package/src/cli/commands/platform/__tests__/status.test.ts +5 -55
  231. package/src/cli/commands/platform/index.ts +16 -16
  232. package/src/cli/commands/skills.ts +88 -16
  233. package/src/cli/commands/trust.ts +2 -2
  234. package/src/cli/lib/daemon-credential-client.ts +2 -3
  235. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  236. package/src/config/bundled-skills/computer-use/TOOLS.json +7 -7
  237. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  238. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  239. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  240. package/src/config/bundled-skills/gmail/SKILL.md +2 -10
  241. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  242. package/src/config/bundled-skills/messaging/SKILL.md +26 -19
  243. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  244. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  245. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  246. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  247. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  248. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  249. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  250. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  251. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  252. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  253. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  254. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  255. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  256. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  257. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  258. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  259. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  260. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  261. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  262. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  263. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  264. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  265. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  266. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  267. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  268. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  269. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  270. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  271. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  272. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  273. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  274. package/src/config/bundled-skills/slack/SKILL.md +1 -7
  275. package/src/config/bundled-tool-registry.ts +56 -4
  276. package/src/config/env-registry.ts +15 -8
  277. package/src/config/feature-flag-registry.json +29 -116
  278. package/src/config/loader.ts +4 -0
  279. package/src/config/schemas/platform.ts +8 -0
  280. package/src/config/schemas/security.ts +0 -6
  281. package/src/config/schemas/services.ts +8 -0
  282. package/src/config/schemas/timeouts.ts +1 -1
  283. package/src/config/skills.ts +18 -7
  284. package/src/context/token-estimator.ts +25 -18
  285. package/src/context/window-manager.ts +32 -9
  286. package/src/credential-execution/approval-bridge.ts +0 -1
  287. package/src/credential-execution/process-manager.ts +3 -1
  288. package/src/daemon/config-watcher.ts +51 -0
  289. package/src/daemon/context-overflow-reducer.ts +46 -2
  290. package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
  291. package/src/daemon/conversation-agent-loop.ts +99 -63
  292. package/src/daemon/conversation-error.ts +31 -8
  293. package/src/daemon/conversation-lifecycle.ts +33 -0
  294. package/src/daemon/conversation-media-retry.ts +85 -7
  295. package/src/daemon/conversation-notifiers.ts +4 -1
  296. package/src/daemon/conversation-process.ts +1 -0
  297. package/src/daemon/conversation-runtime-assembly.ts +5 -0
  298. package/src/daemon/conversation-usage.ts +1 -0
  299. package/src/daemon/conversation.ts +41 -2
  300. package/src/daemon/daemon-control.ts +8 -2
  301. package/src/daemon/handlers/shared.ts +22 -12
  302. package/src/daemon/handlers/skills.ts +423 -201
  303. package/src/daemon/lifecycle.ts +52 -4
  304. package/src/daemon/main.ts +5 -1
  305. package/src/daemon/message-types/conversations.ts +5 -1
  306. package/src/daemon/message-types/messages.ts +3 -1
  307. package/src/daemon/message-types/skills.ts +97 -36
  308. package/src/daemon/providers-setup.ts +7 -0
  309. package/src/daemon/server.ts +35 -22
  310. package/src/daemon/tool-side-effects.ts +27 -5
  311. package/src/events/domain-events.ts +1 -2
  312. package/src/heartbeat/heartbeat-service.ts +1 -0
  313. package/src/hooks/cli.ts +2 -2
  314. package/src/hooks/runner.ts +15 -38
  315. package/src/inbound/platform-callback-registration.ts +14 -14
  316. package/src/memory/admin.ts +11 -45
  317. package/src/memory/conversation-bootstrap.ts +2 -0
  318. package/src/memory/conversation-crud.ts +242 -348
  319. package/src/memory/conversation-group-migration.ts +157 -0
  320. package/src/memory/conversation-queries.ts +4 -2
  321. package/src/memory/db-init.ts +39 -3
  322. package/src/memory/embed.ts +73 -0
  323. package/src/memory/embedding-backend.ts +8 -14
  324. package/src/memory/embedding-runtime-manager.ts +12 -114
  325. package/src/memory/fingerprint.ts +2 -2
  326. package/src/memory/graph/bootstrap.ts +512 -0
  327. package/src/memory/graph/capability-seed.ts +297 -0
  328. package/src/memory/graph/consolidation.ts +691 -0
  329. package/src/memory/graph/conversation-graph-memory.ts +630 -0
  330. package/src/memory/graph/decay.test.ts +208 -0
  331. package/src/memory/graph/decay.ts +195 -0
  332. package/src/memory/graph/extraction-job.ts +69 -0
  333. package/src/memory/graph/extraction.test.ts +936 -0
  334. package/src/memory/graph/extraction.ts +1254 -0
  335. package/src/memory/graph/graph-search.ts +266 -0
  336. package/src/memory/graph/image-ref-utils.ts +29 -0
  337. package/src/memory/graph/injection.test.ts +513 -0
  338. package/src/memory/graph/injection.ts +439 -0
  339. package/src/memory/graph/inspect.ts +534 -0
  340. package/src/memory/graph/narrative.ts +267 -0
  341. package/src/memory/graph/pattern-scan.ts +269 -0
  342. package/src/memory/graph/retriever.ts +1008 -0
  343. package/src/memory/graph/scoring.test.ts +548 -0
  344. package/src/memory/graph/scoring.ts +232 -0
  345. package/src/memory/graph/serendipity.ts +65 -0
  346. package/src/memory/graph/store.test.ts +1050 -0
  347. package/src/memory/graph/store.ts +699 -0
  348. package/src/memory/graph/tool-handlers.ts +426 -0
  349. package/src/memory/graph/tools.ts +141 -0
  350. package/src/memory/graph/triggers.test.ts +487 -0
  351. package/src/memory/graph/triggers.ts +223 -0
  352. package/src/memory/graph/types.ts +271 -0
  353. package/src/memory/group-crud.ts +191 -0
  354. package/src/memory/indexer.ts +37 -19
  355. package/src/memory/job-handlers/cleanup.ts +0 -53
  356. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  357. package/src/memory/job-handlers/embedding.test.ts +3 -27
  358. package/src/memory/job-handlers/embedding.ts +5 -31
  359. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  360. package/src/memory/job-handlers/summarization.ts +32 -17
  361. package/src/memory/job-utils.ts +1 -1
  362. package/src/memory/jobs-store.ts +50 -70
  363. package/src/memory/jobs-worker.ts +147 -112
  364. package/src/memory/llm-usage-store.ts +35 -2
  365. package/src/memory/message-content.ts +1 -0
  366. package/src/memory/migrations/201-oauth-providers-feature-flag.ts +11 -0
  367. package/src/memory/migrations/202-drop-callback-transport-column.ts +13 -0
  368. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  369. package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
  370. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  371. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  372. package/src/memory/migrations/index.ts +6 -0
  373. package/src/memory/migrations/registry.ts +8 -0
  374. package/src/memory/qdrant-client.ts +44 -17
  375. package/src/memory/qdrant-manager.ts +26 -5
  376. package/src/memory/schema/index.ts +1 -0
  377. package/src/memory/schema/memory-graph.ts +139 -0
  378. package/src/memory/schema/oauth.ts +1 -1
  379. package/src/memory/search/semantic.ts +47 -91
  380. package/src/memory/slack-thread-store.ts +17 -0
  381. package/src/memory/task-memory-cleanup.ts +28 -50
  382. package/src/messaging/providers/outlook/adapter.ts +200 -0
  383. package/src/messaging/providers/outlook/client.ts +610 -0
  384. package/src/messaging/providers/outlook/types.ts +201 -0
  385. package/src/notifications/adapters/macos.ts +1 -0
  386. package/src/notifications/adapters/slack.ts +1 -1
  387. package/src/notifications/copy-composer.ts +9 -0
  388. package/src/notifications/signal.ts +16 -0
  389. package/src/oauth/__tests__/identity-verifier.test.ts +1 -1
  390. package/src/oauth/connect-orchestrator.ts +10 -3
  391. package/src/oauth/oauth-store.ts +10 -11
  392. package/src/oauth/provider-serializer.ts +3 -0
  393. package/src/oauth/provider-visibility.ts +16 -0
  394. package/src/oauth/seed-providers.ts +50 -17
  395. package/src/permissions/checker.ts +62 -9
  396. package/src/permissions/defaults.ts +4 -4
  397. package/src/permissions/types.ts +2 -4
  398. package/src/permissions/workspace-policy.ts +1 -1
  399. package/src/playbooks/playbook-compiler.ts +19 -18
  400. package/src/playbooks/types.ts +4 -3
  401. package/src/prompts/system-prompt.ts +6 -93
  402. package/src/prompts/templates/UPDATES.md +6 -0
  403. package/src/providers/anthropic/client.ts +47 -19
  404. package/src/providers/gemini/client.ts +1 -1
  405. package/src/providers/openai/client.ts +1 -1
  406. package/src/providers/registry.ts +1 -1
  407. package/src/providers/retry.ts +19 -3
  408. package/src/runtime/actor-trust-resolver.ts +5 -1
  409. package/src/runtime/auth/__tests__/credential-service.test.ts +1 -27
  410. package/src/runtime/auth/__tests__/token-service.test.ts +1 -25
  411. package/src/runtime/auth/route-policy.ts +7 -4
  412. package/src/runtime/guardian-reply-router.ts +10 -2
  413. package/src/runtime/http-server.ts +23 -3
  414. package/src/runtime/middleware/auth.ts +20 -0
  415. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  416. package/src/runtime/routes/attachment-routes.ts +106 -16
  417. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  418. package/src/runtime/routes/btw-routes.ts +8 -0
  419. package/src/runtime/routes/conversation-management-routes.ts +2 -0
  420. package/src/runtime/routes/conversation-query-routes.ts +2 -58
  421. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  422. package/src/runtime/routes/debug-routes.ts +1 -1
  423. package/src/runtime/routes/global-search-routes.ts +21 -19
  424. package/src/runtime/routes/group-routes.ts +207 -0
  425. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  426. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  427. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  428. package/src/runtime/routes/inbound-stages/background-dispatch.ts +43 -2
  429. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  430. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  431. package/src/runtime/routes/memory-item-routes.test.ts +2 -31
  432. package/src/runtime/routes/memory-item-routes.ts +385 -341
  433. package/src/runtime/routes/oauth-apps.ts +18 -1
  434. package/src/runtime/routes/oauth-providers.ts +13 -1
  435. package/src/runtime/routes/schedule-routes.ts +2 -0
  436. package/src/runtime/routes/settings-routes.ts +1 -0
  437. package/src/runtime/routes/skills-routes.ts +103 -37
  438. package/src/runtime/routes/usage-routes.ts +19 -2
  439. package/src/runtime/routes/work-items-routes.test.ts +2 -27
  440. package/src/runtime/routes/workspace-routes.test.ts +3 -27
  441. package/src/schedule/scheduler.ts +8 -1
  442. package/src/security/oauth2.ts +1 -1
  443. package/src/security/secret-allowlist.ts +4 -4
  444. package/src/security/secure-keys.ts +4 -8
  445. package/src/shared/provider-env-vars.ts +19 -0
  446. package/src/skills/catalog-cache.ts +5 -0
  447. package/src/skills/catalog-install.ts +15 -14
  448. package/src/skills/clawhub.ts +134 -154
  449. package/src/skills/install-meta.ts +208 -0
  450. package/src/skills/managed-store.ts +27 -16
  451. package/src/skills/skill-memory.ts +210 -96
  452. package/src/skills/skillssh-registry.ts +19 -17
  453. package/src/tasks/task-runner.ts +3 -1
  454. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  455. package/src/tools/browser/runtime-check.ts +3 -1
  456. package/src/tools/memory/register.ts +63 -46
  457. package/src/tools/permission-checker.ts +7 -19
  458. package/src/tools/shared/filesystem/image-read.ts +22 -85
  459. package/src/tools/skills/skill-script-runner.ts +1 -1
  460. package/src/tools/terminal/safe-env.ts +1 -0
  461. package/src/tools/tool-manifest.ts +3 -3
  462. package/src/util/browser.ts +25 -10
  463. package/src/util/bun-runtime.ts +172 -0
  464. package/src/util/device-id.ts +3 -65
  465. package/src/watcher/providers/outlook-calendar.ts +343 -0
  466. package/src/watcher/providers/outlook.ts +198 -0
  467. package/src/workspace/git-service.ts +27 -6
  468. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  469. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  470. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  471. package/src/workspace/migrations/registry.ts +6 -0
  472. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  473. package/src/__tests__/journal-context.test.ts +0 -268
  474. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  475. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  476. package/src/__tests__/memory-query-builder.test.ts +0 -59
  477. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  478. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  479. package/src/__tests__/memory-regressions.test.ts +0 -3696
  480. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  481. package/src/daemon/conversation-memory.ts +0 -207
  482. package/src/memory/conversation-starters-cadence.ts +0 -74
  483. package/src/memory/items-extractor.ts +0 -860
  484. package/src/memory/job-handlers/batch-extraction.ts +0 -741
  485. package/src/memory/job-handlers/extraction.ts +0 -40
  486. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -383
  487. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  488. package/src/memory/journal-memory.ts +0 -224
  489. package/src/memory/query-builder.ts +0 -47
  490. package/src/memory/query-expansion.ts +0 -83
  491. package/src/memory/retriever.test.ts +0 -1590
  492. package/src/memory/retriever.ts +0 -1323
  493. package/src/memory/search/formatting.test.ts +0 -140
  494. package/src/memory/search/formatting.ts +0 -262
  495. package/src/memory/search/mmr.ts +0 -136
  496. package/src/memory/search/ranking.ts +0 -15
  497. package/src/memory/search/staleness.ts +0 -40
  498. package/src/memory/search/tier-classifier.ts +0 -18
  499. package/src/memory/search/types.ts +0 -121
  500. package/src/prompts/journal-context.ts +0 -156
  501. package/src/tools/memory/definitions.ts +0 -69
  502. package/src/tools/memory/handlers.test.ts +0 -590
  503. package/src/tools/memory/handlers.ts +0 -434
@@ -1,4 +1,4 @@
1
- import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  mock.module("../util/logger.js", () => ({
4
4
  getLogger: () =>
@@ -8,7 +8,7 @@ mock.module("../util/logger.js", () => ({
8
8
  truncateForLog: (value: string) => value,
9
9
  }));
10
10
 
11
- import { getDb, initializeDb, resetDb } from "../memory/db.js";
11
+ import { getDb, initializeDb } from "../memory/db.js";
12
12
  import { scopedApprovalGrants } from "../memory/schema.js";
13
13
  import {
14
14
  _internal,
@@ -34,10 +34,6 @@ function clearTables(): void {
34
34
  db.delete(scopedApprovalGrants).run();
35
35
  }
36
36
 
37
- afterAll(() => {
38
- resetDb();
39
- });
40
-
41
37
  // ---------------------------------------------------------------------------
42
38
  // Helper to build grant params with sensible defaults
43
39
  // ---------------------------------------------------------------------------
@@ -23,7 +23,7 @@
23
23
  * 11. Guardian identity mismatch cannot mint grant — guardian-grant-minting.test.ts
24
24
  */
25
25
 
26
- import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
26
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
27
27
 
28
28
  mock.module("../util/logger.js", () => ({
29
29
  getLogger: () =>
@@ -33,7 +33,7 @@ mock.module("../util/logger.js", () => ({
33
33
  truncateForLog: (value: string) => value,
34
34
  }));
35
35
 
36
- import { getDb, initializeDb, resetDb } from "../memory/db.js";
36
+ import { getDb, initializeDb } from "../memory/db.js";
37
37
  import { scopedApprovalGrants } from "../memory/schema.js";
38
38
  import {
39
39
  _internal,
@@ -51,10 +51,6 @@ function clearTables(): void {
51
51
  db.delete(scopedApprovalGrants).run();
52
52
  }
53
53
 
54
- afterAll(() => {
55
- resetDb();
56
- });
57
-
58
54
  // ---------------------------------------------------------------------------
59
55
  // Helper to build grant params with sensible defaults
60
56
  // ---------------------------------------------------------------------------
@@ -0,0 +1,421 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Mock state
5
+ // ---------------------------------------------------------------------------
6
+
7
+ const mockCatalogSkills = mock(
8
+ (): Array<{
9
+ id: string;
10
+ displayName: string;
11
+ description: string;
12
+ source: string;
13
+ }> => [],
14
+ );
15
+ const mockClawhubSearch = mock(
16
+ async (
17
+ _query: string,
18
+ ): Promise<{
19
+ skills: Array<{
20
+ name: string;
21
+ slug: string;
22
+ description: string;
23
+ author: string;
24
+ stars: number;
25
+ installs: number;
26
+ version: string;
27
+ createdAt: number;
28
+ source: string;
29
+ }>;
30
+ }> => ({ skills: [] }),
31
+ );
32
+ const mockSkillsshSearch = mock(
33
+ async (
34
+ _query: string,
35
+ _limit?: number,
36
+ ): Promise<
37
+ Array<{
38
+ id: string;
39
+ skillId: string;
40
+ name: string;
41
+ installs: number;
42
+ source: string;
43
+ }>
44
+ > => [],
45
+ );
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Mock modules — before importing module under test
49
+ // ---------------------------------------------------------------------------
50
+
51
+ mock.module("../config/skills.js", () => ({
52
+ loadSkillCatalog: mockCatalogSkills,
53
+ }));
54
+
55
+ mock.module("../skills/catalog-search.js", () => ({
56
+ filterByQuery: (
57
+ items: Array<{ id: string; displayName: string; description: string }>,
58
+ query: string,
59
+ _fields: unknown[],
60
+ ) => {
61
+ const lower = query.toLowerCase();
62
+ return items.filter(
63
+ (s) =>
64
+ s.id.toLowerCase().includes(lower) ||
65
+ s.displayName.toLowerCase().includes(lower) ||
66
+ s.description.toLowerCase().includes(lower),
67
+ );
68
+ },
69
+ }));
70
+
71
+ mock.module("../skills/clawhub.js", () => ({
72
+ clawhubSearch: mockClawhubSearch,
73
+ // Stubs for other exports that may be referenced at import time
74
+ clawhubCheckUpdates: mock(async () => []),
75
+ clawhubInspect: mock(async () => ({})),
76
+ clawhubInstall: mock(async () => ({ success: true })),
77
+ clawhubUpdate: mock(async () => ({ success: true })),
78
+ }));
79
+
80
+ mock.module("../skills/skillssh-registry.js", () => ({
81
+ searchSkillsRegistry: mockSkillsshSearch,
82
+ }));
83
+
84
+ // Stub install-meta (needed by the new origin derivation logic)
85
+ mock.module("../skills/install-meta.js", () => ({
86
+ readInstallMeta: () => null,
87
+ }));
88
+
89
+ // Stub remaining imports pulled in by skills.ts
90
+ mock.module("../config/assistant-feature-flags.js", () => ({
91
+ isAssistantFeatureFlagEnabled: () => true,
92
+ }));
93
+ mock.module("../config/loader.js", () => ({
94
+ getConfig: () => ({}),
95
+ invalidateConfigCache: () => {},
96
+ loadRawConfig: () => ({}),
97
+ saveRawConfig: () => {},
98
+ }));
99
+ mock.module("../config/skill-state.js", () => ({
100
+ resolveSkillStates: () => [],
101
+ skillFlagKey: () => null,
102
+ }));
103
+ mock.module("../providers/provider-send-message.js", () => ({
104
+ createTimeout: () => ({
105
+ signal: AbortSignal.timeout(1000),
106
+ cleanup: () => {},
107
+ }),
108
+ extractText: () => "",
109
+ getConfiguredProvider: async () => null,
110
+ userMessage: () => ({}),
111
+ }));
112
+ mock.module("../runtime/routes/workspace-utils.js", () => ({
113
+ isTextMimeType: () => true,
114
+ }));
115
+ mock.module("../skills/catalog-cache.js", () => ({
116
+ getCatalog: async () => [],
117
+ }));
118
+ mock.module("../skills/catalog-install.js", () => ({
119
+ installSkillLocally: async () => {},
120
+ }));
121
+ mock.module("../skills/managed-store.js", () => ({
122
+ createManagedSkill: () => ({ created: true }),
123
+ deleteManagedSkill: () => ({ deleted: true }),
124
+ removeSkillsIndexEntry: () => {},
125
+ validateManagedSkillId: () => null,
126
+ }));
127
+ mock.module("../skills/skill-memory.js", () => ({
128
+ deleteSkillCapabilityMemory: () => {},
129
+ seedCatalogSkillMemories: () => {},
130
+ }));
131
+ mock.module("../util/platform.js", () => ({
132
+ getWorkspaceSkillsDir: () => "/tmp/test-skills",
133
+ }));
134
+ mock.module("../daemon/handlers/shared.js", () => ({
135
+ CONFIG_RELOAD_DEBOUNCE_MS: 100,
136
+ ensureSkillEntry: () => ({}),
137
+ log: {
138
+ info: () => {},
139
+ warn: () => {},
140
+ error: () => {},
141
+ debug: () => {},
142
+ },
143
+ }));
144
+
145
+ // Import after mocking
146
+ import { searchSkills } from "../daemon/handlers/skills.js";
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // Helpers
150
+ // ---------------------------------------------------------------------------
151
+
152
+ const dummyCtx = {
153
+ debounceTimers: { schedule: () => {} },
154
+ setSuppressConfigReload: () => {},
155
+ updateConfigFingerprint: () => {},
156
+ broadcast: () => {},
157
+ } as unknown as Parameters<typeof searchSkills>[1];
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // Tests
161
+ // ---------------------------------------------------------------------------
162
+
163
+ describe("searchSkills (unified)", () => {
164
+ beforeEach(() => {
165
+ mockCatalogSkills.mockReset();
166
+ mockClawhubSearch.mockReset();
167
+ mockSkillsshSearch.mockReset();
168
+
169
+ // Defaults: empty results
170
+ mockCatalogSkills.mockReturnValue([]);
171
+ mockClawhubSearch.mockResolvedValue({ skills: [] });
172
+ mockSkillsshSearch.mockResolvedValue([]);
173
+ });
174
+
175
+ test("returns results from all three registries", async () => {
176
+ mockCatalogSkills.mockReturnValue([
177
+ {
178
+ id: "weather",
179
+ displayName: "Weather",
180
+ description: "Weather lookup",
181
+ source: "bundled",
182
+ },
183
+ ]);
184
+ mockClawhubSearch.mockResolvedValue({
185
+ skills: [
186
+ {
187
+ name: "Deploy",
188
+ slug: "deploy",
189
+ description: "Deploy helper",
190
+ author: "alice",
191
+ stars: 10,
192
+ installs: 100,
193
+ version: "1.0.0",
194
+ createdAt: 1000,
195
+ source: "clawhub",
196
+ },
197
+ ],
198
+ });
199
+ mockSkillsshSearch.mockResolvedValue([
200
+ {
201
+ id: "vercel-labs/skills/react-best",
202
+ skillId: "react-best",
203
+ name: "React Best Practices",
204
+ installs: 500,
205
+ source: "vercel-labs/skills",
206
+ },
207
+ ]);
208
+
209
+ const result = await searchSkills("e", dummyCtx);
210
+ expect(result.success).toBe(true);
211
+ if (!result.success) throw new Error("Expected success");
212
+
213
+ expect(result.skills).toHaveLength(3);
214
+
215
+ // Verify ordering: catalog first, then clawhub, then skills.sh
216
+ expect(result.skills[0]!.id).toBe("weather");
217
+ expect(result.skills[0]!.origin).toBe("vellum");
218
+ expect(result.skills[0]!.kind).toBe("catalog");
219
+ expect(result.skills[0]!.status).toBe("available");
220
+ expect(result.skills[1]!.id).toBe("deploy");
221
+ expect(result.skills[1]!.origin).toBe("clawhub");
222
+ expect(result.skills[1]!.kind).toBe("catalog");
223
+ expect(result.skills[2]!.id).toBe("vercel-labs/skills/react-best");
224
+ expect(result.skills[2]!.origin).toBe("skillssh");
225
+ expect(result.skills[2]!.kind).toBe("catalog");
226
+ });
227
+
228
+ test("deduplicates: catalog takes precedence over clawhub and skills.sh", async () => {
229
+ mockCatalogSkills.mockReturnValue([
230
+ {
231
+ id: "shared-skill",
232
+ displayName: "Shared Skill",
233
+ description: "From catalog",
234
+ source: "bundled",
235
+ },
236
+ ]);
237
+ mockClawhubSearch.mockResolvedValue({
238
+ skills: [
239
+ {
240
+ name: "Shared Skill",
241
+ slug: "shared-skill",
242
+ description: "From clawhub",
243
+ author: "",
244
+ stars: 5,
245
+ installs: 50,
246
+ version: "2.0.0",
247
+ createdAt: 2000,
248
+ source: "clawhub",
249
+ },
250
+ ],
251
+ });
252
+ // skills.sh uses full id as slug, so it won't collide with catalog/clawhub
253
+ // short slugs. Dedup only removes the clawhub duplicate here.
254
+ mockSkillsshSearch.mockResolvedValue([
255
+ {
256
+ id: "org/repo/shared-skill",
257
+ skillId: "shared-skill",
258
+ name: "Shared Skill",
259
+ installs: 300,
260
+ source: "org/repo",
261
+ },
262
+ ]);
263
+
264
+ const result = await searchSkills("shared", dummyCtx);
265
+ expect(result.success).toBe(true);
266
+ if (!result.success) throw new Error("Expected success");
267
+
268
+ // Catalog deduplicates clawhub (same slug "shared-skill"), but skills.sh
269
+ // now uses the full id "org/repo/shared-skill" so it's a distinct entry.
270
+ expect(result.skills).toHaveLength(2);
271
+ expect(result.skills[0]!.id).toBe("shared-skill");
272
+ expect(result.skills[0]!.origin).toBe("vellum");
273
+ expect(result.skills[1]!.id).toBe("org/repo/shared-skill");
274
+ expect(result.skills[1]!.origin).toBe("skillssh");
275
+ });
276
+
277
+ test("deduplicates: clawhub takes precedence over skills.sh with same slug", async () => {
278
+ mockCatalogSkills.mockReturnValue([]);
279
+ mockClawhubSearch.mockResolvedValue({
280
+ skills: [
281
+ {
282
+ name: "Overlap",
283
+ slug: "overlap-skill",
284
+ description: "From clawhub",
285
+ author: "bob",
286
+ stars: 20,
287
+ installs: 200,
288
+ version: "1.0.0",
289
+ createdAt: 3000,
290
+ source: "clawhub",
291
+ },
292
+ ],
293
+ });
294
+ // skills.sh now uses full id as slug, so it won't collide with clawhub
295
+ // short slugs — both entries survive dedup.
296
+ mockSkillsshSearch.mockResolvedValue([
297
+ {
298
+ id: "org/repo/overlap-skill",
299
+ skillId: "overlap-skill",
300
+ name: "Overlap",
301
+ installs: 100,
302
+ source: "org/repo",
303
+ },
304
+ ]);
305
+
306
+ const result = await searchSkills("overlap", dummyCtx);
307
+ expect(result.success).toBe(true);
308
+ if (!result.success) throw new Error("Expected success");
309
+
310
+ // Full id slug means no collision — both entries survive
311
+ expect(result.skills).toHaveLength(2);
312
+ expect(result.skills[0]!.id).toBe("overlap-skill");
313
+ expect(result.skills[0]!.origin).toBe("clawhub");
314
+ expect(result.skills[1]!.id).toBe("org/repo/overlap-skill");
315
+ expect(result.skills[1]!.origin).toBe("skillssh");
316
+ });
317
+
318
+ test("returns clawhub results when skills.sh fails", async () => {
319
+ mockCatalogSkills.mockReturnValue([]);
320
+ mockClawhubSearch.mockResolvedValue({
321
+ skills: [
322
+ {
323
+ name: "ClawhubSkill",
324
+ slug: "clawhub-only",
325
+ description: "",
326
+ author: "",
327
+ stars: 0,
328
+ installs: 0,
329
+ version: "",
330
+ createdAt: 0,
331
+ source: "clawhub",
332
+ },
333
+ ],
334
+ });
335
+ mockSkillsshSearch.mockRejectedValue(new Error("skills.sh is down"));
336
+
337
+ const result = await searchSkills("clawhub", dummyCtx);
338
+ expect(result.success).toBe(true);
339
+ if (!result.success) throw new Error("Expected success");
340
+
341
+ expect(result.skills).toHaveLength(1);
342
+ expect(result.skills[0]!.id).toBe("clawhub-only");
343
+ expect(result.skills[0]!.origin).toBe("clawhub");
344
+ });
345
+
346
+ test("returns skills.sh results when clawhub fails", async () => {
347
+ mockCatalogSkills.mockReturnValue([]);
348
+ mockClawhubSearch.mockRejectedValue(new Error("clawhub is down"));
349
+ mockSkillsshSearch.mockResolvedValue([
350
+ {
351
+ id: "org/repo/skillssh-only",
352
+ skillId: "skillssh-only",
353
+ name: "SkillsShOnly",
354
+ installs: 42,
355
+ source: "org/repo",
356
+ },
357
+ ]);
358
+
359
+ const result = await searchSkills("skillssh", dummyCtx);
360
+ expect(result.success).toBe(true);
361
+ if (!result.success) throw new Error("Expected success");
362
+
363
+ expect(result.skills).toHaveLength(1);
364
+ expect(result.skills[0]!.id).toBe("org/repo/skillssh-only");
365
+ expect(result.skills[0]!.origin).toBe("skillssh");
366
+ });
367
+
368
+ test("returns catalog-only results when both community registries fail", async () => {
369
+ mockCatalogSkills.mockReturnValue([
370
+ {
371
+ id: "my-skill",
372
+ displayName: "My Skill",
373
+ description: "A bundled skill",
374
+ source: "bundled",
375
+ },
376
+ ]);
377
+ mockClawhubSearch.mockRejectedValue(new Error("clawhub down"));
378
+ mockSkillsshSearch.mockRejectedValue(new Error("skillssh down"));
379
+
380
+ const result = await searchSkills("my", dummyCtx);
381
+ expect(result.success).toBe(true);
382
+ if (!result.success) throw new Error("Expected success");
383
+
384
+ expect(result.skills).toHaveLength(1);
385
+ expect(result.skills[0]!.id).toBe("my-skill");
386
+ expect(result.skills[0]!.origin).toBe("vellum");
387
+ });
388
+
389
+ test("skills.sh results have correct normalized fields", async () => {
390
+ mockCatalogSkills.mockReturnValue([]);
391
+ mockClawhubSearch.mockResolvedValue({ skills: [] });
392
+ mockSkillsshSearch.mockResolvedValue([
393
+ {
394
+ id: "org/repo/test-skill",
395
+ skillId: "test-skill",
396
+ name: "Test Skill",
397
+ installs: 99,
398
+ source: "org/repo",
399
+ },
400
+ ]);
401
+
402
+ const result = await searchSkills("test", dummyCtx);
403
+ expect(result.success).toBe(true);
404
+ if (!result.success) throw new Error("Expected success");
405
+
406
+ expect(result.skills).toHaveLength(1);
407
+
408
+ const skill = result.skills[0]!;
409
+ expect(skill.id).toBe("org/repo/test-skill");
410
+ expect(skill.name).toBe("Test Skill");
411
+ expect(skill.description).toBe("");
412
+ expect(skill.kind).toBe("catalog");
413
+ expect(skill.origin).toBe("skillssh");
414
+ expect(skill.status).toBe("available");
415
+ if (skill.origin === "skillssh") {
416
+ expect(skill.slug).toBe("org/repo/test-skill");
417
+ expect(skill.sourceRepo).toBe("org/repo");
418
+ expect(skill.installs).toBe(99);
419
+ }
420
+ });
421
+ });
@@ -1,10 +1,8 @@
1
- import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
- import { tmpdir } from "node:os";
1
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
2
  import { join } from "node:path";
4
3
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
5
4
 
6
- // Mock the data dir to a temp directory so tests don't touch ~/.vellum/
7
- let testDir: string;
5
+ const testDir = process.env.VELLUM_WORKSPACE_DIR!;
8
6
 
9
7
  mock.module("../util/logger.js", () => ({
10
8
  getLogger: () =>
@@ -13,11 +11,6 @@ mock.module("../util/logger.js", () => ({
13
11
  }),
14
12
  }));
15
13
 
16
- mock.module("../util/platform.js", () => ({
17
- getProtectedDir: () => join(testDir, "protected"),
18
- getDataDir: () => testDir,
19
- }));
20
-
21
14
  import {
22
15
  isAllowlisted,
23
16
  loadAllowlist,
@@ -27,21 +20,13 @@ import { scanText } from "../security/secret-scanner.js";
27
20
 
28
21
  describe("secret-allowlist", () => {
29
22
  beforeEach(() => {
30
- testDir = join(
31
- tmpdir(),
32
- `vellum-allowlist-test-${Date.now()}-${Math.random()
33
- .toString(36)
34
- .slice(2)}`,
35
- );
36
- mkdirSync(join(testDir, "protected"), { recursive: true });
23
+ mkdirSync(join(testDir, "data"), { recursive: true });
37
24
  resetAllowlist();
38
25
  });
39
26
 
40
27
  afterEach(() => {
41
28
  resetAllowlist();
42
- if (existsSync(testDir)) {
43
- rmSync(testDir, { recursive: true, force: true });
44
- }
29
+ rmSync(join(testDir, "data"), { recursive: true, force: true });
45
30
  });
46
31
 
47
32
  // -----------------------------------------------------------------------
@@ -56,7 +41,7 @@ describe("secret-allowlist", () => {
56
41
  // -----------------------------------------------------------------------
57
42
  test("[experimental] suppresses exact values", () => {
58
43
  writeFileSync(
59
- join(testDir, "protected", "secret-allowlist.json"),
44
+ join(testDir, "data", "secret-allowlist.json"),
60
45
  JSON.stringify({ values: ["my-test-api-key-12345"] }),
61
46
  );
62
47
  expect(isAllowlisted("my-test-api-key-12345")).toBe(true);
@@ -65,7 +50,7 @@ describe("secret-allowlist", () => {
65
50
 
66
51
  test("[experimental] exact values are case-sensitive", () => {
67
52
  writeFileSync(
68
- join(testDir, "protected", "secret-allowlist.json"),
53
+ join(testDir, "data", "secret-allowlist.json"),
69
54
  JSON.stringify({ values: ["MyTestKey"] }),
70
55
  );
71
56
  expect(isAllowlisted("MyTestKey")).toBe(true);
@@ -77,7 +62,7 @@ describe("secret-allowlist", () => {
77
62
  // -----------------------------------------------------------------------
78
63
  test("[experimental] suppresses values matching a prefix", () => {
79
64
  writeFileSync(
80
- join(testDir, "protected", "secret-allowlist.json"),
65
+ join(testDir, "data", "secret-allowlist.json"),
81
66
  JSON.stringify({ prefixes: ["my-internal-"] }),
82
67
  );
83
68
  expect(isAllowlisted("my-internal-key-abc123")).toBe(true);
@@ -89,7 +74,7 @@ describe("secret-allowlist", () => {
89
74
  // -----------------------------------------------------------------------
90
75
  test("[experimental] suppresses values matching a regex pattern", () => {
91
76
  writeFileSync(
92
- join(testDir, "protected", "secret-allowlist.json"),
77
+ join(testDir, "data", "secret-allowlist.json"),
93
78
  JSON.stringify({ patterns: ["^ci-test-[a-z0-9]+$"] }),
94
79
  );
95
80
  expect(isAllowlisted("ci-test-abc123")).toBe(true);
@@ -98,7 +83,7 @@ describe("secret-allowlist", () => {
98
83
 
99
84
  test("[experimental] invalid regex is skipped without crashing", () => {
100
85
  writeFileSync(
101
- join(testDir, "protected", "secret-allowlist.json"),
86
+ join(testDir, "data", "secret-allowlist.json"),
102
87
  JSON.stringify({ patterns: ["[invalid", "^valid$"] }),
103
88
  );
104
89
  loadAllowlist();
@@ -113,7 +98,7 @@ describe("secret-allowlist", () => {
113
98
  // -----------------------------------------------------------------------
114
99
  test("[experimental] combines values, prefixes, and patterns", () => {
115
100
  writeFileSync(
116
- join(testDir, "protected", "secret-allowlist.json"),
101
+ join(testDir, "data", "secret-allowlist.json"),
117
102
  JSON.stringify({
118
103
  values: ["exact-match-value"],
119
104
  prefixes: ["test-prefix-"],
@@ -131,7 +116,7 @@ describe("secret-allowlist", () => {
131
116
  // -----------------------------------------------------------------------
132
117
  test("handles malformed JSON gracefully", () => {
133
118
  writeFileSync(
134
- join(testDir, "protected", "secret-allowlist.json"),
119
+ join(testDir, "data", "secret-allowlist.json"),
135
120
  "not json{{{",
136
121
  );
137
122
  loadAllowlist();
@@ -141,7 +126,7 @@ describe("secret-allowlist", () => {
141
126
 
142
127
  test("handles non-array fields gracefully", () => {
143
128
  writeFileSync(
144
- join(testDir, "protected", "secret-allowlist.json"),
129
+ join(testDir, "data", "secret-allowlist.json"),
145
130
  JSON.stringify({ values: "not-an-array", prefixes: 42 }),
146
131
  );
147
132
  loadAllowlist();
@@ -154,7 +139,7 @@ describe("secret-allowlist", () => {
154
139
  test("[experimental] allowlisted values are suppressed by scanText", () => {
155
140
  const awsKey = "AKIAIOSFODNN7REALKEY";
156
141
  writeFileSync(
157
- join(testDir, "protected", "secret-allowlist.json"),
142
+ join(testDir, "data", "secret-allowlist.json"),
158
143
  JSON.stringify({ values: [awsKey] }),
159
144
  );
160
145
  resetAllowlist();
@@ -166,7 +151,7 @@ describe("secret-allowlist", () => {
166
151
 
167
152
  test("non-allowlisted values are still detected by scanText", () => {
168
153
  writeFileSync(
169
- join(testDir, "protected", "secret-allowlist.json"),
154
+ join(testDir, "data", "secret-allowlist.json"),
170
155
  JSON.stringify({ values: ["AKIAIOSFODNN7OTHERKE"] }),
171
156
  );
172
157
  resetAllowlist();
@@ -178,7 +163,7 @@ describe("secret-allowlist", () => {
178
163
 
179
164
  test("[experimental] prefix allowlist suppresses pattern matches", () => {
180
165
  writeFileSync(
181
- join(testDir, "protected", "secret-allowlist.json"),
166
+ join(testDir, "data", "secret-allowlist.json"),
182
167
  JSON.stringify({ prefixes: ["ghp_AAAA"] }),
183
168
  );
184
169
  resetAllowlist();
@@ -198,7 +183,7 @@ describe("secret-allowlist", () => {
198
183
 
199
184
  // Create the file — but fileChecked is cached, so it won't be seen
200
185
  writeFileSync(
201
- join(testDir, "protected", "secret-allowlist.json"),
186
+ join(testDir, "data", "secret-allowlist.json"),
202
187
  JSON.stringify({ values: ["test-key"] }),
203
188
  );
204
189
  expect(isAllowlisted("test-key")).toBe(false);
@@ -211,7 +196,7 @@ describe("secret-allowlist", () => {
211
196
  test("[experimental] retries loading when file was malformed on first call", () => {
212
197
  // First call with malformed JSON
213
198
  writeFileSync(
214
- join(testDir, "protected", "secret-allowlist.json"),
199
+ join(testDir, "data", "secret-allowlist.json"),
215
200
  "not json{{{",
216
201
  );
217
202
  loadAllowlist();
@@ -219,7 +204,7 @@ describe("secret-allowlist", () => {
219
204
 
220
205
  // Fix the file
221
206
  writeFileSync(
222
- join(testDir, "protected", "secret-allowlist.json"),
207
+ join(testDir, "data", "secret-allowlist.json"),
223
208
  JSON.stringify({ values: ["test-key"] }),
224
209
  );
225
210
 
@@ -232,7 +217,7 @@ describe("secret-allowlist", () => {
232
217
  // -----------------------------------------------------------------------
233
218
  test("[experimental] resetAllowlist clears cached state", () => {
234
219
  writeFileSync(
235
- join(testDir, "protected", "secret-allowlist.json"),
220
+ join(testDir, "data", "secret-allowlist.json"),
236
221
  JSON.stringify({ values: ["test-value"] }),
237
222
  );
238
223
  loadAllowlist();
@@ -240,7 +225,7 @@ describe("secret-allowlist", () => {
240
225
 
241
226
  // Reset and remove file — should no longer be allowlisted
242
227
  resetAllowlist();
243
- rmSync(join(testDir, "protected", "secret-allowlist.json"));
228
+ rmSync(join(testDir, "data", "secret-allowlist.json"));
244
229
  expect(isAllowlisted("test-value")).toBe(false);
245
230
  });
246
231
  });
@@ -67,6 +67,8 @@ mock.module("../security/secure-keys.js", () => {
67
67
  syncSet(key, value),
68
68
  deleteSecureKeyAsync: async (key: string) => syncDelete(key),
69
69
  listSecureKeysAsync: async () => ({ accounts: [], unreachable: false }),
70
+ getProviderKeyAsync: async () => undefined,
71
+ getMaskedProviderKey: async () => null,
70
72
  };
71
73
  });
72
74