@vellumai/assistant 0.8.1 → 0.8.3

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 (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +642 -5
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -0,0 +1,158 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
4
+ import type { AssistantConfig } from "../config/schema.js";
5
+ import { PROVIDER_CATALOG } from "../providers/model-catalog.js";
6
+ import { getVisibleProviderCatalog } from "../providers/provider-catalog-visibility.js";
7
+
8
+ beforeEach(() => {
9
+ _setOverridesForTesting({});
10
+ });
11
+
12
+ afterEach(() => {
13
+ _setOverridesForTesting({});
14
+ });
15
+
16
+ /** Minimal AssistantConfig stub for feature-flag resolution. */
17
+ function makeConfig(): AssistantConfig {
18
+ return {} as AssistantConfig;
19
+ }
20
+
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
+
38
+ test("returns the full catalog when all feature flags are enabled", () => {
39
+ const allFlags: Record<string, boolean> = {};
40
+ for (const entry of PROVIDER_CATALOG) {
41
+ if (entry.featureFlag) allFlags[entry.featureFlag] = true;
42
+ for (const model of entry.models) {
43
+ if (model.featureFlag) allFlags[model.featureFlag] = true;
44
+ }
45
+ }
46
+ _setOverridesForTesting(allFlags);
47
+
48
+ const visible = getVisibleProviderCatalog(makeConfig());
49
+ expect(visible.length).toBe(PROVIDER_CATALOG.length);
50
+ expect(visible.map((p) => p.id)).toEqual(PROVIDER_CATALOG.map((p) => p.id));
51
+ });
52
+
53
+ test("hides a provider whose featureFlag is disabled", () => {
54
+ const allFlags: Record<string, boolean> = {};
55
+ for (const entry of PROVIDER_CATALOG) {
56
+ if (entry.featureFlag) allFlags[entry.featureFlag] = true;
57
+ for (const model of entry.models) {
58
+ if (model.featureFlag) allFlags[model.featureFlag] = true;
59
+ }
60
+ }
61
+ _setOverridesForTesting({ ...allFlags, "test-provider-flag": false });
62
+
63
+ const original = [...PROVIDER_CATALOG];
64
+ PROVIDER_CATALOG.push({
65
+ id: "__test_flagged_provider__",
66
+ displayName: "Test Flagged Provider",
67
+ models: [
68
+ {
69
+ id: "test-model",
70
+ displayName: "Test Model",
71
+ },
72
+ ],
73
+ defaultModel: "test-model",
74
+ featureFlag: "test-provider-flag",
75
+ });
76
+
77
+ try {
78
+ const visible = getVisibleProviderCatalog(makeConfig());
79
+ expect(
80
+ visible.find((p) => p.id === "__test_flagged_provider__"),
81
+ ).toBeUndefined();
82
+ expect(visible.length).toBe(original.length);
83
+ } finally {
84
+ PROVIDER_CATALOG.length = 0;
85
+ PROVIDER_CATALOG.push(...original);
86
+ }
87
+ });
88
+
89
+ test("hides a model whose featureFlag is disabled but keeps the provider", () => {
90
+ _setOverridesForTesting({ "test-model-flag": false });
91
+
92
+ const original = [...PROVIDER_CATALOG];
93
+ PROVIDER_CATALOG.push({
94
+ id: "__test_provider_with_flagged_model__",
95
+ displayName: "Test Provider",
96
+ models: [
97
+ {
98
+ id: "visible-model",
99
+ displayName: "Visible Model",
100
+ },
101
+ {
102
+ id: "flagged-model",
103
+ displayName: "Flagged Model",
104
+ featureFlag: "test-model-flag",
105
+ },
106
+ ],
107
+ defaultModel: "visible-model",
108
+ });
109
+
110
+ try {
111
+ const visible = getVisibleProviderCatalog(makeConfig());
112
+ const provider = visible.find(
113
+ (p) => p.id === "__test_provider_with_flagged_model__",
114
+ );
115
+ expect(provider).toBeDefined();
116
+ expect(provider!.models.map((m) => m.id)).toEqual(["visible-model"]);
117
+ } finally {
118
+ PROVIDER_CATALOG.length = 0;
119
+ PROVIDER_CATALOG.push(...original);
120
+ }
121
+ });
122
+
123
+ test("hides a provider entirely when all its models are flagged off", () => {
124
+ _setOverridesForTesting({
125
+ "flag-a": false,
126
+ "flag-b": false,
127
+ });
128
+
129
+ const original = [...PROVIDER_CATALOG];
130
+ PROVIDER_CATALOG.push({
131
+ id: "__test_all_models_flagged__",
132
+ displayName: "All Flagged",
133
+ models: [
134
+ {
135
+ id: "model-a",
136
+ displayName: "Model A",
137
+ featureFlag: "flag-a",
138
+ },
139
+ {
140
+ id: "model-b",
141
+ displayName: "Model B",
142
+ featureFlag: "flag-b",
143
+ },
144
+ ],
145
+ defaultModel: "model-a",
146
+ });
147
+
148
+ try {
149
+ const visible = getVisibleProviderCatalog(makeConfig());
150
+ expect(
151
+ visible.find((p) => p.id === "__test_all_models_flagged__"),
152
+ ).toBeUndefined();
153
+ } finally {
154
+ PROVIDER_CATALOG.length = 0;
155
+ PROVIDER_CATALOG.push(...original);
156
+ }
157
+ });
158
+ });
@@ -753,6 +753,117 @@ describe("RetryProvider — streaming response handling", () => {
753
753
  expect(callCount).toBe(2);
754
754
  });
755
755
 
756
+ test("retries transport-aborted stream (Anthropic 'Request was aborted' with no abortReason)", async () => {
757
+ let callCount = 0;
758
+ const inner: Provider = {
759
+ name: "retry-transport-abort",
760
+ async sendMessage() {
761
+ callCount++;
762
+ if (callCount <= 1) {
763
+ // Mirrors the ProviderError shape produced by the catch-site in
764
+ // providers/anthropic/client.ts when the SDK reports
765
+ // ``Anthropic.APIError(status === undefined, message: "Request
766
+ // was aborted.")`` and the daemon's AbortController was NOT the
767
+ // cause (i.e. abortReason is undefined). Empirically the #1
768
+ // daemon error by a factor of 5x — 1,344 events in 4d on the
769
+ // SSE chat path, all of which used to surface as a 45s blank
770
+ // screen on the web client via LUM-1431.
771
+ throw new ProviderError(
772
+ "Anthropic API error: Request was aborted.",
773
+ "anthropic",
774
+ undefined,
775
+ );
776
+ }
777
+ return successResponse();
778
+ },
779
+ };
780
+ const provider = new RetryProvider(inner);
781
+ await provider.sendMessage(MESSAGES);
782
+ expect(callCount).toBe(2);
783
+ });
784
+
785
+ test("does NOT retry caller-aborted stream (abortReason set short-circuits retry)", async () => {
786
+ let callCount = 0;
787
+ const abortError = new ProviderError(
788
+ "Anthropic API error: Request was aborted.",
789
+ "anthropic",
790
+ undefined,
791
+ // Tagging abortReason exactly matches what the catch-site does when
792
+ // signal.aborted was true at the moment of failure — i.e. the
793
+ // daemon (or the user) cancelled the request, not the transport.
794
+ // The retry layer must respect this and surface the error
795
+ // immediately without consuming retry budget.
796
+ { abortReason: "user-cancelled" },
797
+ );
798
+ const inner: Provider = {
799
+ name: "caller-abort",
800
+ async sendMessage() {
801
+ callCount++;
802
+ throw abortError;
803
+ },
804
+ };
805
+ const provider = new RetryProvider(inner);
806
+ await expect(provider.sendMessage(MESSAGES)).rejects.toBe(abortError);
807
+ expect(callCount).toBe(1);
808
+ });
809
+
810
+ test("does NOT retry inner-timeout stream (deterministic 30min deadline failure)", async () => {
811
+ let callCount = 0;
812
+ // When the inner streamTimeoutMs fires, the catch-site rewrites the
813
+ // message to "Anthropic stream timed out after Xs (inner
814
+ // streamTimeoutMs)" instead of "Request was aborted." That rewrite is
815
+ // what allows this branch to bypass the transport-abort retry —
816
+ // retrying a 30min-deadline failure would almost certainly hit the
817
+ // same deadline on the next attempt and waste retry budget.
818
+ const innerTimeoutError = new ProviderError(
819
+ "Anthropic API error: Anthropic stream timed out after 1800s (inner streamTimeoutMs)",
820
+ "anthropic",
821
+ undefined,
822
+ );
823
+ const inner: Provider = {
824
+ name: "inner-timeout",
825
+ async sendMessage() {
826
+ callCount++;
827
+ throw innerTimeoutError;
828
+ },
829
+ };
830
+ const provider = new RetryProvider(inner);
831
+ await expect(provider.sendMessage(MESSAGES)).rejects.toBe(
832
+ innerTimeoutError,
833
+ );
834
+ expect(callCount).toBe(1);
835
+ });
836
+
837
+ test("does NOT retry OpenAI/Gemini-shaped 'Request was aborted' (no inner-timeout rewrite at those catch-sites)", async () => {
838
+ // The OpenAI chat-completions, OpenAI responses, and Gemini catch-sites
839
+ // format their errors as `"<Provider> API error (undefined): Request
840
+ // was aborted."` (note the `(undefined)` parenthetical that the
841
+ // Anthropic catch-site intentionally omits) and — unlike the Anthropic
842
+ // catch-site — they do NOT rewrite their inner-streamTimeoutMs
843
+ // deadline failures. A provider-agnostic transport-abort predicate
844
+ // would burn three retries on what is by construction a deterministic
845
+ // 30-minute deadline failure that will fire again on every attempt.
846
+ // Scoping the predicate to the Anthropic message prefix avoids that
847
+ // wasted retry budget for non-Anthropic providers until their
848
+ // catch-sites grow the same `innerTimeoutFired` distinction.
849
+ const openaiAbortError = new ProviderError(
850
+ "OpenAI API error (undefined): Request was aborted.",
851
+ "openai",
852
+ undefined,
853
+ );
854
+ let callCount = 0;
855
+ const inner: Provider = {
856
+ name: "openai-aborted-stream",
857
+ async sendMessage() {
858
+ callCount++;
859
+ throw openaiAbortError;
860
+ },
861
+ };
862
+ const provider = new RetryProvider(inner);
863
+ await expect(provider.sendMessage(MESSAGES)).rejects.toBe(openaiAbortError);
864
+ expect(callCount).toBe(1);
865
+ });
866
+
756
867
  test("events accumulate across retries (each attempt delivers events independently)", async () => {
757
868
  let callCount = 0;
758
869
  const inner: Provider = {
@@ -1,6 +1,6 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
- import { MANAGED_PROVIDER_META } from "../providers/managed-proxy/constants.js";
3
+ import { PLATFORM_PROVIDER_META } from "../providers/platform-proxy/constants.js";
4
4
  import { credentialKey } from "../security/credential-key.js";
5
5
 
6
6
  // ---------------------------------------------------------------------------
@@ -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
  });
@@ -436,7 +434,7 @@ describe("managed proxy integration — ollama exclusion", () => {
436
434
  });
437
435
 
438
436
  test("ollama metadata is marked as non-managed", () => {
439
- const meta = MANAGED_PROVIDER_META.ollama;
437
+ const meta = PLATFORM_PROVIDER_META.ollama;
440
438
  expect(meta).toBeDefined();
441
439
  expect(meta.managed).toBe(false);
442
440
  expect(meta.proxyPath).toBeUndefined();
@@ -476,9 +474,9 @@ 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"]) {
481
- const meta = MANAGED_PROVIDER_META[provider];
477
+ test("anthropic, openai, gemini, and fireworks have metadata with managed=true and a proxyPath", () => {
478
+ for (const provider of ["anthropic", "openai", "gemini", "fireworks"]) {
479
+ const meta = PLATFORM_PROVIDER_META[provider];
482
480
  expect(meta).toBeDefined();
483
481
  expect(meta.managed).toBe(true);
484
482
  expect(meta.proxyPath).toBeTruthy();
@@ -487,27 +485,31 @@ describe("managed proxy integration — constants integrity", () => {
487
485
  });
488
486
 
489
487
  test("anthropic routes through anthropic proxy path", () => {
490
- expect(MANAGED_PROVIDER_META.anthropic.proxyPath).toBe(
488
+ expect(PLATFORM_PROVIDER_META.anthropic.proxyPath).toBe(
491
489
  "/v1/runtime-proxy/anthropic",
492
490
  );
493
491
  });
494
492
 
495
493
  test("gemini routes through gemini proxy path", () => {
496
- expect(MANAGED_PROVIDER_META.gemini.proxyPath).toBe(
494
+ expect(PLATFORM_PROVIDER_META.gemini.proxyPath).toBe(
497
495
  "/v1/runtime-proxy/gemini",
498
496
  );
499
497
  });
500
498
 
501
499
  test("openai routes through openai proxy path", () => {
502
- expect(MANAGED_PROVIDER_META.openai.proxyPath).toBe(
500
+ expect(PLATFORM_PROVIDER_META.openai.proxyPath).toBe(
503
501
  "/v1/runtime-proxy/openai",
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(MANAGED_PROVIDER_META[provider].managed).toBe(false);
510
- expect(MANAGED_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
  });
@@ -3,6 +3,7 @@ import { join } from "node:path";
3
3
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
4
4
 
5
5
  const TEST_DIR = process.env.VELLUM_WORKSPACE_DIR!;
6
+ const mockRefreshSkillCapabilityMemories = mock(() => {});
6
7
 
7
8
  mock.module("../util/logger.js", () => ({
8
9
  getLogger: () =>
@@ -11,6 +12,11 @@ mock.module("../util/logger.js", () => ({
11
12
  }),
12
13
  }));
13
14
 
15
+ mock.module("../daemon/skill-memory-refresh.js", () => ({
16
+ refreshSkillCapabilityMemories: mockRefreshSkillCapabilityMemories,
17
+ }));
18
+
19
+ import { loadSkillCatalog } from "../config/skills.js";
14
20
  import { executeScaffoldManagedSkill } from "../tools/skills/scaffold-managed.js";
15
21
  import type { ToolContext } from "../tools/types.js";
16
22
 
@@ -24,6 +30,7 @@ function makeContext(): ToolContext {
24
30
 
25
31
  beforeEach(() => {
26
32
  mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
33
+ mockRefreshSkillCapabilityMemories.mockClear();
27
34
  });
28
35
 
29
36
  afterEach(() => {
@@ -31,7 +38,29 @@ afterEach(() => {
31
38
  });
32
39
 
33
40
  describe("scaffold_managed_skill tool", () => {
34
- test("creates a valid skill and index entry", async () => {
41
+ test("keeps legacy index control as a deprecated no-op schema field", () => {
42
+ const tools = JSON.parse(
43
+ readFileSync(
44
+ join(
45
+ import.meta.dirname,
46
+ "../config/bundled-skills/skill-management/TOOLS.json",
47
+ ),
48
+ "utf-8",
49
+ ),
50
+ );
51
+ const scaffoldTool = tools.tools.find(
52
+ (tool: { name: string }) => tool.name === "scaffold_managed_skill",
53
+ );
54
+
55
+ expect(scaffoldTool).toBeDefined();
56
+ expect(scaffoldTool.input_schema.properties.add_to_index).toEqual({
57
+ type: "boolean",
58
+ description:
59
+ "Deprecated no-op compatibility field. Skills are discovered from top-level SKILL.md files.",
60
+ });
61
+ });
62
+
63
+ test("creates a valid skill discovered from its SKILL.md directory", async () => {
35
64
  const result = await executeScaffoldManagedSkill(
36
65
  {
37
66
  skill_id: "test-skill",
@@ -46,18 +75,40 @@ describe("scaffold_managed_skill tool", () => {
46
75
  const parsed = JSON.parse(result.content);
47
76
  expect(parsed.created).toBe(true);
48
77
  expect(parsed.skill_id).toBe("test-skill");
49
- expect(parsed.index_updated).toBe(true);
78
+ expect(parsed).not.toHaveProperty("index_updated");
50
79
 
51
80
  const skillFile = join(TEST_DIR, "skills", "test-skill", "SKILL.md");
52
81
  expect(existsSync(skillFile)).toBe(true);
53
82
  const content = readFileSync(skillFile, "utf-8");
54
83
  expect(content).toContain('name: "Test Skill"');
55
84
 
56
- const indexContent = readFileSync(
57
- join(TEST_DIR, "skills", "SKILLS.md"),
58
- "utf-8",
85
+ expect(existsSync(join(TEST_DIR, "skills", "SKILLS.md"))).toBe(false);
86
+
87
+ const catalog = loadSkillCatalog();
88
+ const skill = catalog.find((s) => s.id === "test-skill");
89
+ expect(skill).toBeDefined();
90
+ expect(skill!.name).toBe("Test Skill");
91
+ expect(mockRefreshSkillCapabilityMemories).toHaveBeenCalledTimes(1);
92
+ });
93
+
94
+ test("accepts legacy add_to_index input without returning index metadata", async () => {
95
+ const result = await executeScaffoldManagedSkill(
96
+ {
97
+ skill_id: "legacy-input",
98
+ name: "Legacy Input",
99
+ description: "A test skill",
100
+ body_markdown: "Do the thing.",
101
+ add_to_index: true,
102
+ },
103
+ makeContext(),
59
104
  );
60
- expect(indexContent).toContain("- test-skill");
105
+
106
+ expect(result.isError).toBe(false);
107
+ const parsed = JSON.parse(result.content);
108
+ expect(parsed.created).toBe(true);
109
+ expect(parsed).not.toHaveProperty("index_updated");
110
+ expect(existsSync(join(TEST_DIR, "skills", "SKILLS.md"))).toBe(false);
111
+ expect(mockRefreshSkillCapabilityMemories).toHaveBeenCalledTimes(1);
61
112
  });
62
113
 
63
114
  test("rejects duplicate unless overwrite=true", async () => {
@@ -258,7 +309,7 @@ describe("scaffold_managed_skill tool", () => {
258
309
  expect(result.content).toContain("traversal");
259
310
  });
260
311
 
261
- test("e2e: scaffold child then parent with includes, verify files and index", async () => {
312
+ test("e2e: scaffold child then parent with includes, verify file discovery", async () => {
262
313
  const childResult = await executeScaffoldManagedSkill(
263
314
  {
264
315
  skill_id: "e2e-child",
@@ -288,11 +339,12 @@ describe("scaffold_managed_skill tool", () => {
288
339
  expect(parentContent).toContain(" includes:");
289
340
  expect(parentContent).toContain(" - e2e-child");
290
341
 
291
- const indexContent = readFileSync(
292
- join(TEST_DIR, "skills", "SKILLS.md"),
293
- "utf-8",
294
- );
295
- expect(indexContent).toContain("- e2e-child");
296
- expect(indexContent).toContain("- e2e-parent");
342
+ expect(existsSync(join(TEST_DIR, "skills", "SKILLS.md"))).toBe(false);
343
+
344
+ const catalog = loadSkillCatalog();
345
+ expect(catalog.find((s) => s.id === "e2e-child")).toBeDefined();
346
+ const parent = catalog.find((s) => s.id === "e2e-parent");
347
+ expect(parent).toBeDefined();
348
+ expect(parent!.includes).toEqual(["e2e-child"]);
297
349
  });
298
350
  });
@@ -39,8 +39,11 @@ mock.module("../daemon/conversation-store.js", () => ({
39
39
  },
40
40
  }));
41
41
 
42
+ import { SYNC_TAGS } from "../daemon/message-types/sync.js";
42
43
  import { getDb } from "../memory/db-connection.js";
43
44
  import { initializeDb } from "../memory/db-init.js";
45
+ import type { AssistantEvent } from "../runtime/assistant-event.js";
46
+ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
44
47
  import { ROUTES } from "../runtime/routes/schedule-routes.js";
45
48
  import type { RouteDefinition } from "../runtime/routes/types.js";
46
49
  import {
@@ -71,6 +74,15 @@ function findRoute(endpoint: string, method: string): RouteDefinition {
71
74
  return route;
72
75
  }
73
76
 
77
+ async function waitFor(predicate: () => boolean): Promise<void> {
78
+ const deadline = Date.now() + 500;
79
+ while (Date.now() < deadline) {
80
+ if (predicate()) return;
81
+ await new Promise((resolve) => setTimeout(resolve, 5));
82
+ }
83
+ throw new Error("Timed out waiting for schedule route event");
84
+ }
85
+
74
86
  describe("schedule run-now trust propagation", () => {
75
87
  beforeEach(() => {
76
88
  clearTables();
@@ -218,6 +230,41 @@ describe("GET /schedules — default defer exclusion", () => {
218
230
  expect(result.schedules).toHaveLength(1);
219
231
  expect(result.schedules[0].id).toBe(agent.id);
220
232
  });
233
+
234
+ test("mutation routes emit schedule sync invalidation", async () => {
235
+ const received: AssistantEvent[] = [];
236
+ const subscription = assistantEventHub.subscribe({
237
+ type: "process",
238
+ callback: (event) => {
239
+ received.push(event);
240
+ },
241
+ });
242
+
243
+ try {
244
+ const agent = createSchedule({
245
+ name: "Agent schedule",
246
+ cronExpression: "* * * * *",
247
+ message: "hello",
248
+ syntax: "cron",
249
+ });
250
+ await waitFor(() => received.length >= 1);
251
+ received.length = 0;
252
+
253
+ const route = findRoute("schedules/:id/toggle", "POST");
254
+ route.handler({
255
+ pathParams: { id: agent.id },
256
+ body: { enabled: false },
257
+ });
258
+
259
+ await waitFor(() => received.length >= 1);
260
+ expect(received[0].message).toEqual({
261
+ type: "sync_changed",
262
+ tags: [SYNC_TAGS.assistantSchedules],
263
+ });
264
+ } finally {
265
+ subscription.dispose();
266
+ }
267
+ });
221
268
  });
222
269
 
223
270
  // ── schedules/:id/runs limit handling ─────────────────────────────────────
@@ -388,9 +435,9 @@ describe("POST /schedules — create", () => {
388
435
  });
389
436
 
390
437
  test("rejects missing required fields", () => {
391
- expect(() => postCreate({ expression: "* * * * *", message: "hi" })).toThrow(
392
- "name is required",
393
- );
438
+ expect(() =>
439
+ postCreate({ expression: "* * * * *", message: "hi" }),
440
+ ).toThrow("name is required");
394
441
  expect(() => postCreate({ name: "x", message: "hi" })).toThrow(
395
442
  "expression is required",
396
443
  );