@vellumai/assistant 0.8.2 → 0.8.4

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 +11 -12
  2. package/docker-entrypoint.sh +13 -2
  3. package/docker-init-apt-root.sh +79 -6
  4. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  5. package/openapi.yaml +945 -36
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
  8. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  9. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +272 -0
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  14. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  15. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  16. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  17. package/src/__tests__/compaction-events.test.ts +1 -1
  18. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  19. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  20. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  22. package/src/__tests__/config-watcher.test.ts +1 -1
  23. package/src/__tests__/context-token-estimator.test.ts +112 -57
  24. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  27. package/src/__tests__/conversation-agent-loop.test.ts +77 -3
  28. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  29. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  30. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  32. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  33. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  34. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  35. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  36. package/src/__tests__/conversation-pairing.test.ts +2 -2
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  39. package/src/__tests__/conversation-queue.test.ts +1 -1
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
  41. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  42. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  43. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  44. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  45. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  46. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  47. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  48. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  49. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  50. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  51. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  52. package/src/__tests__/date-context.test.ts +45 -0
  53. package/src/__tests__/dm-backfill.test.ts +64 -0
  54. package/src/__tests__/dm-persistence.test.ts +33 -0
  55. package/src/__tests__/document-find-replace.test.ts +501 -0
  56. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  57. package/src/__tests__/first-greeting.test.ts +23 -2
  58. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  59. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  60. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  61. package/src/__tests__/heartbeat-service.test.ts +24 -164
  62. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  63. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  64. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  66. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  67. package/src/__tests__/host-file-proxy.test.ts +8 -1
  68. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  69. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  70. package/src/__tests__/identity-routes.test.ts +57 -0
  71. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  72. package/src/__tests__/injector-background-turn.test.ts +153 -0
  73. package/src/__tests__/injector-chain.test.ts +7 -0
  74. package/src/__tests__/injector-document-comments.test.ts +378 -0
  75. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  76. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  77. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  78. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  79. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  80. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  81. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  82. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  83. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  84. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  85. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  86. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  87. package/src/__tests__/llm-resolver.test.ts +340 -3
  88. package/src/__tests__/log-export-routes.test.ts +99 -2
  89. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  90. package/src/__tests__/message-queue-steer.test.ts +114 -0
  91. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  92. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  93. package/src/__tests__/notification-deep-link.test.ts +15 -0
  94. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  95. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  96. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  97. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  98. package/src/__tests__/openai-provider.test.ts +323 -3
  99. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  100. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  101. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  102. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  103. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  104. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  105. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  106. package/src/__tests__/platform.test.ts +0 -3
  107. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  108. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  109. package/src/__tests__/plugin-types.test.ts +2 -2
  110. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  111. package/src/__tests__/process-message-display-content.test.ts +21 -16
  112. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  113. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  115. package/src/__tests__/server-history-render.test.ts +83 -4
  116. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  117. package/src/__tests__/system-prompt.test.ts +57 -101
  118. package/src/__tests__/terminal-tools.test.ts +11 -1
  119. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  120. package/src/__tests__/thread-backfill.test.ts +370 -22
  121. package/src/__tests__/tool-executor.test.ts +90 -1
  122. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  123. package/src/__tests__/twilio-routes.test.ts +1 -1
  124. package/src/__tests__/web-fetch.test.ts +2 -2
  125. package/src/__tests__/workspace-git-service.test.ts +88 -5
  126. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  127. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  128. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  129. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  130. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  131. package/src/a2a/__tests__/task-store.test.ts +246 -0
  132. package/src/a2a/agent-card.ts +58 -0
  133. package/src/a2a/feature-gate.ts +8 -0
  134. package/src/a2a/protocol-constants.ts +21 -0
  135. package/src/a2a/protocol-errors.ts +50 -0
  136. package/src/a2a/protocol-types.ts +162 -0
  137. package/src/a2a/task-store.ts +168 -0
  138. package/src/agent/attachments.ts +1 -0
  139. package/src/agent/loop.ts +208 -22
  140. package/src/background-wake/next-wake.test.ts +289 -0
  141. package/src/background-wake/next-wake.ts +172 -0
  142. package/src/browser/operations.ts +15 -0
  143. package/src/channels/config.ts +9 -0
  144. package/src/channels/types.ts +14 -0
  145. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  146. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  147. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  148. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  149. package/src/cli/commands/conversations.ts +128 -1
  150. package/src/cli/commands/inference-providers.ts +147 -1
  151. package/src/cli/commands/memory-v2.ts +308 -0
  152. package/src/cli/commands/notifications.ts +89 -37
  153. package/src/cli/commands/plugins.ts +67 -0
  154. package/src/cli/commands/schedules.ts +297 -5
  155. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  156. package/src/cli/lib/install-from-github.ts +8 -9
  157. package/src/cli/lib/search-plugins.ts +163 -0
  158. package/src/cli/program.ts +14 -0
  159. package/src/cli/utils/conversation-id.ts +17 -5
  160. package/src/config/assistant-feature-flags.ts +24 -54
  161. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  162. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  163. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  164. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  165. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  166. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  167. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  168. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  169. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  170. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  171. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  172. package/src/config/bundled-tool-registry.ts +22 -12
  173. package/src/config/call-site-defaults.ts +124 -0
  174. package/src/config/feature-flag-registry.json +111 -23
  175. package/src/config/llm-resolver.ts +66 -1
  176. package/src/config/schema.ts +2 -0
  177. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
  178. package/src/config/schemas/call-site-catalog.ts +21 -0
  179. package/src/config/schemas/channels.ts +9 -0
  180. package/src/config/schemas/conversations.ts +10 -0
  181. package/src/config/schemas/heartbeat.ts +14 -0
  182. package/src/config/schemas/llm.ts +4 -3
  183. package/src/config/schemas/memory-retrospective.ts +1 -1
  184. package/src/config/schemas/memory-v2.ts +51 -4
  185. package/src/config/schemas/memory.ts +3 -1
  186. package/src/config/seed-inference-profiles.ts +99 -29
  187. package/src/context/compactor.ts +80 -13
  188. package/src/context/token-estimator.ts +72 -31
  189. package/src/context/window-manager.ts +25 -0
  190. package/src/credential-health/credential-health-service.ts +34 -19
  191. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  192. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  193. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  194. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  195. package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
  196. package/src/daemon/conversation-agent-loop.ts +252 -56
  197. package/src/daemon/conversation-lifecycle.ts +142 -116
  198. package/src/daemon/conversation-messaging.ts +3 -0
  199. package/src/daemon/conversation-process.ts +273 -0
  200. package/src/daemon/conversation-queue-manager.ts +14 -0
  201. package/src/daemon/conversation-runtime-assembly.ts +144 -75
  202. package/src/daemon/conversation-slash.ts +37 -5
  203. package/src/daemon/conversation-surfaces.ts +45 -2
  204. package/src/daemon/conversation-tool-setup.ts +7 -0
  205. package/src/daemon/conversation.ts +42 -12
  206. package/src/daemon/date-context.ts +40 -0
  207. package/src/daemon/first-greeting.ts +10 -0
  208. package/src/daemon/guardian-action-generators.ts +1 -125
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  212. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  213. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  214. package/src/daemon/handlers/config-a2a.ts +449 -0
  215. package/src/daemon/handlers/config-model.test.ts +1 -0
  216. package/src/daemon/handlers/conversations.ts +80 -0
  217. package/src/daemon/handlers/shared.ts +92 -29
  218. package/src/daemon/host-app-control-proxy.ts +69 -18
  219. package/src/daemon/host-bash-proxy.ts +1 -1
  220. package/src/daemon/host-cu-proxy.ts +1 -1
  221. package/src/daemon/host-file-proxy.ts +1 -1
  222. package/src/daemon/host-proxy-preactivation.ts +85 -18
  223. package/src/daemon/host-transfer-proxy.ts +1 -1
  224. package/src/daemon/lifecycle.ts +67 -65
  225. package/src/daemon/memory-v2-startup.ts +49 -13
  226. package/src/daemon/message-protocol.ts +4 -0
  227. package/src/daemon/message-types/conversations.ts +8 -0
  228. package/src/daemon/message-types/document-comments.ts +50 -0
  229. package/src/daemon/message-types/messages.ts +68 -1
  230. package/src/daemon/message-types/notifications.ts +21 -0
  231. package/src/daemon/message-types/surfaces.ts +3 -1
  232. package/src/daemon/message-types/web-activity.ts +57 -0
  233. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  234. package/src/daemon/pkb-reminder-builder.ts +4 -19
  235. package/src/daemon/plugin-source-watcher.ts +135 -3
  236. package/src/daemon/process-message.ts +72 -12
  237. package/src/daemon/query-complexity-router.ts +75 -0
  238. package/src/daemon/skill-memory-refresh.ts +5 -1
  239. package/src/daemon/trust-context.ts +6 -0
  240. package/src/daemon/wake-target-adapter.ts +2 -0
  241. package/src/documents/document-comments-store.test.ts +338 -0
  242. package/src/documents/document-comments-store.ts +237 -0
  243. package/src/documents/document-store.ts +202 -0
  244. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  245. package/src/export/transcript-formatter.ts +54 -20
  246. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
  247. package/src/heartbeat/heartbeat-service.ts +35 -191
  248. package/src/home/__tests__/feed-types.test.ts +40 -0
  249. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  250. package/src/home/feed-types.ts +20 -3
  251. package/src/home/home-content-refresh.ts +52 -0
  252. package/src/home/home-greeting-cache.ts +69 -0
  253. package/src/home/home-greeting.ts +94 -0
  254. package/src/home/suggested-prompts.ts +177 -9
  255. package/src/ipc/cli-client.ts +147 -45
  256. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  257. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  258. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  259. package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
  260. package/src/memory/conversation-crud.ts +133 -43
  261. package/src/memory/conversation-queries.ts +87 -1
  262. package/src/memory/conversation-title-service.ts +26 -4
  263. package/src/memory/db-init.ts +22 -0
  264. package/src/memory/delivery-crud.ts +41 -0
  265. package/src/memory/delivery-status.ts +141 -15
  266. package/src/memory/external-conversation-store.ts +32 -1
  267. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  268. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  269. package/src/memory/graph/tools.ts +6 -37
  270. package/src/memory/invite-store.ts +53 -0
  271. package/src/memory/jobs-worker.ts +21 -1
  272. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  273. package/src/memory/llm-request-log-store.ts +92 -1
  274. package/src/memory/memory-retrospective-constants.ts +28 -0
  275. package/src/memory/memory-retrospective-enqueue.ts +4 -22
  276. package/src/memory/memory-retrospective-job.ts +438 -21
  277. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  278. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  279. package/src/memory/migrations/100-core-tables.ts +1 -0
  280. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  281. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  282. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  283. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  284. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  285. package/src/memory/migrations/253-document-comments.ts +47 -0
  286. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  287. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  288. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  289. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  290. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  291. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  292. package/src/memory/migrations/index.ts +20 -0
  293. package/src/memory/migrations/registry.ts +33 -0
  294. package/src/memory/onboarding-events-store.ts +7 -0
  295. package/src/memory/schema/a2a.ts +15 -0
  296. package/src/memory/schema/calls.ts +1 -0
  297. package/src/memory/schema/conversations.ts +3 -0
  298. package/src/memory/schema/index.ts +1 -0
  299. package/src/memory/schema/inference.ts +2 -0
  300. package/src/memory/schema/infrastructure.ts +2 -0
  301. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  302. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  303. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  304. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  305. package/src/memory/v2/__tests__/injection.test.ts +221 -17
  306. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  307. package/src/memory/v2/__tests__/router.test.ts +489 -1
  308. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  309. package/src/memory/v2/activation-store.ts +14 -16
  310. package/src/memory/v2/cli-command-content.ts +19 -0
  311. package/src/memory/v2/cli-command-store.ts +304 -0
  312. package/src/memory/v2/consolidation-job.ts +14 -0
  313. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  314. package/src/memory/v2/injection-events.ts +101 -0
  315. package/src/memory/v2/injection.ts +69 -29
  316. package/src/memory/v2/page-index.ts +246 -19
  317. package/src/memory/v2/page-store.ts +18 -0
  318. package/src/memory/v2/router.ts +209 -55
  319. package/src/memory/v2/static-context.ts +4 -4
  320. package/src/memory/v2/types.ts +23 -0
  321. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  322. package/src/messaging/providers/a2a/deliver.ts +156 -0
  323. package/src/messaging/providers/gmail/client.ts +9 -2
  324. package/src/messaging/providers/index.ts +18 -3
  325. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  327. package/src/messaging/providers/slack/adapter.ts +178 -25
  328. package/src/messaging/providers/slack/api.test.ts +54 -0
  329. package/src/messaging/providers/slack/api.ts +119 -3
  330. package/src/messaging/providers/slack/client.ts +12 -0
  331. package/src/messaging/providers/slack/deep-link.ts +20 -1
  332. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  333. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  334. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  335. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  336. package/src/messaging/providers/slack/send.test.ts +77 -0
  337. package/src/messaging/providers/slack/send.ts +8 -2
  338. package/src/messaging/providers/slack/types.ts +14 -0
  339. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  340. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  341. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  342. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
  343. package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
  344. package/src/notifications/adapters/macos.ts +12 -2
  345. package/src/notifications/broadcaster.ts +29 -4
  346. package/src/notifications/conversation-seed-composer.ts +14 -2
  347. package/src/notifications/copy-composer.ts +17 -64
  348. package/src/notifications/decision-engine.ts +111 -44
  349. package/src/notifications/deferred-emit.ts +135 -0
  350. package/src/notifications/deterministic-checks.ts +96 -0
  351. package/src/notifications/emit-signal.ts +10 -1
  352. package/src/notifications/home-feed-side-effect.ts +136 -27
  353. package/src/notifications/signal.ts +0 -4
  354. package/src/notifications/types.ts +8 -0
  355. package/src/oauth/connect-orchestrator.ts +3 -0
  356. package/src/oauth/credential-token-resolver.ts +2 -0
  357. package/src/oauth/manual-token-connection.ts +19 -0
  358. package/src/oauth/oauth-store.ts +12 -0
  359. package/src/oauth/platform-connection.test.ts +43 -3
  360. package/src/oauth/platform-connection.ts +13 -4
  361. package/src/oauth/seed-providers.ts +22 -0
  362. package/src/permissions/prompter.ts +5 -2
  363. package/src/permissions/secret-prompter.ts +4 -1
  364. package/src/plugins/defaults/injectors.ts +118 -26
  365. package/src/plugins/external-plugin-loader.ts +82 -10
  366. package/src/plugins/types.ts +16 -7
  367. package/src/prompts/__tests__/system-prompt.test.ts +44 -45
  368. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  369. package/src/prompts/normalize-onboarding.ts +40 -0
  370. package/src/prompts/sections.ts +32 -14
  371. package/src/prompts/system-prompt.ts +105 -76
  372. package/src/prompts/template-detection.ts +37 -0
  373. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  374. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  375. package/src/prompts/templates/VOICE.md +3 -0
  376. package/src/prompts/templates/system-sections.ts +51 -10
  377. package/src/providers/__tests__/inference.test.ts +2 -0
  378. package/src/providers/anthropic/client.ts +132 -5
  379. package/src/providers/call-site-routing.ts +24 -6
  380. package/src/providers/connection-resolution.ts +63 -13
  381. package/src/providers/fireworks/client.ts +20 -2
  382. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  383. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  384. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  385. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  386. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  387. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  388. package/src/providers/inference/adapter-factory.ts +24 -21
  389. package/src/providers/inference/auth.ts +15 -3
  390. package/src/providers/inference/backfill.ts +14 -1
  391. package/src/providers/inference/codex-token-refresh.ts +128 -0
  392. package/src/providers/inference/connections.ts +85 -5
  393. package/src/providers/inference/resolve-auth.ts +50 -5
  394. package/src/providers/model-catalog.ts +244 -242
  395. package/src/providers/model-intents.ts +3 -3
  396. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  397. package/src/providers/openai/chat-completions-provider.ts +215 -25
  398. package/src/providers/openai/responses-provider.ts +9 -3
  399. package/src/providers/openrouter/client.ts +46 -4
  400. package/src/providers/platform-proxy/constants.ts +3 -4
  401. package/src/providers/provider-catalog-visibility.ts +3 -1
  402. package/src/providers/provider-send-message.ts +27 -12
  403. package/src/providers/registry.ts +30 -1
  404. package/src/providers/types.ts +25 -0
  405. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  406. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  407. package/src/runtime/agent-wake.ts +212 -57
  408. package/src/runtime/auth/route-policy.ts +20 -3
  409. package/src/runtime/background-job-runner.ts +26 -0
  410. package/src/runtime/channel-reply-delivery.ts +182 -47
  411. package/src/runtime/channel-retry-sweep.ts +141 -16
  412. package/src/runtime/http-server.ts +7 -16
  413. package/src/runtime/http-types.ts +7 -51
  414. package/src/runtime/pending-interactions.ts +51 -8
  415. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  416. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  417. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
  418. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  419. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  420. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  421. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  422. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  423. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  424. package/src/runtime/routes/approval-routes.ts +4 -1
  425. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  426. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  427. package/src/runtime/routes/consolidation-routes.ts +100 -0
  428. package/src/runtime/routes/content-source-routes.ts +78 -0
  429. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  430. package/src/runtime/routes/conversation-query-routes.ts +130 -12
  431. package/src/runtime/routes/conversation-routes.ts +288 -76
  432. package/src/runtime/routes/document-comments-routes.ts +287 -0
  433. package/src/runtime/routes/documents-routes.ts +33 -0
  434. package/src/runtime/routes/home-feed-routes.ts +6 -3
  435. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  436. package/src/runtime/routes/host-browser-routes.ts +8 -1
  437. package/src/runtime/routes/identity-routes.ts +21 -0
  438. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  439. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  440. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  441. package/src/runtime/routes/index.ts +14 -4
  442. package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
  443. package/src/runtime/routes/integrations/a2a.ts +294 -0
  444. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  445. package/src/runtime/routes/log-export-routes.ts +39 -0
  446. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  447. package/src/runtime/routes/notification-routes.ts +19 -2
  448. package/src/runtime/routes/question-routes.ts +4 -1
  449. package/src/runtime/routes/sanity-routes.ts +159 -0
  450. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  451. package/src/runtime/routes/subagents-routes.ts +41 -0
  452. package/src/runtime/services/conversation-serializer.ts +30 -4
  453. package/src/schedule/integration-status.ts +3 -1
  454. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  455. package/src/security/oauth2-device-code.ts +307 -0
  456. package/src/security/oauth2.ts +26 -9
  457. package/src/security/secure-keys.ts +5 -0
  458. package/src/skills/catalog-install.ts +6 -2
  459. package/src/subagent/manager.ts +2 -0
  460. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  461. package/src/tools/browser/browser-execution.ts +93 -0
  462. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  463. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  464. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  465. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  466. package/src/tools/browser/cdp-client/factory.ts +87 -3
  467. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  468. package/src/tools/browser/cdp-client/types.ts +36 -0
  469. package/src/tools/browser/pinned-tabs.ts +90 -0
  470. package/src/tools/document/document-comment-tool.test.ts +379 -0
  471. package/src/tools/document/document-comment-tool.ts +156 -0
  472. package/src/tools/document/document-tool.ts +128 -2
  473. package/src/tools/memory/register.ts +1 -9
  474. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  475. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  476. package/src/tools/network/domain-normalize.ts +17 -0
  477. package/src/tools/network/web-fetch.ts +213 -64
  478. package/src/tools/network/web-search.ts +191 -66
  479. package/src/tools/registry.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +3 -2
  481. package/src/tools/tool-approval-handler.ts +19 -12
  482. package/src/tools/types.ts +41 -2
  483. package/src/tools/ui-surface/definitions.ts +3 -1
  484. package/src/types/onboarding-context.ts +4 -0
  485. package/src/util/__tests__/favicon.test.ts +84 -0
  486. package/src/util/favicon.ts +40 -0
  487. package/src/util/platform.ts +0 -5
  488. package/src/workspace/git-service.ts +75 -4
  489. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  490. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  491. package/src/workspace/migrations/registry.ts +4 -0
  492. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  493. package/src/config/bundled-skills/document/SKILL.md +0 -54
  494. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  495. package/src/daemon/seed-files.ts +0 -18
  496. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  497. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
  498. package/src/runtime/routes/interface-routes.ts +0 -43
  499. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  500. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  501. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  502. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  503. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Tests for {@link searchPlugins}.
3
+ *
4
+ * Network is replaced with an in-memory fixture passed via the `fetch`
5
+ * dependency — no globals are monkey-patched and no `--test-hook` exports
6
+ * leak into production code.
7
+ */
8
+
9
+ import { describe, expect, test } from "bun:test";
10
+
11
+ import {
12
+ type FetchLike,
13
+ InvalidSearchPatternError,
14
+ searchPlugins,
15
+ } from "../search-plugins.js";
16
+
17
+ /**
18
+ * Build a GitHub Contents API fixture from an in-memory directory listing.
19
+ *
20
+ * `entries` maps each name under `experimental/plugins/` to its `type`. The
21
+ * fixture answers GET requests against
22
+ * - `https://api.github.com/repos/vellum-ai/vellum-assistant/contents/experimental/plugins...`
23
+ * and returns 500 for anything else (forces test bugs to surface loudly).
24
+ */
25
+ function fixtureFetch(
26
+ entries: Record<string, "dir" | "file" | "symlink" | "submodule">,
27
+ ): FetchLike {
28
+ const PREFIX_API =
29
+ "https://api.github.com/repos/vellum-ai/vellum-assistant/contents/experimental/plugins";
30
+
31
+ return (async (input: RequestInfo | URL) => {
32
+ const url = typeof input === "string" ? input : input.toString();
33
+ if (!url.startsWith(PREFIX_API)) {
34
+ return new Response("unexpected url: " + url, { status: 500 });
35
+ }
36
+ const body = Object.entries(entries).map(([name, type]) => ({
37
+ name,
38
+ path: `experimental/plugins/${name}`,
39
+ type,
40
+ size: type === "file" ? 1 : 0,
41
+ download_url:
42
+ type === "file"
43
+ ? `https://raw.githubusercontent.com/vellum-ai/vellum-assistant/main/experimental/plugins/${name}`
44
+ : null,
45
+ }));
46
+ return new Response(JSON.stringify(body), {
47
+ status: 200,
48
+ headers: { "content-type": "application/json" },
49
+ });
50
+ }) as FetchLike;
51
+ }
52
+
53
+ describe("searchPlugins", () => {
54
+ test("matches the query as a case-insensitive regex against directory names", async () => {
55
+ const result = await searchPlugins(
56
+ { query: "memory" },
57
+ {
58
+ fetch: fixtureFetch({
59
+ "simple-memory": "dir",
60
+ "memory-graph": "dir",
61
+ "git-tools": "dir",
62
+ }),
63
+ },
64
+ );
65
+
66
+ expect(result.matches.map((m) => m.name)).toEqual([
67
+ "memory-graph",
68
+ "simple-memory",
69
+ ]);
70
+ expect(result.matches[0]!.path).toBe("experimental/plugins/memory-graph");
71
+ expect(result.query).toBe("memory");
72
+ expect(result.ref).toBe("main");
73
+ });
74
+
75
+ test("matches regardless of query casing (case-insensitive)", async () => {
76
+ const result = await searchPlugins(
77
+ { query: "MEMORY" },
78
+ { fetch: fixtureFetch({ "simple-memory": "dir" }) },
79
+ );
80
+ expect(result.matches.map((m) => m.name)).toEqual(["simple-memory"]);
81
+ });
82
+
83
+ test("anchored patterns work without escaping", async () => {
84
+ const result = await searchPlugins(
85
+ { query: "^memory-" },
86
+ {
87
+ fetch: fixtureFetch({
88
+ "memory-graph": "dir",
89
+ "simple-memory": "dir",
90
+ }),
91
+ },
92
+ );
93
+ expect(result.matches.map((m) => m.name)).toEqual(["memory-graph"]);
94
+ });
95
+
96
+ test("empty query matches all directories", async () => {
97
+ const result = await searchPlugins(
98
+ { query: "" },
99
+ {
100
+ fetch: fixtureFetch({
101
+ "simple-memory": "dir",
102
+ "memory-graph": "dir",
103
+ "git-tools": "dir",
104
+ }),
105
+ },
106
+ );
107
+ expect(result.matches.map((m) => m.name)).toEqual([
108
+ "git-tools",
109
+ "memory-graph",
110
+ "simple-memory",
111
+ ]);
112
+ });
113
+
114
+ test("skips entries that are not directories", async () => {
115
+ const result = await searchPlugins(
116
+ { query: "" },
117
+ {
118
+ fetch: fixtureFetch({
119
+ "simple-memory": "dir",
120
+ "README.md": "file",
121
+ "broken-symlink": "symlink",
122
+ "old-plugin": "submodule",
123
+ }),
124
+ },
125
+ );
126
+ expect(result.matches.map((m) => m.name)).toEqual(["simple-memory"]);
127
+ });
128
+
129
+ test("rejects invalid regex patterns up front (no network call)", async () => {
130
+ let fetchCalled = false;
131
+ const fetch: FetchLike = (async () => {
132
+ fetchCalled = true;
133
+ return new Response("", { status: 200 });
134
+ }) as FetchLike;
135
+
136
+ await expect(
137
+ searchPlugins({ query: "(unterminated" }, { fetch }),
138
+ ).rejects.toBeInstanceOf(InvalidSearchPatternError);
139
+ expect(fetchCalled).toBe(false);
140
+ });
141
+
142
+ test("empty result set on no matches", async () => {
143
+ const result = await searchPlugins(
144
+ { query: "nothing-matches" },
145
+ { fetch: fixtureFetch({ "simple-memory": "dir" }) },
146
+ );
147
+ expect(result.matches).toEqual([]);
148
+ });
149
+
150
+ test("respects `ref` option by forwarding to GitHub", async () => {
151
+ // The CLI does not surface a `--ref` flag (the source-path convention
152
+ // may change), but the underlying function keeps `ref` for test
153
+ // injection and future internal callers.
154
+ let seenRef: string | undefined;
155
+ const result = await searchPlugins(
156
+ { query: "memory", ref: "feat-branch" },
157
+ {
158
+ fetch: (async (input: RequestInfo | URL) => {
159
+ const url = typeof input === "string" ? input : input.toString();
160
+ const m = /[?&]ref=([^&]+)/.exec(url);
161
+ seenRef = m ? decodeURIComponent(m[1]!) : undefined;
162
+ return new Response(
163
+ JSON.stringify([
164
+ {
165
+ name: "simple-memory",
166
+ path: "experimental/plugins/simple-memory",
167
+ type: "dir",
168
+ size: 0,
169
+ download_url: null,
170
+ },
171
+ ]),
172
+ { status: 200, headers: { "content-type": "application/json" } },
173
+ );
174
+ }) as FetchLike,
175
+ },
176
+ );
177
+
178
+ expect(seenRef).toBe("feat-branch");
179
+ expect(result.ref).toBe("feat-branch");
180
+ expect(result.matches.map((m) => m.name)).toEqual(["simple-memory"]);
181
+ });
182
+
183
+ test("HTTP 5xx from GitHub propagates with the status code", async () => {
184
+ await expect(
185
+ searchPlugins(
186
+ { query: "memory" },
187
+ {
188
+ fetch: (async () =>
189
+ new Response("upstream broken", { status: 503 })) as FetchLike,
190
+ },
191
+ ),
192
+ ).rejects.toThrow(/HTTP 503/);
193
+ });
194
+
195
+ test("HTTP 403 (rate-limited / forbidden) surfaces as an error", async () => {
196
+ await expect(
197
+ searchPlugins(
198
+ { query: "memory" },
199
+ {
200
+ fetch: (async () =>
201
+ new Response("rate limit exceeded", { status: 403 })) as FetchLike,
202
+ },
203
+ ),
204
+ ).rejects.toThrow(/HTTP 403/);
205
+ });
206
+
207
+ test("404 on the plugins prefix surfaces as an error (not silently empty)", async () => {
208
+ // Distinct from `installPlugin`, where 404 on a specific plugin name is
209
+ // normal "not found". For the search, 404 on the prefix means the
210
+ // canonical source path itself is gone — that's an upstream problem
211
+ // worth surfacing, not a clean empty result.
212
+ await expect(
213
+ searchPlugins(
214
+ { query: "memory" },
215
+ {
216
+ fetch: (async () =>
217
+ new Response("not found", { status: 404 })) as FetchLike,
218
+ },
219
+ ),
220
+ ).rejects.toThrow(/HTTP 404/);
221
+ });
222
+
223
+ test("returns matches sorted by name", async () => {
224
+ const result = await searchPlugins(
225
+ { query: "" },
226
+ {
227
+ fetch: fixtureFetch({
228
+ "zeta-plugin": "dir",
229
+ "alpha-plugin": "dir",
230
+ "mu-plugin": "dir",
231
+ }),
232
+ },
233
+ );
234
+ expect(result.matches.map((m) => m.name)).toEqual([
235
+ "alpha-plugin",
236
+ "mu-plugin",
237
+ "zeta-plugin",
238
+ ]);
239
+ });
240
+
241
+ test("sends no Authorization header (canonical source is a public repo)", async () => {
242
+ let seenAuth: string | undefined;
243
+ let seenUserAgent: string | undefined;
244
+ await searchPlugins(
245
+ { query: "memory" },
246
+ {
247
+ fetch: (async (_input: RequestInfo | URL, init?: RequestInit) => {
248
+ const headers = init?.headers as Record<string, string> | undefined;
249
+ seenAuth = headers?.Authorization;
250
+ seenUserAgent = headers?.["User-Agent"];
251
+ return new Response("[]", {
252
+ status: 200,
253
+ headers: { "content-type": "application/json" },
254
+ });
255
+ }) as FetchLike,
256
+ },
257
+ );
258
+ expect(seenAuth).toBeUndefined();
259
+ expect(seenUserAgent).toBe("vellum-assistant-cli");
260
+ });
261
+ });
@@ -286,19 +286,18 @@ async function copyFile(
286
286
 
287
287
  /**
288
288
  * Wraps `fetchFn` with the headers we want to send to GitHub for every
289
- * request. Honors `GITHUB_TOKEN` when present so users who hit the
290
- * unauthenticated rate limit can opt into a higher cap.
289
+ * request. Unauthenticated the canonical source is a public repo, so
290
+ * there is nothing for an `Authorization` header to do.
291
291
  */
292
292
  async function githubFetch(
293
293
  url: string,
294
294
  accept: string,
295
295
  fetchFn: FetchLike,
296
296
  ): Promise<Response> {
297
- const headers: Record<string, string> = {
298
- Accept: accept,
299
- "User-Agent": "vellum-assistant-cli",
300
- };
301
- const token = process.env.GITHUB_TOKEN?.trim();
302
- if (token) headers.Authorization = `Bearer ${token}`;
303
- return fetchFn(url, { headers });
297
+ return fetchFn(url, {
298
+ headers: {
299
+ Accept: accept,
300
+ "User-Agent": "vellum-assistant-cli",
301
+ },
302
+ });
304
303
  }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Search for plugin directories in the canonical GitHub source.
3
+ *
4
+ * Lists `vellum-ai/vellum-assistant/experimental/plugins/` at the configured
5
+ * git ref and filters the directory entries by case-insensitive ECMAScript
6
+ * regex. A plain query like `"memory"` matches anywhere in the name; anchors
7
+ * like `"^simple"` work without escaping.
8
+ *
9
+ * Designed for direct programmatic use. The CLI command
10
+ * `assistant plugins search <query>` is a thin wrapper that supplies
11
+ * production deps (`globalThis.fetch`) and formats the result for the
12
+ * terminal; downstream callers may supply their own `fetch` (e.g. a
13
+ * retry-decorated client, or a test fixture).
14
+ */
15
+
16
+ import type { FetchLike } from "./install-from-github.js";
17
+ import { DEFAULT_PLUGIN_REF } from "./install-from-github.js";
18
+
19
+ // Re-export the dep-injection type so callers can grab everything they need
20
+ // from one module rather than reaching into `install-from-github.js`.
21
+ export type { FetchLike } from "./install-from-github.js";
22
+
23
+ const PLUGIN_SOURCE_OWNER = "vellum-ai";
24
+ const PLUGIN_SOURCE_REPO = "vellum-assistant";
25
+ const PLUGIN_SOURCE_PATH_PREFIX = "experimental/plugins";
26
+
27
+ /** Entry shape returned by the GitHub Contents API for a directory listing. */
28
+ interface GitHubContentEntry {
29
+ readonly name: string;
30
+ readonly path: string;
31
+ readonly type: "file" | "dir" | "symlink" | "submodule";
32
+ readonly size: number;
33
+ readonly download_url: string | null;
34
+ }
35
+
36
+ /** Options that control the search. */
37
+ export interface SearchPluginsOptions {
38
+ /**
39
+ * ECMAScript regex pattern. Matched case-insensitively against directory
40
+ * names. Empty string matches everything.
41
+ */
42
+ readonly query: string;
43
+ /** Git ref to list from. Defaults to {@link DEFAULT_PLUGIN_REF}. */
44
+ readonly ref?: string;
45
+ }
46
+
47
+ /** Dependencies injected by the caller. */
48
+ export interface SearchPluginsDeps {
49
+ /** HTTP client. Production callers pass `globalThis.fetch.bind(globalThis)`. */
50
+ readonly fetch: FetchLike;
51
+ }
52
+
53
+ /** One matching plugin directory. */
54
+ export interface PluginSearchMatch {
55
+ /** Directory name under `experimental/plugins/`. */
56
+ readonly name: string;
57
+ /** Path within the repo (e.g. `experimental/plugins/<name>`). */
58
+ readonly path: string;
59
+ }
60
+
61
+ /** Search result envelope. */
62
+ export interface SearchPluginsResult {
63
+ readonly query: string;
64
+ readonly ref: string;
65
+ readonly matches: readonly PluginSearchMatch[];
66
+ }
67
+
68
+ /** Caller passed a query that doesn't compile as an ECMAScript regex. */
69
+ export class InvalidSearchPatternError extends Error {
70
+ constructor(pattern: string, cause: unknown) {
71
+ const detail = cause instanceof Error ? cause.message : String(cause);
72
+ super(`Invalid regex pattern ${JSON.stringify(pattern)}: ${detail}`);
73
+ this.name = "InvalidSearchPatternError";
74
+ }
75
+ }
76
+
77
+ /**
78
+ * List directories under `experimental/plugins/` at {@link opts.ref} and
79
+ * filter by {@link opts.query}.
80
+ *
81
+ * Only `type === "dir"` entries are returned — `experimental/plugins/`
82
+ * follows a convention where each plugin lives in its own directory, so
83
+ * loose files at the prefix are not plugins.
84
+ */
85
+ export async function searchPlugins(
86
+ opts: SearchPluginsOptions,
87
+ deps: SearchPluginsDeps,
88
+ ): Promise<SearchPluginsResult> {
89
+ const ref = opts.ref ?? DEFAULT_PLUGIN_REF;
90
+
91
+ // Compile the matcher up front so an invalid regex fails before we hit
92
+ // the network — keeps "user typo" cheap to recover from.
93
+ const matcher = buildMatcher(opts.query);
94
+
95
+ const entries = await listDir(PLUGIN_SOURCE_PATH_PREFIX, ref, deps.fetch);
96
+
97
+ const matches: PluginSearchMatch[] = [];
98
+ for (const entry of entries) {
99
+ if (entry.type !== "dir") continue;
100
+ if (!matcher(entry.name)) continue;
101
+ matches.push({ name: entry.name, path: entry.path });
102
+ }
103
+ matches.sort((a, b) => a.name.localeCompare(b.name));
104
+
105
+ return { query: opts.query, ref, matches };
106
+ }
107
+
108
+ function buildMatcher(query: string): (name: string) => boolean {
109
+ let re: RegExp;
110
+ try {
111
+ re = new RegExp(query, "i");
112
+ } catch (err) {
113
+ throw new InvalidSearchPatternError(query, err);
114
+ }
115
+ return (name) => re.test(name);
116
+ }
117
+
118
+ async function listDir(
119
+ apiPath: string,
120
+ ref: string,
121
+ fetchFn: FetchLike,
122
+ ): Promise<readonly GitHubContentEntry[]> {
123
+ const url =
124
+ `https://api.github.com/repos/${PLUGIN_SOURCE_OWNER}/${PLUGIN_SOURCE_REPO}` +
125
+ `/contents/${encodeURIComponent(apiPath).replaceAll("%2F", "/")}` +
126
+ `?ref=${encodeURIComponent(ref)}`;
127
+
128
+ const res = await githubFetch(url, fetchFn);
129
+ if (!res.ok) {
130
+ // Unlike `installPlugin`, where 404 on a specific plugin name is a
131
+ // legitimate "not found" outcome, 404 on the plugins prefix itself
132
+ // means the canonical source path is gone — surface it as an error
133
+ // rather than silently returning empty results.
134
+ throw new Error(
135
+ `GitHub contents listing failed for ${apiPath} @ ${ref}: HTTP ${res.status}`,
136
+ );
137
+ }
138
+
139
+ const body = (await res.json()) as unknown;
140
+ if (!Array.isArray(body)) {
141
+ // A non-array body for a /contents/<dir> path means the path is a
142
+ // file, not a directory — treat the prefix as empty rather than crash.
143
+ return [];
144
+ }
145
+ return body as readonly GitHubContentEntry[];
146
+ }
147
+
148
+ /**
149
+ * Wraps `fetchFn` with the headers we want to send to GitHub for every
150
+ * request. Unauthenticated — the canonical source is a public repo, mirroring
151
+ * `installPlugin` which uses the same envelope.
152
+ */
153
+ async function githubFetch(
154
+ url: string,
155
+ fetchFn: FetchLike,
156
+ ): Promise<Response> {
157
+ return fetchFn(url, {
158
+ headers: {
159
+ Accept: "application/vnd.github+json",
160
+ "User-Agent": "vellum-assistant-cli",
161
+ },
162
+ });
163
+ }
@@ -62,6 +62,20 @@ import { log } from "./logger.js";
62
62
  */
63
63
  export async function buildCliProgram(): Promise<Command> {
64
64
  await initFeatureFlagOverrides({ retryBackoffsMs: [], callTimeoutMs: 200 });
65
+ return buildCliProgramTree();
66
+ }
67
+
68
+ /**
69
+ * Synchronously build the CLI program tree without pre-populating the
70
+ * feature-flag cache. Use this from inside the daemon, where flags are
71
+ * already initialized — calling `buildCliProgram` from there would round-trip
72
+ * to the gateway unnecessarily.
73
+ *
74
+ * Same shape as `buildCliProgram` minus the async feature-flag init: registers
75
+ * the full subcommand set (conditionally gated on email / external-plugins
76
+ * flags via `getConfigReadOnly()`) and installs the workspace-preAction hook.
77
+ */
78
+ export function buildCliProgramTree(): Command {
65
79
  const program = new Command();
66
80
 
67
81
  program
@@ -3,12 +3,11 @@
3
3
  * 1. Explicit value provided in `opts.explicit`
4
4
  * 2. `__SKILL_CONTEXT_JSON` env var (set by skill sandbox runner)
5
5
  * 3. `__CONVERSATION_ID` env var (set by bash tool subprocess)
6
- * 4. Throw with the provided `failureHelp` message
6
+ * 4. `undefined`
7
7
  */
8
- export function resolveConversationId(opts: {
9
- explicit?: string;
10
- failureHelp: string;
11
- }): string {
8
+ export function tryResolveConversationId(
9
+ opts: { explicit?: string } = {},
10
+ ): string | undefined {
12
11
  if (opts.explicit) return opts.explicit;
13
12
 
14
13
  const contextJson = process.env.__SKILL_CONTEXT_JSON;
@@ -26,5 +25,18 @@ export function resolveConversationId(opts: {
26
25
  const envConvId = process.env.__CONVERSATION_ID;
27
26
  if (envConvId && typeof envConvId === "string") return envConvId;
28
27
 
28
+ return undefined;
29
+ }
30
+
31
+ /**
32
+ * Same precedence as `tryResolveConversationId` but throws with the
33
+ * provided `failureHelp` when no source produces a value.
34
+ */
35
+ export function resolveConversationId(opts: {
36
+ explicit?: string;
37
+ failureHelp: string;
38
+ }): string {
39
+ const resolved = tryResolveConversationId({ explicit: opts.explicit });
40
+ if (resolved) return resolved;
29
41
  throw new Error(opts.failureHelp);
30
42
  }
@@ -110,20 +110,22 @@ function parseRegistryToDefaults(parsed: unknown): FeatureFlagDefaultsRegistry {
110
110
  }
111
111
 
112
112
  // ---------------------------------------------------------------------------
113
- // Override loading — reads from gateway IPC socket or local file
113
+ // Override loading — reads from gateway IPC socket
114
114
  // ---------------------------------------------------------------------------
115
115
 
116
116
  /**
117
- * Module-level cache of feature flag override values. Populated lazily on
118
- * first access, invalidated by `clearFeatureFlagOverridesCache()`.
117
+ * Module-level cache of feature flag override values. Populated by
118
+ * `initFeatureFlagOverrides()` at startup, invalidated by
119
+ * `clearFeatureFlagOverridesCache()`.
119
120
  */
120
121
  let cachedOverrides: Record<string, boolean> | null = null;
121
122
 
122
123
  /**
123
- * True when `cachedOverrides` was populated by the gateway IPC fetch (or
124
- * preseeded by a test). False/unset when the cache was populated by the sync
125
- * file fallback in `loadOverrides()`, which must not prevent a subsequent
126
- * authoritative gateway fetch from running.
124
+ * True when `cachedOverrides` was populated by the gateway IPC fetch or
125
+ * preseeded by a test via `_setOverridesForTesting()`. Guards
126
+ * `initFeatureFlagOverrides()` from clobbering an existing populated cache
127
+ * when called a second time (e.g. by a CLI entry point after the daemon
128
+ * has already initialized).
127
129
  */
128
130
  let cachedOverridesFromGateway = false;
129
131
 
@@ -247,59 +249,30 @@ function loadOverrides(): Record<string, boolean> {
247
249
  return cachedOverrides ?? {};
248
250
  }
249
251
 
250
- // ---------------------------------------------------------------------------
251
- // Remote values — platform-pushed flags cached in a local JSON file
252
- // ---------------------------------------------------------------------------
253
-
254
252
  /**
255
- * Module-level cache of remote feature flag values. Populated lazily on
256
- * first access, invalidated by `clearFeatureFlagOverridesCache()`.
257
- */
258
- let cachedRemoteValues: Record<string, boolean> | null = null;
259
-
260
- /**
261
- * Load remote values with module-level caching.
253
+ * Invalidate the cached overrides so the next call to
254
+ * `isAssistantFeatureFlagEnabled` re-reads from the gateway.
262
255
  *
263
- * Remote values are now always included in the gateway IPC response (merged
264
- * server-side), so this only returns the injected test cache. In production,
265
- * remote values flow through the overrides cache.
266
- */
267
- function loadRemoteValues(): Record<string, boolean> {
268
- return cachedRemoteValues ?? {};
269
- }
270
-
271
- /**
272
- * Invalidate the cached override and remote values so the next call to
273
- * `isAssistantFeatureFlagEnabled` re-reads from the source.
274
- *
275
- * Called by the config watcher when the feature-flags file changes.
256
+ * Used by tests between cases to reset module state.
276
257
  */
277
258
  export function clearFeatureFlagOverridesCache(): void {
278
259
  cachedOverrides = null;
279
260
  cachedOverridesFromGateway = false;
280
- cachedRemoteValues = null;
281
261
  }
282
262
 
283
263
  /**
284
264
  * Directly inject override values into the module-level cache.
285
265
  *
286
- * **Test-only** — bypasses file/gateway loading so unit tests can control
287
- * flag state without writing to disk. Production code should never call this;
288
- * use `clearFeatureFlagOverridesCache()` instead and let the resolver
289
- * re-read from the appropriate source.
290
- *
291
- * Forces `cachedRemoteValues` to an empty record (not `null`) so the resolver
292
- * does not fall through to reading `feature-flags-remote.json` from disk. This
293
- * matters because a developer's local remote-cache file can leak platform-set
294
- * values into the test environment (e.g. `email-channel: true`), defeating
295
- * test isolation.
266
+ * **Test-only** — bypasses the gateway IPC fetch so unit tests can control
267
+ * flag state without standing up a real gateway. Production code should
268
+ * never call this; use `clearFeatureFlagOverridesCache()` instead and let
269
+ * the resolver re-read from the gateway.
296
270
  */
297
271
  export function _setOverridesForTesting(
298
272
  overrides: Record<string, boolean>,
299
273
  ): void {
300
274
  cachedOverrides = { ...overrides };
301
275
  cachedOverridesFromGateway = true;
302
- cachedRemoteValues = {};
303
276
  }
304
277
 
305
278
  // ---------------------------------------------------------------------------
@@ -310,9 +283,11 @@ export function _setOverridesForTesting(
310
283
  * Resolve whether an assistant feature flag is enabled.
311
284
  *
312
285
  * Resolution order:
313
- * 1. Override from gateway IPC socket
314
- * 2. defaults registry `defaultEnabled` (for declared assistant-scope keys)
315
- * 3. `true` (for undeclared keys with no override)
286
+ * 1. Override from the gateway IPC fetch (includes platform-pushed remote
287
+ * values, which the gateway merges server-side: persisted > remote >
288
+ * registry)
289
+ * 2. Registry `defaultEnabled` (for declared assistant-scope keys)
290
+ * 3. `true` (for undeclared keys with no override)
316
291
  */
317
292
  export function isAssistantFeatureFlagEnabled(
318
293
  key: string,
@@ -322,18 +297,13 @@ export function isAssistantFeatureFlagEnabled(
322
297
  const declared = defaults[key];
323
298
  const overrides = loadOverrides();
324
299
 
325
- // 1. Check overrides from gateway / local file
300
+ // 1. Check overrides from the gateway IPC cache.
326
301
  const explicit = overrides[key];
327
302
  if (typeof explicit === "boolean") return explicit;
328
303
 
329
- // 2. Check remote values (platform-pushed, cached locally)
330
- const remote = loadRemoteValues();
331
- const remoteValue = remote[key];
332
- if (typeof remoteValue === "boolean") return remoteValue;
333
-
334
- // 3. For declared keys, use the registry default
304
+ // 2. For declared keys, use the registry default.
335
305
  if (declared) return declared.defaultEnabled;
336
306
 
337
- // 4. Undeclared keys with no persisted override default to enabled
307
+ // 3. Undeclared keys with no override default to enabled.
338
308
  return true;
339
309
  }