@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,302 @@
1
+ /**
2
+ * Tests for PluginSourceWatcher — filesystem watcher that detects plugin
3
+ * directory changes and triggers debounced reregistration.
4
+ *
5
+ * Key regression: the watcher must survive (and recover from) the Linux/Bun
6
+ * recursive-watch limitation where subdirectories created after watch starts
7
+ * are silently dropped. We test that close→reopen + rescan catches these.
8
+ */
9
+
10
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Mocks — must be set up before importing the module under test
14
+ // ---------------------------------------------------------------------------
15
+
16
+ const TEST_PLUGINS_DIR = "/tmp/test-plugins";
17
+
18
+ let capturedWatchCallback:
19
+ | ((eventType: string, filename: string | null) => void)
20
+ | null = null;
21
+ let mockWatchShouldThrow = false;
22
+ const mockWatcher = { close: mock(() => {}) };
23
+
24
+ const mockWatch = mock(
25
+ (
26
+ _path: string,
27
+ _opts: Record<string, unknown>,
28
+ callback: (eventType: string, filename: string | null) => void,
29
+ ) => {
30
+ if (mockWatchShouldThrow) throw new Error("watch failed");
31
+ capturedWatchCallback = callback;
32
+ return mockWatcher;
33
+ },
34
+ );
35
+
36
+ const mockRereadirSync = mock((_path: string): string[] => []);
37
+
38
+ let mockGetRegisteredPluginImpl: (name: string) => unknown | undefined = (
39
+ _name,
40
+ ) => undefined;
41
+ const mockGetRegisteredPlugin = mock((name: string) =>
42
+ mockGetRegisteredPluginImpl(name),
43
+ );
44
+
45
+ let mockReregisterExternalPluginImpl = mock(async (_name: string) => {});
46
+ const mockReregisterExternalPlugin = mock(async (name: string) =>
47
+ mockReregisterExternalPluginImpl(name),
48
+ );
49
+
50
+ mock.module("node:fs", () => {
51
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
52
+ const actualFs = require("node:fs");
53
+ return {
54
+ ...actualFs,
55
+ watch: mockWatch,
56
+ readdirSync: mockRereadirSync,
57
+ };
58
+ });
59
+
60
+ mock.module("../plugins/registry.js", () => ({
61
+ getRegisteredPlugin: mockGetRegisteredPlugin,
62
+ }));
63
+
64
+ mock.module("../util/platform.js", () => ({
65
+ getWorkspacePluginsDir: mock(() => TEST_PLUGINS_DIR),
66
+ }));
67
+
68
+ mock.module("../daemon/external-plugins-bootstrap.js", () => ({
69
+ reregisterExternalPlugin: mockReregisterExternalPlugin,
70
+ }));
71
+
72
+ mock.module("../util/logger.js", () => ({
73
+ getLogger: mock(() => ({
74
+ info: mock(() => {}),
75
+ warn: mock(() => {}),
76
+ })),
77
+ }));
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Import after mocks
81
+ // ---------------------------------------------------------------------------
82
+
83
+ import { PluginSourceWatcher } from "../daemon/plugin-source-watcher.js";
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // Tests
87
+ // ---------------------------------------------------------------------------
88
+
89
+ describe("PluginSourceWatcher", () => {
90
+ beforeEach(() => {
91
+ PluginSourceWatcher.resetForTests();
92
+ capturedWatchCallback = null;
93
+ mockWatchShouldThrow = false;
94
+ mockWatcher.close.mockClear();
95
+ mockWatch.mockClear();
96
+ mockRereadirSync.mockClear();
97
+ mockGetRegisteredPlugin.mockClear();
98
+ mockReregisterExternalPlugin.mockClear();
99
+ mockGetRegisteredPluginImpl = (_name: string) => undefined;
100
+ mockReregisterExternalPluginImpl = mock(async (_name: string) => {});
101
+ });
102
+
103
+ afterEach(() => {
104
+ const watcher = PluginSourceWatcher.getInstance();
105
+ watcher.stop();
106
+ });
107
+
108
+ test("start() creates a recursive watcher on the plugins directory", () => {
109
+ const watcher = PluginSourceWatcher.getInstance();
110
+ watcher.start();
111
+ expect(capturedWatchCallback).not.toBeNull();
112
+ });
113
+
114
+ test("plugin directory creation triggers rebuild", async () => {
115
+ const watcher = PluginSourceWatcher.getInstance();
116
+ watcher.start();
117
+
118
+ capturedWatchCallback!("change", "my-plugin");
119
+
120
+ // Wait for debounce
121
+ await new Promise((r) => setTimeout(r, 600));
122
+ expect(mockReregisterExternalPlugin).toHaveBeenCalledWith("my-plugin");
123
+ });
124
+
125
+ test("nested file change within plugin triggers rebuild", async () => {
126
+ const watcher = PluginSourceWatcher.getInstance();
127
+ watcher.start();
128
+
129
+ capturedWatchCallback!("change", "my-plugin/src/index.ts");
130
+
131
+ await new Promise((r) => setTimeout(r, 600));
132
+ expect(mockReregisterExternalPlugin).toHaveBeenCalledWith("my-plugin");
133
+ });
134
+
135
+ test("deeply nested file change triggers rebuild", async () => {
136
+ const watcher = PluginSourceWatcher.getInstance();
137
+ watcher.start();
138
+
139
+ capturedWatchCallback!("change", "my-plugin/src/handlers/util/helper.ts");
140
+
141
+ await new Promise((r) => setTimeout(r, 600));
142
+ expect(mockReregisterExternalPlugin).toHaveBeenCalledWith("my-plugin");
143
+ });
144
+
145
+ test("dotfiles in plugins root are ignored", async () => {
146
+ const watcher = PluginSourceWatcher.getInstance();
147
+ watcher.start();
148
+
149
+ capturedWatchCallback!("change", ".DS_Store");
150
+
151
+ await new Promise((r) => setTimeout(r, 600));
152
+ expect(mockReregisterExternalPlugin).not.toHaveBeenCalled();
153
+ });
154
+
155
+ test("null filename is ignored", async () => {
156
+ const watcher = PluginSourceWatcher.getInstance();
157
+ watcher.start();
158
+
159
+ capturedWatchCallback!("change", null);
160
+
161
+ await new Promise((r) => setTimeout(r, 600));
162
+ expect(mockReregisterExternalPlugin).not.toHaveBeenCalled();
163
+ });
164
+
165
+ test("rapid changes to same plugin are debounced into single rebuild", async () => {
166
+ const watcher = PluginSourceWatcher.getInstance();
167
+ watcher.start();
168
+
169
+ capturedWatchCallback!("change", "my-plugin/src/index.ts");
170
+ capturedWatchCallback!("change", "my-plugin/src/handlers.ts");
171
+ capturedWatchCallback!("change", "my-plugin/package.json");
172
+
173
+ await new Promise((r) => setTimeout(r, 600));
174
+ expect(mockReregisterExternalPlugin).toHaveBeenCalledTimes(1);
175
+ expect(mockReregisterExternalPlugin).toHaveBeenCalledWith("my-plugin");
176
+ });
177
+
178
+ test("changes to different plugins trigger separate rebuilds (debounced)", async () => {
179
+ const watcher = PluginSourceWatcher.getInstance();
180
+ watcher.start();
181
+
182
+ capturedWatchCallback!("change", "plugin-a/src/index.ts");
183
+ capturedWatchCallback!("change", "plugin-b/src/index.ts");
184
+
185
+ await new Promise((r) => setTimeout(r, 600));
186
+ expect(mockReregisterExternalPlugin).toHaveBeenCalledTimes(2);
187
+ expect(mockReregisterExternalPlugin).toHaveBeenNthCalledWith(1, "plugin-a");
188
+ expect(mockReregisterExternalPlugin).toHaveBeenNthCalledWith(2, "plugin-b");
189
+ });
190
+
191
+ test("stop() closes watcher and cancels pending rebuilds", () => {
192
+ const watcher = PluginSourceWatcher.getInstance();
193
+ watcher.start();
194
+
195
+ capturedWatchCallback!("change", "my-plugin/src/index.ts");
196
+ watcher.stop();
197
+
198
+ expect(mockWatcher.close).toHaveBeenCalledTimes(1);
199
+ // No rebuild should fire after stop
200
+ expect(mockReregisterExternalPlugin).not.toHaveBeenCalled();
201
+ });
202
+
203
+ test("ensureStarted() initializes watcher after start() if watch coverage was lost", () => {
204
+ const watcher = PluginSourceWatcher.getInstance();
205
+ watcher.start();
206
+ expect(capturedWatchCallback).not.toBeNull();
207
+
208
+ // Simulate lost coverage while the daemon lifecycle is still started
209
+ // (e.g. a previous watch attempt failed after startup).
210
+ (watcher as unknown as { watcher: unknown }).watcher = null;
211
+ capturedWatchCallback = null;
212
+
213
+ watcher.ensureStarted();
214
+ expect(capturedWatchCallback).not.toBeNull();
215
+ });
216
+
217
+ test("ensureStarted() is a no-op when watcher is already running", () => {
218
+ const watcher = PluginSourceWatcher.getInstance();
219
+ watcher.start();
220
+ const callCountAfterStart = mockWatch.mock.calls.length;
221
+
222
+ watcher.ensureStarted();
223
+ expect(mockWatch.mock.calls.length).toBe(callCountAfterStart);
224
+ });
225
+
226
+ test("watcher restart keeps the previous watcher active if replacement fails", async () => {
227
+ const watcher = PluginSourceWatcher.getInstance();
228
+ watcher.start();
229
+ const firstCallback = capturedWatchCallback;
230
+
231
+ mockWatchShouldThrow = true;
232
+ capturedWatchCallback!("change", "my-plugin/src/index.ts");
233
+
234
+ await new Promise((r) => setTimeout(r, 600));
235
+
236
+ expect(mockWatcher.close).not.toHaveBeenCalled();
237
+ expect((watcher as unknown as { watcher: unknown }).watcher).toBe(
238
+ mockWatcher,
239
+ );
240
+ expect(capturedWatchCallback).toBe(firstCallback);
241
+ });
242
+
243
+ test("singleton instance is shared across calls", () => {
244
+ const watcher1 = PluginSourceWatcher.getInstance();
245
+ const watcher2 = PluginSourceWatcher.getInstance();
246
+ expect(watcher1).toBe(watcher2);
247
+ });
248
+
249
+ test("resetForTests() clears the singleton", () => {
250
+ const watcher1 = PluginSourceWatcher.getInstance();
251
+ watcher1.start();
252
+
253
+ PluginSourceWatcher.resetForTests();
254
+ const watcher2 = PluginSourceWatcher.getInstance();
255
+
256
+ expect(watcher1).not.toBe(watcher2);
257
+ });
258
+
259
+ /**
260
+ * REGRESSION: When an event arrives during a close→reopen swap, rescan
261
+ * must catch any plugin not yet in the registry.
262
+ *
263
+ * Scenario:
264
+ * 1. Plugin "new-plugin" directory is created
265
+ * 2. Event fires, triggering a watcher restart (close + reopen)
266
+ * 3. Before the old watcher's callback is fully fired, another plugin
267
+ * "late-plugin" is created
268
+ * 4. The new watcher doesn't yet know about "late-plugin"
269
+ * 5. After the reopen, rescan walks the directory and finds "late-plugin"
270
+ * not in the registry, and schedules its rebuild
271
+ */
272
+ test("watcher restart + rescan catches plugins created during close→reopen", async () => {
273
+ const watcher = PluginSourceWatcher.getInstance();
274
+ watcher.start();
275
+
276
+ // Track which plugins are "registered" at each point
277
+ const registeredPlugins = new Set<string>();
278
+ mockGetRegisteredPluginImpl = (name: string) =>
279
+ registeredPlugins.has(name) ? { name } : undefined;
280
+
281
+ // Simulate multiple plugins on disk
282
+ mockRereadirSync.mockImplementation(() => [
283
+ "new-plugin",
284
+ "late-plugin",
285
+ ".DS_Store",
286
+ ]);
287
+
288
+ // Fire an event on new-plugin (this will trigger a watcher restart)
289
+ capturedWatchCallback!("change", "new-plugin/src/index.ts");
290
+
291
+ // Wait for the direct rebuild debounce, the watcher-restart debounce,
292
+ // and the rescan-triggered rebuild debounce.
293
+ await new Promise((r) => setTimeout(r, 1100));
294
+
295
+ // At this point, rescan should have run and discovered late-plugin,
296
+ // even though no direct fs.watch event was delivered for it.
297
+ const calls = mockReregisterExternalPlugin.mock.calls.map((c) => c[0]);
298
+ expect(calls).toContain("new-plugin");
299
+ expect(calls).toContain("late-plugin");
300
+ expect(calls).not.toContain(".DS_Store");
301
+ });
302
+ });
@@ -59,7 +59,7 @@ import {
59
59
  unregisterPluginTools,
60
60
  } from "../tools/registry.js";
61
61
  import type {
62
- PluginTool,
62
+ LoadedPluginTool,
63
63
  ToolContext,
64
64
  ToolExecutionResult,
65
65
  } from "../tools/types.js";
@@ -81,8 +81,8 @@ const fakeCtx: DaemonContext = {
81
81
 
82
82
  function makeFakeTool(
83
83
  name: string,
84
- extras: Partial<PluginTool> = {},
85
- ): PluginTool {
84
+ extras: Partial<LoadedPluginTool> = {},
85
+ ): LoadedPluginTool {
86
86
  return {
87
87
  name,
88
88
  description: `Fake ${name}`,
@@ -46,7 +46,7 @@ import {
46
46
  type ToolResultTruncateResult,
47
47
  type TurnContext,
48
48
  } from "../plugins/types.js";
49
- import type { PluginTool } from "../tools/types.js";
49
+ import type { LoadedPluginTool } from "../tools/types.js";
50
50
 
51
51
  const sampleTrust: TrustContext = {
52
52
  sourceChannel: "vellum",
@@ -207,7 +207,7 @@ describe("plugin core types", () => {
207
207
  },
208
208
  };
209
209
 
210
- const sampleTool: PluginTool = {
210
+ const sampleTool: LoadedPluginTool = {
211
211
  name: "sample-tool",
212
212
  description: "Sample plugin tool",
213
213
  defaultRiskLevel: RiskLevel.Low,
@@ -95,7 +95,6 @@ type PersistUserMessageMock = ReturnType<
95
95
  type RunAgentLoopMock = ReturnType<
96
96
  typeof mock<(...args: unknown[]) => Promise<void>>
97
97
  >;
98
- type NoticeMock = ReturnType<typeof mock<(notice: string | undefined) => void>>;
99
98
  interface TestConversation {
100
99
  conversationId: string;
101
100
  trustContext: unknown;
@@ -123,13 +122,10 @@ interface TestConversation {
123
122
  estimatedCost: number;
124
123
  };
125
124
  persistUserMessage: PersistUserMessageMock;
126
- setSlackRuntimeContextNotice: NoticeMock;
127
125
  runAgentLoop: RunAgentLoopMock;
128
126
  updateClient: (sender: (...args: unknown[]) => void) => void;
129
127
  getCurrentSender: () => ((...args: unknown[]) => void) | undefined;
130
128
  __loopDeferred: Deferred<void>;
131
- __noticeCalls: Array<string | undefined>;
132
- __loopNotices: Array<string | undefined>;
133
129
  __clientSenders: Array<((...args: unknown[]) => void) | undefined>;
134
130
  }
135
131
 
@@ -171,11 +167,8 @@ async function waitForRunAgentLoopCall(): Promise<void> {
171
167
  function makeConversation(): TestConversation {
172
168
  let turnChannelContext: TurnChannelContext | null = null;
173
169
  let turnInterfaceContext: TurnInterfaceContext | null = null;
174
- let slackNotice: string | undefined;
175
170
  let currentSender: ((...args: unknown[]) => void) | undefined;
176
- const noticeCalls: Array<string | undefined> = [];
177
171
  const loopDeferred = createDeferred<void>();
178
- const loopNotices: Array<string | undefined> = [];
179
172
  const clientSenders: Array<((...args: unknown[]) => void) | undefined> = [];
180
173
  const messages: unknown[] = [];
181
174
 
@@ -223,12 +216,7 @@ function makeConversation(): TestConversation {
223
216
  _metadata?: Record<string, unknown>,
224
217
  ) => "persisted-user-message-id",
225
218
  ),
226
- setSlackRuntimeContextNotice: mock((notice: string | undefined) => {
227
- slackNotice = notice;
228
- noticeCalls.push(notice);
229
- }),
230
219
  runAgentLoop: mock(async (..._args: unknown[]) => {
231
- loopNotices.push(slackNotice);
232
220
  await loopDeferred.promise;
233
221
  }),
234
222
  updateClient: (sender: (...args: unknown[]) => void) => {
@@ -237,8 +225,6 @@ function makeConversation(): TestConversation {
237
225
  },
238
226
  getCurrentSender: () => currentSender,
239
227
  __loopDeferred: loopDeferred,
240
- __noticeCalls: noticeCalls,
241
- __loopNotices: loopNotices,
242
228
  __clientSenders: clientSenders,
243
229
  };
244
230
 
@@ -252,15 +238,13 @@ describe("processMessageInBackground Slack option propagation", () => {
252
238
  broadcastMessages.length = 0;
253
239
  });
254
240
 
255
- test("passes Slack inbound metadata to persistence and exposes the runtime notice during the loop", async () => {
241
+ test("passes Slack inbound metadata to persistence during background processing", async () => {
256
242
  const slackInbound = {
257
243
  channelId: "C0123CHANNEL",
258
244
  channelTs: "1700000001.111111",
259
245
  threadTs: "1700000000.000001",
260
246
  displayName: "Alice",
261
247
  };
262
- const notice =
263
- "Slack context note: this turn joined an existing thread. 2 earlier messages were backfilled.";
264
248
 
265
249
  const result = await processMessageInBackground(
266
250
  "conv-background-slack",
@@ -268,7 +252,6 @@ describe("processMessageInBackground Slack option propagation", () => {
268
252
  undefined,
269
253
  {
270
254
  slackInbound,
271
- slackRuntimeContextNotice: notice,
272
255
  },
273
256
  "slack",
274
257
  "slack",
@@ -280,43 +263,10 @@ describe("processMessageInBackground Slack option propagation", () => {
280
263
  slackInbound,
281
264
  });
282
265
  expect(activeConversation.runAgentLoop).toHaveBeenCalledTimes(1);
283
- expect(activeConversation.__loopNotices).toEqual([notice]);
284
266
 
285
267
  activeConversation.__loopDeferred.resolve();
286
268
  await activeConversation.__loopDeferred.promise;
287
269
  await Promise.resolve();
288
-
289
- expect(activeConversation.__noticeCalls).toEqual([notice, undefined]);
290
- });
291
-
292
- test("clears the Slack runtime notice after normal message processing", async () => {
293
- const notice =
294
- "Slack context note: this turn joined an existing thread. 2 earlier messages were backfilled.";
295
-
296
- const processing = processMessage(
297
- "conv-background-slack",
298
- "Reply from Slack",
299
- undefined,
300
- {
301
- slackRuntimeContextNotice: notice,
302
- isInteractive: true,
303
- },
304
- "slack",
305
- "slack",
306
- );
307
-
308
- await waitForRunAgentLoopCall();
309
-
310
- expect(activeConversation.runAgentLoop).toHaveBeenCalledTimes(1);
311
- expect(activeConversation.__loopNotices).toEqual([notice]);
312
-
313
- activeConversation.__loopDeferred.resolve();
314
- await expect(processing).resolves.toEqual({
315
- messageId: "persisted-user-message-id",
316
- });
317
-
318
- expect(activeConversation.__noticeCalls).toEqual([notice, undefined]);
319
- expect(activeConversation.__clientSenders).toHaveLength(2);
320
270
  });
321
271
 
322
272
  test("observes live agent events without replacing the broadcast emitter", async () => {
@@ -196,7 +196,6 @@ function makeTestConversation() {
196
196
  metadata,
197
197
  displayContent,
198
198
  ),
199
- setSlackRuntimeContextNotice: () => {},
200
199
  runAgentLoop,
201
200
  updateClient: () => {},
202
201
  getCurrentSender: () => undefined,
@@ -361,6 +360,7 @@ describe("processMessage displayContent", () => {
361
360
  expect(persistedBlocks).toEqual([
362
361
  {
363
362
  type: "file",
363
+ _attachmentId: "att-1",
364
364
  source: {
365
365
  type: "base64",
366
366
  media_type: "application/pdf",
@@ -370,21 +370,26 @@ describe("processMessage displayContent", () => {
370
370
  },
371
371
  ]);
372
372
  expect(addMessageCalls[0]!.content).not.toContain("<external_content");
373
- expect(conversation.getMessages()[0]).toEqual({
374
- role: "user",
375
- content: [
376
- { type: "text", text: modelContent },
377
- {
378
- type: "file",
379
- source: {
380
- type: "base64",
381
- media_type: "application/pdf",
382
- data: Buffer.from("pdf bytes").toString("base64"),
383
- filename: "attachment.pdf",
384
- },
385
- extracted_text: undefined,
386
- },
387
- ],
373
+ const inMemoryMessage = conversation.getMessages()[0]!;
374
+ expect(inMemoryMessage.role).toBe("user");
375
+ expect(inMemoryMessage.content[0]).toEqual({
376
+ type: "text",
377
+ text: modelContent,
378
+ });
379
+ const inMemoryFileBlock = inMemoryMessage.content[1] as unknown as Record<
380
+ string,
381
+ unknown
382
+ >;
383
+ expect(inMemoryFileBlock._attachmentId).toBe("att-1");
384
+ expect(inMemoryFileBlock).toMatchObject({
385
+ type: "file",
386
+ source: {
387
+ type: "base64",
388
+ media_type: "application/pdf",
389
+ data: Buffer.from("pdf bytes").toString("base64"),
390
+ filename: "attachment.pdf",
391
+ },
392
+ extracted_text: undefined,
388
393
  });
389
394
  });
390
395
 
@@ -19,6 +19,22 @@ function makeConfig(): AssistantConfig {
19
19
  }
20
20
 
21
21
  describe("getVisibleProviderCatalog", () => {
22
+ test("hides openai-compatible endpoints by default", () => {
23
+ _setOverridesForTesting({});
24
+
25
+ const visible = getVisibleProviderCatalog(makeConfig());
26
+
27
+ expect(visible.find((p) => p.id === "openai-compatible")).toBeUndefined();
28
+ });
29
+
30
+ test("shows openai-compatible endpoints when its flag is enabled", () => {
31
+ _setOverridesForTesting({ "openai-compatible-endpoints": true });
32
+
33
+ const visible = getVisibleProviderCatalog(makeConfig());
34
+
35
+ expect(visible.find((p) => p.id === "openai-compatible")).toBeDefined();
36
+ });
37
+
22
38
  test("returns the full catalog when all feature flags are enabled", () => {
23
39
  const allFlags: Record<string, boolean> = {};
24
40
  for (const entry of PROVIDER_CATALOG) {
@@ -113,7 +113,7 @@ const DIRECT_OR_MANAGED_PROVIDER_KEYS: string[] = [
113
113
  "fireworks",
114
114
  "openrouter",
115
115
  ];
116
- const MANAGED_FALLBACK_PROVIDERS: string[] = ["anthropic", "gemini", "openai"];
116
+ const MANAGED_FALLBACK_PROVIDERS: string[] = ["anthropic", "gemini", "openai", "fireworks"];
117
117
 
118
118
  function enableManagedProxy() {
119
119
  mockPlatformBaseUrl = PLATFORM_BASE;
@@ -209,20 +209,19 @@ describe("managed proxy integration — credential precedence", () => {
209
209
  },
210
210
  );
211
211
 
212
- test("managed bootstrap registers anthropic, openai, and gemini", async () => {
212
+ test("managed bootstrap registers anthropic, openai, gemini, and fireworks", async () => {
213
213
  enableManagedProxy();
214
214
  mockProviderKeys = {};
215
215
  await initializeProviders(makeProvidersConfig("anthropic", "test-model"));
216
216
  expect(listProviders()).toEqual(
217
- expect.arrayContaining(["anthropic", "openai", "gemini"]),
217
+ expect.arrayContaining(["anthropic", "openai", "gemini", "fireworks"]),
218
218
  );
219
- expect(listProviders()).toHaveLength(3);
219
+ expect(listProviders()).toHaveLength(4);
220
220
  expect(getProviderRoutingSource("anthropic")).toBe("managed-proxy");
221
221
  expect(getProviderRoutingSource("openai")).toBe("managed-proxy");
222
222
  expect(getProviderRoutingSource("gemini")).toBe("managed-proxy");
223
- for (const p of ["fireworks", "openrouter"]) {
224
- expect(getProviderRoutingSource(p)).toBeUndefined();
225
- }
223
+ expect(getProviderRoutingSource("fireworks")).toBe("managed-proxy");
224
+ expect(getProviderRoutingSource("openrouter")).toBeUndefined();
226
225
  });
227
226
 
228
227
  test("managed anthropic uses anthropic proxy path", async () => {
@@ -376,7 +375,7 @@ describe("managed proxy integration — credential precedence", () => {
376
375
  });
377
376
 
378
377
  describe("mixed: some user keys + managed fallback fills gaps", () => {
379
- test("user key for anthropic routes direct and managed fallback fills openai and gemini", async () => {
378
+ test("user key for anthropic routes direct and managed fallback fills openai, gemini, and fireworks", async () => {
380
379
  enableManagedProxy();
381
380
  setUserKeysFor("anthropic");
382
381
  await initializeProviders(makeProvidersConfig("anthropic", "test-model"));
@@ -387,13 +386,13 @@ describe("managed proxy integration — credential precedence", () => {
387
386
  expect(getProviderRoutingSource("openai")).toBe("managed-proxy");
388
387
  expect(registered).toContain("gemini");
389
388
  expect(getProviderRoutingSource("gemini")).toBe("managed-proxy");
390
- for (const p of ["fireworks", "openrouter"]) {
391
- expect(registered).not.toContain(p);
392
- expect(getProviderRoutingSource(p)).toBeUndefined();
393
- }
389
+ expect(registered).toContain("fireworks");
390
+ expect(getProviderRoutingSource("fireworks")).toBe("managed-proxy");
391
+ expect(registered).not.toContain("openrouter");
392
+ expect(getProviderRoutingSource("openrouter")).toBeUndefined();
394
393
  });
395
394
 
396
- test("user key for openai routes direct while anthropic and gemini still bootstrap via managed proxy", async () => {
395
+ test("user key for openai routes direct while anthropic, gemini, and fireworks still bootstrap via managed proxy", async () => {
397
396
  enableManagedProxy();
398
397
  setUserKeysFor("openai");
399
398
  await initializeProviders(makeProvidersConfig("openai", "test-model"));
@@ -404,11 +403,10 @@ describe("managed proxy integration — credential precedence", () => {
404
403
  expect(getProviderRoutingSource("anthropic")).toBe("managed-proxy");
405
404
  expect(registered).toContain("gemini");
406
405
  expect(getProviderRoutingSource("gemini")).toBe("managed-proxy");
407
- // OpenAI has a user key so it's user-key, not managed-proxy
408
- for (const p of ["fireworks", "openrouter"]) {
409
- expect(registered).not.toContain(p);
410
- expect(getProviderRoutingSource(p)).toBeUndefined();
411
- }
406
+ expect(registered).toContain("fireworks");
407
+ expect(getProviderRoutingSource("fireworks")).toBe("managed-proxy");
408
+ expect(registered).not.toContain("openrouter");
409
+ expect(getProviderRoutingSource("openrouter")).toBeUndefined();
412
410
  });
413
411
  });
414
412
  });
@@ -476,8 +474,8 @@ describe("config mode flip → provider reinit", () => {
476
474
  });
477
475
 
478
476
  describe("managed proxy integration — constants integrity", () => {
479
- test("anthropic, openai, and gemini have metadata with managed=true and a proxyPath", () => {
480
- for (const provider of ["anthropic", "openai", "gemini"]) {
477
+ test("anthropic, openai, gemini, and fireworks have metadata with managed=true and a proxyPath", () => {
478
+ for (const provider of ["anthropic", "openai", "gemini", "fireworks"]) {
481
479
  const meta = PLATFORM_PROVIDER_META[provider];
482
480
  expect(meta).toBeDefined();
483
481
  expect(meta.managed).toBe(true);
@@ -504,10 +502,14 @@ describe("managed proxy integration — constants integrity", () => {
504
502
  );
505
503
  });
506
504
 
507
- test("fireworks and openrouter are not managed proxy capable", () => {
508
- for (const provider of ["fireworks", "openrouter"]) {
509
- expect(PLATFORM_PROVIDER_META[provider].managed).toBe(false);
510
- expect(PLATFORM_PROVIDER_META[provider].proxyPath).toBeUndefined();
511
- }
505
+ test("fireworks routes through fireworks proxy path", () => {
506
+ expect(PLATFORM_PROVIDER_META.fireworks.proxyPath).toBe(
507
+ "/v1/runtime-proxy/fireworks",
508
+ );
509
+ });
510
+
511
+ test("openrouter is not managed proxy capable", () => {
512
+ expect(PLATFORM_PROVIDER_META.openrouter.managed).toBe(false);
513
+ expect(PLATFORM_PROVIDER_META.openrouter.proxyPath).toBeUndefined();
512
514
  });
513
515
  });
@@ -12,7 +12,7 @@ let providerRefreshCalls = 0;
12
12
  const PLATFORM_BASE_URL = "https://platform.example.com";
13
13
  const ASSISTANT_API_KEY_PATH = credentialKey("vellum", "assistant_api_key");
14
14
  const PLATFORM_BASE_URL_PATH = credentialKey("vellum", "platform_base_url");
15
- const MANAGED_PROVIDERS = ["anthropic", "openai", "gemini"] as const;
15
+ const MANAGED_PROVIDERS = ["anthropic", "openai", "gemini", "fireworks"] as const;
16
16
 
17
17
  let platformBaseUrlOverride: string | undefined;
18
18