@vellumai/assistant 0.6.5 → 0.6.6

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 (443) hide show
  1. package/AGENTS.md +9 -1
  2. package/ARCHITECTURE.md +15 -17
  3. package/Dockerfile +6 -4
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
  5. package/docs/architecture/integrations.md +32 -39
  6. package/docs/architecture/memory.md +25 -30
  7. package/docs/architecture/security.md +7 -6
  8. package/docs/browser-use-architecture-phase2.md +63 -20
  9. package/docs/plugins.md +761 -0
  10. package/examples/plugins/echo/README.md +132 -0
  11. package/examples/plugins/echo/package.json +17 -0
  12. package/examples/plugins/echo/register.ts +187 -0
  13. package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
  14. package/openapi.yaml +212 -68
  15. package/package.json +1 -1
  16. package/src/__tests__/app-compiler.test.ts +57 -0
  17. package/src/__tests__/approval-cascade.test.ts +7 -2
  18. package/src/__tests__/auto-analysis-end-to-end.test.ts +1 -0
  19. package/src/__tests__/avatar-generator.test.ts +4 -2
  20. package/src/__tests__/bundled-asset.test.ts +6 -6
  21. package/src/__tests__/catalog-cache.test.ts +69 -0
  22. package/src/__tests__/checker.test.ts +459 -171
  23. package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
  24. package/src/__tests__/compaction-events.test.ts +501 -0
  25. package/src/__tests__/compaction-pipeline.test.ts +210 -0
  26. package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
  27. package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
  28. package/src/__tests__/config-model-image-provider.test.ts +110 -0
  29. package/src/__tests__/config-schema.test.ts +22 -9
  30. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
  31. package/src/__tests__/contacts-tools.test.ts +26 -0
  32. package/src/__tests__/context-overflow-policy.test.ts +7 -7
  33. package/src/__tests__/context-window-manager.test.ts +355 -4
  34. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  35. package/src/__tests__/conversation-agent-loop-overflow.test.ts +26 -30
  36. package/src/__tests__/conversation-agent-loop.test.ts +30 -141
  37. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -1
  38. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  39. package/src/__tests__/conversation-init.benchmark.test.ts +2 -16
  40. package/src/__tests__/conversation-pairing.test.ts +174 -10
  41. package/src/__tests__/conversation-pre-run-repair.test.ts +4 -1
  42. package/src/__tests__/conversation-process-callsite.test.ts +3 -0
  43. package/src/__tests__/conversation-provider-retry-repair.test.ts +16 -7
  44. package/src/__tests__/conversation-queue.test.ts +29 -14
  45. package/src/__tests__/conversation-routes-disk-view.test.ts +7 -6
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +155 -110
  47. package/src/__tests__/conversation-runtime-workspace.test.ts +23 -38
  48. package/src/__tests__/conversation-seed-composer.test.ts +2 -2
  49. package/src/__tests__/conversation-slash-queue.test.ts +7 -2
  50. package/src/__tests__/conversation-slash-unknown.test.ts +25 -2
  51. package/src/__tests__/conversation-speed-override.test.ts +6 -1
  52. package/src/__tests__/conversation-title-service.test.ts +116 -0
  53. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -2
  54. package/src/__tests__/conversation-usage.test.ts +1 -1
  55. package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -1
  56. package/src/__tests__/conversation-workspace-injection.test.ts +3 -0
  57. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -1
  58. package/src/__tests__/credential-health-service.test.ts +78 -9
  59. package/src/__tests__/credential-security-invariants.test.ts +2 -2
  60. package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
  61. package/src/__tests__/empty-response-pipeline.test.ts +305 -0
  62. package/src/__tests__/extension-id-sync-guard.test.ts +3 -3
  63. package/src/__tests__/first-greeting.test.ts +247 -5
  64. package/src/__tests__/headless-browser-mode.test.ts +57 -0
  65. package/src/__tests__/history-repair-pipeline.test.ts +399 -0
  66. package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
  67. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
  68. package/src/__tests__/host-proxy-interface.test.ts +36 -2
  69. package/src/__tests__/image-credentials.test.ts +137 -0
  70. package/src/__tests__/image-service-dispatcher.test.ts +186 -0
  71. package/src/__tests__/injector-chain.test.ts +526 -0
  72. package/src/__tests__/intent-routing.test.ts +0 -26
  73. package/src/__tests__/llm-call-pipeline.test.ts +285 -0
  74. package/src/__tests__/llm-schema.test.ts +1 -1
  75. package/src/__tests__/media-generate-image.test.ts +119 -13
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
  77. package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
  78. package/src/__tests__/migration-import-from-url.test.ts +5 -68
  79. package/src/__tests__/model-intents.test.ts +4 -2
  80. package/src/__tests__/notification-broadcaster.test.ts +3 -3
  81. package/src/__tests__/notification-decision-strategy.test.ts +0 -11
  82. package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
  83. package/src/__tests__/oauth-apps-routes.test.ts +1 -1
  84. package/src/__tests__/oauth-cli.test.ts +14 -12
  85. package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
  86. package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
  87. package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
  88. package/src/__tests__/oauth-providers-routes.test.ts +3 -2
  89. package/src/__tests__/oauth-store.test.ts +41 -76
  90. package/src/__tests__/onboarding-template-contract.test.ts +16 -64
  91. package/src/__tests__/openai-image-service.test.ts +368 -0
  92. package/src/__tests__/overflow-reduce-pipeline.test.ts +676 -0
  93. package/src/__tests__/permission-checker-host-gate.test.ts +0 -24
  94. package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
  95. package/src/__tests__/persistence-pipeline.test.ts +377 -0
  96. package/src/__tests__/pipeline-runner.test.ts +565 -0
  97. package/src/__tests__/platform.test.ts +5 -2
  98. package/src/__tests__/plugin-bootstrap.test.ts +483 -0
  99. package/src/__tests__/plugin-registry.test.ts +273 -0
  100. package/src/__tests__/plugin-route-contribution.test.ts +288 -0
  101. package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
  102. package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
  103. package/src/__tests__/plugin-types.test.ts +320 -0
  104. package/src/__tests__/pricing.test.ts +44 -12
  105. package/src/__tests__/proxy-approval-callback.test.ts +69 -8
  106. package/src/__tests__/reaction-persistence.test.ts +1 -0
  107. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  108. package/src/__tests__/registry.test.ts +0 -2
  109. package/src/__tests__/schedule-routes.test.ts +131 -1
  110. package/src/__tests__/scheduler-recurrence.test.ts +14 -70
  111. package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
  112. package/src/__tests__/secret-detection-handler.test.ts +0 -10
  113. package/src/__tests__/shell-identity.test.ts +0 -134
  114. package/src/__tests__/suggestion-routes.test.ts +103 -4
  115. package/src/__tests__/task-memory-cleanup.test.ts +1 -0
  116. package/src/__tests__/task-scheduler.test.ts +3 -15
  117. package/src/__tests__/test-preload.ts +11 -0
  118. package/src/__tests__/title-generate-pipeline.test.ts +224 -0
  119. package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
  120. package/src/__tests__/tool-error-pipeline.test.ts +244 -0
  121. package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
  122. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -6
  123. package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
  124. package/src/__tests__/tool-executor.test.ts +141 -0
  125. package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
  126. package/src/__tests__/tool-result-truncation.test.ts +0 -110
  127. package/src/__tests__/user-plugin-loader.test.ts +191 -0
  128. package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
  129. package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
  130. package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
  131. package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
  132. package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
  133. package/src/__tests__/workspace-policy.test.ts +21 -3
  134. package/src/agent/loop.ts +340 -102
  135. package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
  136. package/src/approvals/guardian-request-resolvers.ts +80 -0
  137. package/src/backup/__tests__/backup-worker.test.ts +2 -13
  138. package/src/backup/backup-worker.ts +3 -15
  139. package/src/bundler/app-compiler.ts +84 -1
  140. package/src/calls/call-state.ts +2 -2
  141. package/src/channels/__tests__/types.test.ts +3 -3
  142. package/src/channels/types.ts +6 -4
  143. package/src/cli/__tests__/notifications.test.ts +87 -211
  144. package/src/cli/commands/__tests__/backup.test.ts +1 -1
  145. package/src/cli/commands/__tests__/image-generation.test.ts +255 -35
  146. package/src/cli/commands/__tests__/inference-send.test.ts +12 -0
  147. package/src/cli/commands/__tests__/tts-synthesize.test.ts +12 -0
  148. package/src/cli/commands/backup.ts +2 -2
  149. package/src/cli/commands/clients.ts +138 -0
  150. package/src/cli/commands/completions.ts +2 -9
  151. package/src/cli/commands/conversations.ts +55 -7
  152. package/src/cli/commands/image-generation.ts +33 -34
  153. package/src/cli/commands/notifications.ts +68 -103
  154. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
  155. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
  156. package/src/cli/commands/oauth/connect.ts +2 -2
  157. package/src/cli/commands/oauth/providers.ts +176 -8
  158. package/src/cli/commands/oauth/status.ts +46 -36
  159. package/src/cli/commands/skills.ts +3 -4
  160. package/src/cli/program.ts +25 -29
  161. package/src/config/__tests__/backup-schema.test.ts +7 -2
  162. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  163. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
  164. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
  165. package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
  166. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
  167. package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
  168. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
  169. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
  170. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  171. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
  172. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +12 -0
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +58 -0
  174. package/src/config/bundled-skills/schedule/SKILL.md +8 -3
  175. package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
  176. package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
  177. package/src/config/bundled-tool-registry.ts +0 -15
  178. package/src/config/feature-flag-registry.json +17 -1
  179. package/src/config/schema.ts +19 -0
  180. package/src/config/schemas/backup.ts +1 -1
  181. package/src/config/schemas/conversations.ts +16 -0
  182. package/src/config/schemas/llm.ts +2 -3
  183. package/src/config/schemas/security.ts +6 -6
  184. package/src/config/schemas/tts.ts +11 -0
  185. package/src/config/skill-state.ts +6 -2
  186. package/src/config/skills.ts +94 -5
  187. package/src/context/__tests__/compact-prompt.test.ts +27 -9
  188. package/src/context/prompts/compact.md +26 -12
  189. package/src/context/tool-result-truncation.ts +3 -63
  190. package/src/context/window-manager.ts +190 -16
  191. package/src/credential-health/credential-health-service.ts +19 -6
  192. package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
  193. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
  194. package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
  195. package/src/daemon/config-watcher.ts +0 -2
  196. package/src/daemon/context-overflow-policy.ts +4 -13
  197. package/src/daemon/conversation-agent-loop-handlers.ts +83 -22
  198. package/src/daemon/conversation-agent-loop.ts +984 -683
  199. package/src/daemon/conversation-history.ts +10 -19
  200. package/src/daemon/conversation-lifecycle.ts +37 -19
  201. package/src/daemon/conversation-notifiers.ts +2 -110
  202. package/src/daemon/conversation-process.ts +14 -7
  203. package/src/daemon/conversation-runtime-assembly.ts +532 -411
  204. package/src/daemon/conversation-tool-setup.ts +41 -4
  205. package/src/daemon/conversation.ts +80 -35
  206. package/src/daemon/external-plugins-bootstrap.ts +478 -0
  207. package/src/daemon/first-greeting.ts +191 -14
  208. package/src/daemon/handlers/config-model.ts +11 -0
  209. package/src/daemon/handlers/skills.ts +5 -1
  210. package/src/daemon/lifecycle.ts +33 -68
  211. package/src/daemon/message-types/computer-use.ts +2 -34
  212. package/src/daemon/message-types/conversations.ts +49 -0
  213. package/src/daemon/message-types/messages.ts +12 -0
  214. package/src/daemon/server.ts +5 -3
  215. package/src/daemon/shutdown-handlers.ts +2 -12
  216. package/src/daemon/tool-side-effects.ts +14 -56
  217. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +160 -0
  218. package/src/heartbeat/heartbeat-service.ts +24 -1
  219. package/src/home/__tests__/feed-population-integration.test.ts +312 -0
  220. package/src/home/emit-feed-event.ts +7 -0
  221. package/src/home/feed-types.ts +41 -2
  222. package/src/home/rewrite-command-preview.ts +66 -0
  223. package/src/ipc/__tests__/socket-path.test.ts +11 -50
  224. package/src/ipc/cli-client.ts +1 -1
  225. package/src/ipc/cli-server.ts +3 -3
  226. package/src/ipc/gateway-client.ts +4 -1
  227. package/src/ipc/routes/browser-context.ts +2 -0
  228. package/src/ipc/routes/browser.ts +1 -0
  229. package/src/ipc/routes/get-contact.ts +16 -0
  230. package/src/ipc/routes/index.ts +14 -0
  231. package/src/ipc/routes/list-clients.ts +31 -0
  232. package/src/ipc/routes/merge-contacts.ts +17 -0
  233. package/src/ipc/routes/notification.ts +133 -0
  234. package/src/ipc/routes/rename-conversation.ts +59 -0
  235. package/src/ipc/routes/search-contacts.ts +19 -0
  236. package/src/ipc/routes/upsert-contact.ts +25 -0
  237. package/src/ipc/socket-path.ts +14 -38
  238. package/src/media/app-icon-generator.ts +23 -46
  239. package/src/media/avatar-router.ts +26 -41
  240. package/src/media/gemini-image-service.ts +8 -41
  241. package/src/media/image-credentials.ts +73 -0
  242. package/src/media/image-service.ts +85 -0
  243. package/src/media/openai-image-service.ts +131 -0
  244. package/src/media/types.ts +46 -0
  245. package/src/memory/conversation-crud.ts +48 -18
  246. package/src/memory/conversation-queries.ts +57 -4
  247. package/src/memory/conversation-title-service.ts +25 -0
  248. package/src/memory/db-init.ts +8 -0
  249. package/src/memory/embedding-gemini.test.ts +41 -2
  250. package/src/memory/embedding-gemini.ts +6 -1
  251. package/src/memory/graph/bootstrap.test.ts +282 -0
  252. package/src/memory/graph/bootstrap.ts +8 -5
  253. package/src/memory/graph/extraction.ts +10 -2
  254. package/src/memory/graph/graph-search.test.ts +1 -0
  255. package/src/memory/graph/inspect.ts +2 -2
  256. package/src/memory/graph/retriever.ts +10 -3
  257. package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
  258. package/src/memory/migrations/149-oauth-tables.ts +1 -0
  259. package/src/memory/migrations/223-schedule-script-column.ts +11 -0
  260. package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
  261. package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
  262. package/src/memory/migrations/index.ts +4 -0
  263. package/src/memory/pkb/pkb-index.test.ts +1 -0
  264. package/src/memory/pkb/pkb-reconcile.test.ts +1 -0
  265. package/src/memory/pkb/pkb-search.test.ts +65 -4
  266. package/src/memory/pkb/pkb-search.ts +40 -18
  267. package/src/memory/qdrant-client.test.ts +60 -0
  268. package/src/memory/qdrant-client.ts +25 -0
  269. package/src/memory/schema/infrastructure.ts +1 -0
  270. package/src/memory/schema/oauth.ts +4 -1
  271. package/src/messaging/providers/slack/render-transcript.test.ts +77 -29
  272. package/src/messaging/providers/slack/render-transcript.ts +58 -0
  273. package/src/notifications/conversation-pairing.ts +78 -19
  274. package/src/notifications/copy-composer.ts +0 -5
  275. package/src/notifications/emit-signal.ts +1 -1
  276. package/src/notifications/signal.ts +1 -2
  277. package/src/oauth/AGENTS.md +1 -1
  278. package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
  279. package/src/oauth/connect-orchestrator.ts +8 -34
  280. package/src/oauth/connect-types.ts +6 -10
  281. package/src/oauth/manual-token-connection.ts +23 -0
  282. package/src/oauth/oauth-store.ts +30 -14
  283. package/src/oauth/provider-serializer.ts +6 -1
  284. package/src/oauth/seed-providers.ts +56 -108
  285. package/src/outbound-proxy/http-forwarder.ts +9 -0
  286. package/src/permissions/approval-policy.test.ts +293 -18
  287. package/src/permissions/approval-policy.ts +110 -58
  288. package/src/permissions/arg-parser.test.ts +161 -0
  289. package/src/permissions/arg-parser.ts +141 -0
  290. package/src/permissions/bash-risk-classifier.test.ts +414 -2
  291. package/src/permissions/bash-risk-classifier.ts +303 -60
  292. package/src/permissions/checker.ts +157 -29
  293. package/src/permissions/command-registry.test.ts +239 -0
  294. package/src/permissions/command-registry.ts +234 -54
  295. package/src/permissions/defaults.ts +5 -4
  296. package/src/permissions/gateway-threshold-reader.ts +196 -0
  297. package/src/permissions/prompter.ts +4 -0
  298. package/src/permissions/risk-types.ts +61 -4
  299. package/src/permissions/schedule-risk-classifier.test.ts +129 -0
  300. package/src/permissions/schedule-risk-classifier.ts +85 -0
  301. package/src/permissions/shell-identity.ts +2 -42
  302. package/src/permissions/types.ts +2 -0
  303. package/src/permissions/workspace-policy.ts +8 -3
  304. package/src/plugins/defaults/circuit-breaker.ts +146 -0
  305. package/src/plugins/defaults/compaction.ts +145 -0
  306. package/src/plugins/defaults/empty-response.ts +126 -0
  307. package/src/plugins/defaults/history-repair.ts +85 -0
  308. package/src/plugins/defaults/index.ts +116 -0
  309. package/src/plugins/defaults/injectors.ts +491 -0
  310. package/src/plugins/defaults/llm-call.ts +82 -0
  311. package/src/plugins/defaults/memory-retrieval.ts +226 -0
  312. package/src/plugins/defaults/overflow-reduce.ts +181 -0
  313. package/src/plugins/defaults/persistence.ts +129 -0
  314. package/src/plugins/defaults/title-generate.ts +95 -0
  315. package/src/plugins/defaults/token-estimate.ts +104 -0
  316. package/src/plugins/defaults/tool-error.ts +126 -0
  317. package/src/plugins/defaults/tool-execute.ts +89 -0
  318. package/src/plugins/defaults/tool-result-truncate.ts +88 -0
  319. package/src/plugins/pipeline.ts +316 -0
  320. package/src/plugins/plugin-skill-contributions.ts +292 -0
  321. package/src/plugins/registry.ts +241 -0
  322. package/src/plugins/types.ts +1134 -0
  323. package/src/plugins/user-loader.ts +177 -0
  324. package/src/prompts/templates/BOOTSTRAP.md +27 -77
  325. package/src/providers/model-catalog.ts +52 -29
  326. package/src/providers/model-intents.ts +1 -1
  327. package/src/providers/openrouter/client.ts +5 -1
  328. package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
  329. package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
  330. package/src/providers/speech-to-text/xai-realtime.test.ts +72 -4
  331. package/src/providers/speech-to-text/xai-realtime.ts +39 -14
  332. package/src/runtime/AGENTS.md +25 -16
  333. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
  334. package/src/runtime/__tests__/client-registry.test.ts +293 -0
  335. package/src/runtime/client-registry.ts +261 -0
  336. package/src/runtime/http-server.ts +77 -8
  337. package/src/runtime/http-types.ts +0 -2
  338. package/src/runtime/migrations/vbundle-builder.ts +1 -22
  339. package/src/runtime/routes/approval-prompt-ts-tracker.ts +51 -31
  340. package/src/runtime/routes/approval-routes.ts +17 -0
  341. package/src/runtime/routes/browser-extension-pair-routes.ts +27 -8
  342. package/src/runtime/routes/conversation-routes.ts +223 -116
  343. package/src/runtime/routes/inbound-message-handler.ts +88 -13
  344. package/src/runtime/routes/memory-item-routes.test.ts +1 -0
  345. package/src/runtime/routes/migration-routes.ts +0 -3
  346. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
  347. package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
  348. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
  349. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
  350. package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
  351. package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
  352. package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
  353. package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
  354. package/src/runtime/routes/playground/deps.ts +56 -0
  355. package/src/runtime/routes/playground/force-compact.ts +73 -0
  356. package/src/runtime/routes/playground/guard.ts +37 -0
  357. package/src/runtime/routes/playground/index.ts +28 -0
  358. package/src/runtime/routes/playground/inject-failures.ts +159 -0
  359. package/src/runtime/routes/playground/reset-circuit.ts +115 -0
  360. package/src/runtime/routes/playground/seed-conversation.ts +139 -0
  361. package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
  362. package/src/runtime/routes/playground/state.ts +78 -0
  363. package/src/runtime/routes/schedule-routes.ts +89 -8
  364. package/src/runtime/skill-route-registry.ts +75 -15
  365. package/src/schedule/run-script.ts +68 -0
  366. package/src/schedule/schedule-store.ts +7 -1
  367. package/src/schedule/scheduler.ts +48 -8
  368. package/src/skills/catalog-cache.ts +12 -5
  369. package/src/tools/browser/__tests__/browser-status.test.ts +189 -0
  370. package/src/tools/browser/browser-execution.ts +88 -19
  371. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
  372. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
  373. package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
  374. package/src/tools/browser/cdp-client/factory.ts +15 -4
  375. package/src/tools/executor.ts +126 -74
  376. package/src/tools/network/script-proxy/session-manager.ts +37 -1
  377. package/src/tools/permission-checker.ts +98 -49
  378. package/src/tools/policy-context.ts +4 -0
  379. package/src/tools/registry.ts +140 -3
  380. package/src/tools/schedule/create.ts +23 -8
  381. package/src/tools/schedule/update.ts +3 -1
  382. package/src/tools/secret-detection-handler.ts +0 -51
  383. package/src/tools/system/avatar-generator.ts +6 -2
  384. package/src/tools/types.ts +28 -2
  385. package/src/util/platform.ts +7 -2
  386. package/src/util/pricing.ts +26 -3
  387. package/src/workspace/migrations/006-services-config.ts +2 -4
  388. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
  389. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +3 -4
  390. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
  391. package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
  392. package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
  393. package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
  394. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
  395. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
  396. package/src/workspace/migrations/registry.ts +12 -0
  397. package/tsconfig.json +1 -1
  398. package/hook-templates/debug-prompt-logger/hook.json +0 -7
  399. package/hook-templates/debug-prompt-logger/run.sh +0 -66
  400. package/src/__tests__/compaction-circuit-breaker.test.ts +0 -336
  401. package/src/__tests__/context-overflow-approval.test.ts +0 -156
  402. package/src/__tests__/hooks-blocking.test.ts +0 -178
  403. package/src/__tests__/hooks-cli.test.ts +0 -182
  404. package/src/__tests__/hooks-config.test.ts +0 -108
  405. package/src/__tests__/hooks-discovery.test.ts +0 -211
  406. package/src/__tests__/hooks-integration.test.ts +0 -196
  407. package/src/__tests__/hooks-manager.test.ts +0 -226
  408. package/src/__tests__/hooks-runner.test.ts +0 -175
  409. package/src/__tests__/hooks-settings.test.ts +0 -160
  410. package/src/__tests__/hooks-templates.test.ts +0 -169
  411. package/src/__tests__/hooks-ts-runner.test.ts +0 -170
  412. package/src/__tests__/hooks-watch.test.ts +0 -112
  413. package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
  414. package/src/__tests__/oauth-scope-policy.test.ts +0 -180
  415. package/src/__tests__/send-notification-tool.test.ts +0 -83
  416. package/src/cli/commands/shotgun.ts +0 -266
  417. package/src/config/bundled-skills/conversations/SKILL.md +0 -20
  418. package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
  419. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -88
  420. package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
  421. package/src/config/bundled-skills/notifications/SKILL.md +0 -40
  422. package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
  423. package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
  424. package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
  425. package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
  426. package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
  427. package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
  428. package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
  429. package/src/daemon/context-overflow-approval.ts +0 -52
  430. package/src/daemon/watch-handler.ts +0 -399
  431. package/src/hooks/cli.ts +0 -253
  432. package/src/hooks/config.ts +0 -100
  433. package/src/hooks/discovery.ts +0 -135
  434. package/src/hooks/manager.ts +0 -179
  435. package/src/hooks/runner.ts +0 -117
  436. package/src/hooks/templates.ts +0 -77
  437. package/src/hooks/types.ts +0 -75
  438. package/src/oauth/scope-policy.ts +0 -89
  439. package/src/runtime/gateway-internal-client.ts +0 -94
  440. package/src/runtime/routes/watch-routes.ts +0 -156
  441. package/src/signals/shotgun.ts +0 -203
  442. package/src/tools/watch/screen-watch.ts +0 -144
  443. package/src/tools/watch/watch-state.ts +0 -142
@@ -568,6 +568,313 @@ describe("macOS message ingress with connected extension", () => {
568
568
  });
569
569
  });
570
570
 
571
+ // ── macOS SSE bridge ingress (no extension registry) ────────────────
572
+ //
573
+ // Exercises the cloud-hosted + desktop SSE bridge path for macOS turns
574
+ // WITHOUT relying on the ChromeExtensionRegistry. This validates the
575
+ // native macOS host-browser proxy path where `host_browser_request`
576
+ // frames travel through `assistantEventHub` (SSE) rather than the
577
+ // extension WebSocket.
578
+ //
579
+ // In production, this path is used when:
580
+ // - The macOS desktop client is connected to the assistant via SSE
581
+ // - The user does NOT have the Chrome extension installed
582
+ // - The desktop client receives `host_browser_request` frames via SSE,
583
+ // executes CDP commands against the local Chrome, and POSTs results
584
+ // back to `/v1/host-browser-result`
585
+ //
586
+ // The test constructs a HostBrowserProxy wired to a mock SSE sender
587
+ // (simulating the `onEvent` hub publisher) and a mock macOS client that
588
+ // observes the sent frames and returns results via POST.
589
+
590
+ describe("macOS SSE bridge ingress (no extension registry)", () => {
591
+ let server: RuntimeHttpServer;
592
+ let port: number;
593
+ let runtimeBaseUrl: string;
594
+
595
+ beforeEach(async () => {
596
+ const db = getDb();
597
+ db.run("DELETE FROM contact_channels");
598
+ db.run("DELETE FROM contacts");
599
+ pendingInteractions.clear();
600
+ __resetChromeExtensionRegistryForTests();
601
+
602
+ port = 20200 + Math.floor(Math.random() * 200);
603
+ runtimeBaseUrl = `http://127.0.0.1:${port}`;
604
+ server = new RuntimeHttpServer({ port });
605
+ await server.start();
606
+ });
607
+
608
+ afterEach(async () => {
609
+ await server?.stop();
610
+ pendingInteractions.clear();
611
+ __resetChromeExtensionRegistryForTests();
612
+ });
613
+
614
+ /**
615
+ * Create a HostBrowserProxy wired to a mock SSE sender. The sender
616
+ * captures `host_browser_request` frames and simulates what the macOS
617
+ * desktop client does: execute the CDP command locally and POST the
618
+ * result back to `/v1/host-browser-result`.
619
+ *
620
+ * Unlike `createBoundProxy` (which routes through the extension
621
+ * registry), this helper routes through a direct function call —
622
+ * simulating the `onEvent` SSE hub publisher path.
623
+ */
624
+ function createSseBoundProxy(
625
+ conversationId: string,
626
+ token: string,
627
+ ): {
628
+ proxy: HostBrowserProxy;
629
+ conversation: Conversation;
630
+ sentFrames: Array<{ type: string; [key: string]: unknown }>;
631
+ } {
632
+ let proxyRef: HostBrowserProxy | null = null;
633
+ const conversation = {
634
+ resolveHostBrowser(
635
+ requestId: string,
636
+ response: { content: string; isError: boolean },
637
+ ) {
638
+ proxyRef?.resolve(requestId, response);
639
+ },
640
+ } as unknown as Conversation;
641
+
642
+ const sentFrames: Array<{ type: string; [key: string]: unknown }> = [];
643
+
644
+ // The SSE sender simulates what `assistantEventHub.publish` does in
645
+ // production: it delivers the message to the connected SSE client.
646
+ // Here we capture the frame and immediately simulate the macOS client
647
+ // handling it — executing a mock CDP command and POSTing the result
648
+ // back to the runtime.
649
+ const sseSender = (msg: ServerMessage) => {
650
+ const frame = msg as { type: string; [key: string]: unknown };
651
+ sentFrames.push(frame);
652
+
653
+ if (frame.type === "host_browser_request") {
654
+ const requestId = frame.requestId as string;
655
+
656
+ // Register the pending interaction (in production this happens
657
+ // in makeHubPublisher inside conversation-routes.ts).
658
+ pendingInteractions.register(requestId, {
659
+ conversation,
660
+ conversationId,
661
+ kind: "host_browser",
662
+ });
663
+
664
+ // Simulate the macOS desktop client processing the CDP command
665
+ // and POSTing the result back to the runtime.
666
+ const cdpMethod = frame.cdpMethod as string;
667
+ let content: string;
668
+ let isError = false;
669
+ if (cdpMethod === "Browser.getVersion") {
670
+ content = JSON.stringify({
671
+ product: "Chrome/macOS-SSE-Test",
672
+ protocolVersion: "1.3",
673
+ revision: "@macos-sse",
674
+ userAgent: "Mozilla/5.0 (macOS SSE bridge e2e fixture)",
675
+ jsVersion: "0.0.0-macos-sse",
676
+ });
677
+ } else if (cdpMethod === "Runtime.evaluate") {
678
+ content = JSON.stringify({ result: { value: "complete" } });
679
+ } else {
680
+ content = `mock macOS client: unsupported cdpMethod "${cdpMethod}"`;
681
+ isError = true;
682
+ }
683
+
684
+ // POST result asynchronously (simulating the real macOS client).
685
+ void fetch(`${runtimeBaseUrl}/v1/host-browser-result`, {
686
+ method: "POST",
687
+ headers: {
688
+ "Content-Type": "application/json",
689
+ Authorization: `Bearer ${token}`,
690
+ },
691
+ body: JSON.stringify({ requestId, content, isError }),
692
+ })
693
+ .then((res) => res.body?.cancel())
694
+ .catch(() => {});
695
+ }
696
+ };
697
+
698
+ const proxy = new HostBrowserProxy(sseSender);
699
+ proxyRef = proxy;
700
+ return { proxy, conversation, sentFrames };
701
+ }
702
+
703
+ test("happy path: Browser.getVersion round-trips through the SSE bridge without extension registry", async () => {
704
+ const guardianId = `test-guardian-macos-sse-${crypto.randomUUID()}`;
705
+ const token = mintActorToken(guardianId);
706
+
707
+ const { proxy, sentFrames } = createSseBoundProxy(
708
+ "conv-macos-sse-happy",
709
+ token,
710
+ );
711
+
712
+ const result = await proxy.request(
713
+ { cdpMethod: "Browser.getVersion" },
714
+ "conv-macos-sse-happy",
715
+ );
716
+
717
+ // The request completed via the SSE bridge path, not the extension registry.
718
+ expect(result.isError).toBe(false);
719
+ expect(result.content).toContain("Chrome/macOS-SSE-Test");
720
+
721
+ // The SSE sender received exactly one host_browser_request frame.
722
+ const requests = sentFrames.filter(
723
+ (f) => f.type === "host_browser_request",
724
+ );
725
+ expect(requests).toHaveLength(1);
726
+ expect(requests[0].cdpMethod).toBe("Browser.getVersion");
727
+ expect(requests[0].conversationId).toBe("conv-macos-sse-happy");
728
+
729
+ // The extension registry should NOT have been involved — no entries exist.
730
+ expect(getChromeExtensionRegistry().get(guardianId)).toBeUndefined();
731
+
732
+ proxy.dispose();
733
+ });
734
+
735
+ test("abort: SSE-bridged request resolves to 'Aborted' when signal fires", async () => {
736
+ const guardianId = `test-guardian-macos-sse-abort-${crypto.randomUUID()}`;
737
+ const _token = mintActorToken(guardianId);
738
+
739
+ // Use a CDP handler that hangs forever so we can abort mid-flight.
740
+ const sentFrames: Array<{ type: string; [key: string]: unknown }> = [];
741
+ let proxyRef: HostBrowserProxy | null = null;
742
+ const conversation = {
743
+ resolveHostBrowser(
744
+ requestId: string,
745
+ response: { content: string; isError: boolean },
746
+ ) {
747
+ proxyRef?.resolve(requestId, response);
748
+ },
749
+ } as unknown as Conversation;
750
+
751
+ const hangingSender = (msg: ServerMessage) => {
752
+ const frame = msg as { type: string; [key: string]: unknown };
753
+ sentFrames.push(frame);
754
+ if (frame.type === "host_browser_request") {
755
+ const requestId = frame.requestId as string;
756
+ pendingInteractions.register(requestId, {
757
+ conversation,
758
+ conversationId: "conv-macos-sse-abort",
759
+ kind: "host_browser",
760
+ });
761
+ // Simulate a macOS client that never responds (hangs).
762
+ }
763
+ };
764
+
765
+ const proxy = new HostBrowserProxy(hangingSender);
766
+ proxyRef = proxy;
767
+
768
+ const controller = new AbortController();
769
+ const resultPromise = proxy.request(
770
+ { cdpMethod: "Browser.getVersion" },
771
+ "conv-macos-sse-abort",
772
+ controller.signal,
773
+ );
774
+
775
+ // Wait for the SSE sender to observe the request.
776
+ await waitFor(() => sentFrames.length === 1);
777
+
778
+ controller.abort();
779
+ const result = await resultPromise;
780
+
781
+ expect(result.content).toBe("Aborted");
782
+ expect(result.isError).toBe(true);
783
+
784
+ // The cancel frame should have been sent through the SSE sender.
785
+ const cancels = sentFrames.filter((f) => f.type === "host_browser_cancel");
786
+ expect(cancels).toHaveLength(1);
787
+
788
+ proxy.dispose();
789
+ });
790
+
791
+ test("timeout: SSE-bridged request surfaces timeout when macOS client never responds", async () => {
792
+ const guardianId = `test-guardian-macos-sse-timeout-${crypto.randomUUID()}`;
793
+ const _token = mintActorToken(guardianId);
794
+
795
+ const sentFrames: Array<{ type: string; [key: string]: unknown }> = [];
796
+ let proxyRef: HostBrowserProxy | null = null;
797
+ const conversation = {
798
+ resolveHostBrowser(
799
+ requestId: string,
800
+ response: { content: string; isError: boolean },
801
+ ) {
802
+ proxyRef?.resolve(requestId, response);
803
+ },
804
+ } as unknown as Conversation;
805
+
806
+ const hangingSender = (msg: ServerMessage) => {
807
+ const frame = msg as { type: string; [key: string]: unknown };
808
+ sentFrames.push(frame);
809
+ if (frame.type === "host_browser_request") {
810
+ const requestId = frame.requestId as string;
811
+ pendingInteractions.register(requestId, {
812
+ conversation,
813
+ conversationId: "conv-macos-sse-timeout",
814
+ kind: "host_browser",
815
+ });
816
+ // Never respond — simulate unresponsive macOS client.
817
+ }
818
+ };
819
+
820
+ const proxy = new HostBrowserProxy(hangingSender);
821
+ proxyRef = proxy;
822
+
823
+ const result = await proxy.request(
824
+ { cdpMethod: "Browser.getVersion", timeout_seconds: 0.05 },
825
+ "conv-macos-sse-timeout",
826
+ );
827
+
828
+ expect(result.isError).toBe(true);
829
+ expect(result.content).toContain("timed out");
830
+
831
+ // The SSE sender received the request frame (confirming the timeout
832
+ // is from the proxy timer, not a send failure).
833
+ const requests = sentFrames.filter(
834
+ (f) => f.type === "host_browser_request",
835
+ );
836
+ expect(requests).toHaveLength(1);
837
+ expect(requests[0].cdpMethod).toBe("Browser.getVersion");
838
+
839
+ proxy.dispose();
840
+ });
841
+
842
+ test("multiple sequential commands round-trip through the SSE bridge", async () => {
843
+ const guardianId = `test-guardian-macos-sse-seq-${crypto.randomUUID()}`;
844
+ const token = mintActorToken(guardianId);
845
+
846
+ const { proxy, sentFrames } = createSseBoundProxy(
847
+ "conv-macos-sse-seq",
848
+ token,
849
+ );
850
+
851
+ // First command: Browser.getVersion
852
+ const result1 = await proxy.request(
853
+ { cdpMethod: "Browser.getVersion" },
854
+ "conv-macos-sse-seq",
855
+ );
856
+ expect(result1.isError).toBe(false);
857
+ expect(result1.content).toContain("Chrome/macOS-SSE-Test");
858
+
859
+ // Second command: Runtime.evaluate
860
+ const result2 = await proxy.request(
861
+ { cdpMethod: "Runtime.evaluate", cdpParams: { expression: "1+1" } },
862
+ "conv-macos-sse-seq",
863
+ );
864
+ expect(result2.isError).toBe(false);
865
+
866
+ // Both requests went through the SSE sender.
867
+ const requests = sentFrames.filter(
868
+ (f) => f.type === "host_browser_request",
869
+ );
870
+ expect(requests).toHaveLength(2);
871
+ expect(requests[0].cdpMethod).toBe("Browser.getVersion");
872
+ expect(requests[1].cdpMethod).toBe("Runtime.evaluate");
873
+
874
+ proxy.dispose();
875
+ });
876
+ });
877
+
571
878
  // ── Local wait helpers ──────────────────────────────────────────────
572
879
 
573
880
  async function waitFor(
@@ -46,7 +46,7 @@ import {
46
46
  verifyHostBrowserCapability,
47
47
  } from "../runtime/capability-tokens.js";
48
48
  import {
49
- ALLOWED_EXTENSION_ORIGINS,
49
+ getAllowedExtensionOrigins,
50
50
  handleBrowserExtensionPair,
51
51
  } from "../runtime/routes/browser-extension-pair-routes.js";
52
52
 
@@ -81,10 +81,10 @@ const HELPER_BINARY = resolveHelperBinary();
81
81
  const HELPER_EXISTS = existsSync(HELPER_BINARY);
82
82
 
83
83
  const ALLOWED_ORIGIN = (() => {
84
- const first = Array.from(ALLOWED_EXTENSION_ORIGINS)[0];
84
+ const first = Array.from(getAllowedExtensionOrigins())[0];
85
85
  if (!first) {
86
86
  throw new Error(
87
- "ALLOWED_EXTENSION_ORIGINS must contain at least one extension origin for tests",
87
+ "getAllowedExtensionOrigins() must contain at least one extension origin for tests",
88
88
  );
89
89
  }
90
90
  return first;
@@ -42,11 +42,11 @@ describe("supportsHostProxy (runtime)", () => {
42
42
  expect(supportsHostProxy("chrome-extension", "host_cu")).toBe(false);
43
43
  });
44
44
 
45
- test("capability form grants host_bash/file/cu to macOS but not host_browser", () => {
45
+ test("capability form grants all four capabilities to macOS including host_browser", () => {
46
46
  expect(supportsHostProxy("macos", "host_bash")).toBe(true);
47
47
  expect(supportsHostProxy("macos", "host_file")).toBe(true);
48
48
  expect(supportsHostProxy("macos", "host_cu")).toBe(true);
49
- expect(supportsHostProxy("macos", "host_browser")).toBe(false);
49
+ expect(supportsHostProxy("macos", "host_browser")).toBe(true);
50
50
  });
51
51
 
52
52
  test("capability form rejects everything for non-host-proxy interfaces", () => {
@@ -163,3 +163,37 @@ describe("isHostProxyTransport", () => {
163
163
  expect(isHostProxyTransport(transport)).toBe(false);
164
164
  });
165
165
  });
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // macOS host_browser capability — regression guards
169
+ // ---------------------------------------------------------------------------
170
+
171
+ describe("macOS host_browser capability", () => {
172
+ test("macOS supports host_browser via supportsHostProxy capability check", () => {
173
+ // macOS is now host-browser-capable, enabling host_browser_request
174
+ // frames to be sent to the desktop client via SSE or extension registry.
175
+ expect(supportsHostProxy("macos", "host_browser")).toBe(true);
176
+ });
177
+
178
+ test("macOS still passes the no-arg host-proxy check (full desktop proxy)", () => {
179
+ // The no-arg form gates computer-use preactivation and full proxy restore.
180
+ // macOS must still pass this check.
181
+ expect(supportsHostProxy("macos")).toBe(true);
182
+ });
183
+
184
+ test("non-macOS non-extension interfaces remain host_browser-ineligible", () => {
185
+ const ineligible: InterfaceId[] = [
186
+ "ios",
187
+ "cli",
188
+ "telegram",
189
+ "phone",
190
+ "vellum",
191
+ "whatsapp",
192
+ "slack",
193
+ "email",
194
+ ];
195
+ for (const id of ineligible) {
196
+ expect(supportsHostProxy(id, "host_browser")).toBe(false);
197
+ }
198
+ });
199
+ });
@@ -0,0 +1,137 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Module mocks — must appear before importing the module under test
5
+ // ---------------------------------------------------------------------------
6
+
7
+ let mockProviderKey: string | undefined;
8
+ let mockPlatformBaseUrl = "";
9
+ let mockAssistantApiKey = "";
10
+
11
+ mock.module("../security/secure-keys.js", () => ({
12
+ getProviderKeyAsync: async (_provider: string) => mockProviderKey,
13
+ }));
14
+
15
+ mock.module("../providers/managed-proxy/context.js", () => ({
16
+ resolveManagedProxyContext: async () => ({
17
+ enabled: !!mockPlatformBaseUrl && !!mockAssistantApiKey,
18
+ platformBaseUrl: mockPlatformBaseUrl,
19
+ assistantApiKey: mockAssistantApiKey,
20
+ }),
21
+ }));
22
+
23
+ // Import after mocks
24
+ import { resolveImageGenCredentials } from "../media/image-credentials.js";
25
+
26
+ describe("resolveImageGenCredentials", () => {
27
+ beforeEach(() => {
28
+ mockProviderKey = undefined;
29
+ mockPlatformBaseUrl = "";
30
+ mockAssistantApiKey = "";
31
+ });
32
+
33
+ describe("managed mode", () => {
34
+ test("returns managed-proxy credentials when context is enabled", async () => {
35
+ mockPlatformBaseUrl = "https://platform.example.com";
36
+ mockAssistantApiKey = "sk-assistant-key";
37
+
38
+ const result = await resolveImageGenCredentials({
39
+ provider: "gemini",
40
+ mode: "managed",
41
+ });
42
+
43
+ expect(result.errorHint).toBeUndefined();
44
+ expect(result.credentials).toEqual({
45
+ type: "managed-proxy",
46
+ assistantApiKey: "sk-assistant-key",
47
+ baseUrl: "https://platform.example.com/v1/runtime-proxy/gemini",
48
+ });
49
+ });
50
+
51
+ test("returns errorHint mentioning 'log in to Vellum' when platform URL is missing", async () => {
52
+ mockPlatformBaseUrl = "";
53
+ mockAssistantApiKey = "sk-assistant-key";
54
+
55
+ const result = await resolveImageGenCredentials({
56
+ provider: "gemini",
57
+ mode: "managed",
58
+ });
59
+
60
+ expect(result.credentials).toBeUndefined();
61
+ expect(result.errorHint).toBeDefined();
62
+ expect(result.errorHint).toContain("log in to Vellum");
63
+ });
64
+
65
+ test("returns errorHint when assistant API key is empty (TOCTOU-safe)", async () => {
66
+ mockPlatformBaseUrl = "https://platform.example.com";
67
+ mockAssistantApiKey = "";
68
+
69
+ const result = await resolveImageGenCredentials({
70
+ provider: "gemini",
71
+ mode: "managed",
72
+ });
73
+
74
+ expect(result.credentials).toBeUndefined();
75
+ expect(result.errorHint).toBeDefined();
76
+ expect(result.errorHint).toContain("log in to Vellum");
77
+ });
78
+ });
79
+
80
+ describe("your-own mode", () => {
81
+ test("returns direct credentials for gemini when key is present", async () => {
82
+ mockProviderKey = "gemini-api-key";
83
+
84
+ const result = await resolveImageGenCredentials({
85
+ provider: "gemini",
86
+ mode: "your-own",
87
+ });
88
+
89
+ expect(result.errorHint).toBeUndefined();
90
+ expect(result.credentials).toEqual({
91
+ type: "direct",
92
+ apiKey: "gemini-api-key",
93
+ });
94
+ });
95
+
96
+ test("returns errorHint mentioning 'Gemini API key' when no key is set", async () => {
97
+ mockProviderKey = undefined;
98
+
99
+ const result = await resolveImageGenCredentials({
100
+ provider: "gemini",
101
+ mode: "your-own",
102
+ });
103
+
104
+ expect(result.credentials).toBeUndefined();
105
+ expect(result.errorHint).toBeDefined();
106
+ expect(result.errorHint).toContain("Gemini API key");
107
+ });
108
+
109
+ test("returns direct credentials for openai when key is present", async () => {
110
+ mockProviderKey = "openai-api-key";
111
+
112
+ const result = await resolveImageGenCredentials({
113
+ provider: "openai",
114
+ mode: "your-own",
115
+ });
116
+
117
+ expect(result.errorHint).toBeUndefined();
118
+ expect(result.credentials).toEqual({
119
+ type: "direct",
120
+ apiKey: "openai-api-key",
121
+ });
122
+ });
123
+
124
+ test("returns errorHint mentioning 'OpenAI API key' when no key is set", async () => {
125
+ mockProviderKey = undefined;
126
+
127
+ const result = await resolveImageGenCredentials({
128
+ provider: "openai",
129
+ mode: "your-own",
130
+ });
131
+
132
+ expect(result.credentials).toBeUndefined();
133
+ expect(result.errorHint).toBeDefined();
134
+ expect(result.errorHint).toContain("OpenAI API key");
135
+ });
136
+ });
137
+ });