@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
package/openapi.yaml CHANGED
@@ -3,7 +3,7 @@
3
3
  openapi: 3.0.0
4
4
  info:
5
5
  title: Vellum Assistant API
6
- version: 0.6.5
6
+ version: 0.6.6
7
7
  description: Auto-generated OpenAPI specification for the Vellum Assistant runtime HTTP server.
8
8
  servers:
9
9
  - url: http://127.0.0.1:7821
@@ -1894,72 +1894,6 @@ paths:
1894
1894
  responses:
1895
1895
  "200":
1896
1896
  description: Successful response
1897
- /v1/computer-use/watch:
1898
- post:
1899
- operationId: computeruse_watch_post
1900
- summary: Submit watch observation
1901
- description: Send a screen observation from ambient watch mode.
1902
- tags:
1903
- - computer-use
1904
- responses:
1905
- "200":
1906
- description: Successful response
1907
- content:
1908
- application/json:
1909
- schema:
1910
- type: object
1911
- properties:
1912
- ok:
1913
- type: boolean
1914
- required:
1915
- - ok
1916
- additionalProperties: false
1917
- requestBody:
1918
- required: true
1919
- content:
1920
- application/json:
1921
- schema:
1922
- type: object
1923
- properties:
1924
- watchId:
1925
- type: string
1926
- description: Watch session ID
1927
- conversationId:
1928
- type: string
1929
- description: Conversation to associate with
1930
- ocrText:
1931
- type: string
1932
- description: OCR text from screen capture
1933
- appName:
1934
- type: string
1935
- description: Active application name
1936
- windowTitle:
1937
- type: string
1938
- description: Active window title
1939
- bundleIdentifier:
1940
- type: string
1941
- description: Application bundle identifier
1942
- timestamp:
1943
- type: number
1944
- description: Capture timestamp (epoch ms)
1945
- captureIndex:
1946
- type: integer
1947
- minimum: -9007199254740991
1948
- maximum: 9007199254740991
1949
- description: Index of this capture in the batch
1950
- totalExpected:
1951
- type: integer
1952
- minimum: -9007199254740991
1953
- maximum: 9007199254740991
1954
- description: Total captures expected in the batch
1955
- required:
1956
- - watchId
1957
- - conversationId
1958
- - ocrText
1959
- - timestamp
1960
- - captureIndex
1961
- - totalExpected
1962
- additionalProperties: false
1963
1897
  /v1/config:
1964
1898
  get:
1965
1899
  operationId: config_get
@@ -2880,6 +2814,82 @@ paths:
2880
2814
  required:
2881
2815
  - name
2882
2816
  additionalProperties: false
2817
+ /v1/conversations/{id}/playground/compact:
2818
+ post:
2819
+ operationId: conversations_by_id_playground_compact_post
2820
+ summary: Force compaction on a conversation (dev-only playground)
2821
+ tags:
2822
+ - playground
2823
+ responses:
2824
+ "200":
2825
+ description: Successful response
2826
+ parameters:
2827
+ - name: id
2828
+ in: path
2829
+ required: true
2830
+ schema:
2831
+ type: string
2832
+ /v1/conversations/{id}/playground/compaction-state:
2833
+ get:
2834
+ operationId: conversations_by_id_playground_compactionstate_get
2835
+ summary: Read current compaction state for a conversation
2836
+ tags:
2837
+ - playground
2838
+ responses:
2839
+ "200":
2840
+ description: Successful response
2841
+ parameters:
2842
+ - name: id
2843
+ in: path
2844
+ required: true
2845
+ schema:
2846
+ type: string
2847
+ /v1/conversations/{id}/playground/inject-compaction-failures:
2848
+ post:
2849
+ operationId: conversations_by_id_playground_injectcompactionfailures_post
2850
+ summary: Directly mutate compaction circuit-breaker state (dev-only playground)
2851
+ tags:
2852
+ - playground
2853
+ responses:
2854
+ "200":
2855
+ description: Successful response
2856
+ parameters:
2857
+ - name: id
2858
+ in: path
2859
+ required: true
2860
+ schema:
2861
+ type: string
2862
+ requestBody:
2863
+ required: true
2864
+ content:
2865
+ application/json:
2866
+ schema:
2867
+ type: object
2868
+ properties:
2869
+ consecutiveFailures:
2870
+ type: integer
2871
+ minimum: 0
2872
+ maximum: 10
2873
+ circuitOpenForMs:
2874
+ type: integer
2875
+ minimum: 0
2876
+ maximum: 86400000
2877
+ additionalProperties: false
2878
+ /v1/conversations/{id}/playground/reset-compaction-circuit:
2879
+ post:
2880
+ operationId: conversations_by_id_playground_resetcompactioncircuit_post
2881
+ summary: Clear compaction circuit-breaker state (dev-only playground)
2882
+ tags:
2883
+ - playground
2884
+ responses:
2885
+ "200":
2886
+ description: Successful response
2887
+ parameters:
2888
+ - name: id
2889
+ in: path
2890
+ required: true
2891
+ schema:
2892
+ type: string
2883
2893
  /v1/conversations/{id}/regenerate:
2884
2894
  post:
2885
2895
  operationId: conversations_by_id_regenerate_post
@@ -4234,6 +4244,7 @@ paths:
4234
4244
  - slack
4235
4245
  - calendar
4236
4246
  - assistant
4247
+ - telegram
4237
4248
  timestamp:
4238
4249
  type: string
4239
4250
  status:
@@ -4273,6 +4284,23 @@ paths:
4273
4284
  - medium
4274
4285
  - high
4275
4286
  - critical
4287
+ conversationId:
4288
+ type: string
4289
+ detailPanel:
4290
+ type: object
4291
+ properties:
4292
+ kind:
4293
+ type: string
4294
+ enum:
4295
+ - emailDraft
4296
+ - documentPreview
4297
+ - permissionChat
4298
+ - paymentAuth
4299
+ - toolPermission
4300
+ - updatesList
4301
+ required:
4302
+ - kind
4303
+ additionalProperties: false
4276
4304
  author:
4277
4305
  type: string
4278
4306
  enum:
@@ -4405,6 +4433,7 @@ paths:
4405
4433
  - slack
4406
4434
  - calendar
4407
4435
  - assistant
4436
+ - telegram
4408
4437
  timestamp:
4409
4438
  type: string
4410
4439
  status:
@@ -4444,6 +4473,23 @@ paths:
4444
4473
  - medium
4445
4474
  - high
4446
4475
  - critical
4476
+ conversationId:
4477
+ type: string
4478
+ detailPanel:
4479
+ type: object
4480
+ properties:
4481
+ kind:
4482
+ type: string
4483
+ enum:
4484
+ - emailDraft
4485
+ - documentPreview
4486
+ - permissionChat
4487
+ - paymentAuth
4488
+ - toolPermission
4489
+ - updatesList
4490
+ required:
4491
+ - kind
4492
+ additionalProperties: false
4447
4493
  author:
4448
4494
  type: string
4449
4495
  enum:
@@ -6056,6 +6102,70 @@ paths:
6056
6102
  schema:
6057
6103
  type: string
6058
6104
  description: Conversation ID
6105
+ /v1/playground/seed-conversation:
6106
+ post:
6107
+ operationId: playground_seedconversation_post
6108
+ summary: Create a synthetic seeded conversation for compaction testing
6109
+ tags:
6110
+ - playground
6111
+ responses:
6112
+ "200":
6113
+ description: Successful response
6114
+ requestBody:
6115
+ required: true
6116
+ content:
6117
+ application/json:
6118
+ schema:
6119
+ type: object
6120
+ properties:
6121
+ turns:
6122
+ type: integer
6123
+ exclusiveMinimum: 0
6124
+ maximum: 500
6125
+ avgTokensPerTurn:
6126
+ default: 500
6127
+ type: integer
6128
+ exclusiveMinimum: 0
6129
+ maximum: 5000
6130
+ title:
6131
+ type: string
6132
+ maxLength: 120
6133
+ required:
6134
+ - turns
6135
+ - avgTokensPerTurn
6136
+ additionalProperties: false
6137
+ /v1/playground/seeded-conversations:
6138
+ delete:
6139
+ operationId: playground_seededconversations_delete
6140
+ summary: Delete every seeded playground conversation (prefix-gated)
6141
+ tags:
6142
+ - playground
6143
+ responses:
6144
+ "200":
6145
+ description: Successful response
6146
+ get:
6147
+ operationId: playground_seededconversations_get
6148
+ summary: List conversations created by the seed-conversation endpoint
6149
+ tags:
6150
+ - playground
6151
+ responses:
6152
+ "200":
6153
+ description: Successful response
6154
+ /v1/playground/seeded-conversations/{id}:
6155
+ delete:
6156
+ operationId: playground_seededconversations_by_id_delete
6157
+ summary: Delete a single seeded conversation (prefix-gated)
6158
+ tags:
6159
+ - playground
6160
+ responses:
6161
+ "200":
6162
+ description: Successful response
6163
+ parameters:
6164
+ - name: id
6165
+ in: path
6166
+ required: true
6167
+ schema:
6168
+ type: string
6059
6169
  /v1/profiler/runs:
6060
6170
  get:
6061
6171
  operationId: profiler_runs_get
@@ -6532,9 +6642,14 @@ paths:
6532
6642
  type: string
6533
6643
  message:
6534
6644
  type: string
6645
+ script:
6646
+ anyOf:
6647
+ - type: string
6648
+ - type: "null"
6649
+ description: Shell command for script mode
6535
6650
  mode:
6536
6651
  type: string
6537
- description: notify or execute
6652
+ description: notify, execute, or script
6538
6653
  routingIntent:
6539
6654
  type: string
6540
6655
  description: single_channel, multi_channel, or all_channels
@@ -6547,6 +6662,7 @@ paths:
6547
6662
  - expression
6548
6663
  - timezone
6549
6664
  - message
6665
+ - script
6550
6666
  - mode
6551
6667
  - routingIntent
6552
6668
  - quiet
@@ -6608,6 +6724,34 @@ paths:
6608
6724
  required: true
6609
6725
  schema:
6610
6726
  type: string
6727
+ /v1/schedules/{id}/runs:
6728
+ get:
6729
+ operationId: schedules_by_id_runs_get
6730
+ summary: List schedule runs
6731
+ description: Return recent invocation history for a schedule.
6732
+ tags:
6733
+ - schedules
6734
+ responses:
6735
+ "200":
6736
+ description: Successful response
6737
+ content:
6738
+ application/json:
6739
+ schema:
6740
+ type: object
6741
+ properties:
6742
+ runs:
6743
+ type: array
6744
+ items: {}
6745
+ description: Schedule run objects
6746
+ required:
6747
+ - runs
6748
+ additionalProperties: false
6749
+ parameters:
6750
+ - name: id
6751
+ in: path
6752
+ required: true
6753
+ schema:
6754
+ type: string
6611
6755
  /v1/schedules/{id}/toggle:
6612
6756
  post:
6613
6757
  operationId: schedules_by_id_toggle_post
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -355,6 +355,63 @@ console.log("styled");`,
355
355
  const r2 = await compileApp(appDir2);
356
356
  expect(r2.ok).toBe(true);
357
357
  }, 30_000);
358
+
359
+ test("concurrent callers during an in-flight compile coalesce to at most one follow-up", async () => {
360
+ const appDir = await scaffold("concurrent-dedup", {
361
+ "main.tsx": `console.log("hello");`,
362
+ "index.html": MINIMAL_HTML,
363
+ });
364
+
365
+ // Kick off three concurrent compiles. Serialisation guarantees the
366
+ // first runs to completion alone; callers 2 and 3 coalesce into a
367
+ // single follow-up compile that begins after the first settles.
368
+ const [r1, r2, r3] = await Promise.all([
369
+ compileApp(appDir),
370
+ compileApp(appDir),
371
+ compileApp(appDir),
372
+ ]);
373
+
374
+ expect(r1.ok).toBe(true);
375
+ expect(r2.ok).toBe(true);
376
+ expect(r3.ok).toBe(true);
377
+
378
+ // Callers 2 and 3 share the coalesced follow-up promise.
379
+ expect(r2).toBe(r3);
380
+ // Caller 1 ran a distinct compile from the coalesced follow-up.
381
+ expect(r1).not.toBe(r2);
382
+
383
+ // Final dist/ output must be intact regardless of how many compiles
384
+ // ran: main.js non-empty and index.html has the injected script tag.
385
+ const js = await readFile(join(appDir, "dist", "main.js"), "utf-8");
386
+ expect(js.length).toBeGreaterThan(0);
387
+ const html = await readFile(join(appDir, "dist", "index.html"), "utf-8");
388
+ expect(html).toContain('src="main.js"');
389
+ });
390
+
391
+ test("source edit during an in-flight compile is picked up by the follow-up", async () => {
392
+ const appDir = await scaffold("mid-build-edit", {
393
+ "main.tsx": `console.log("original");`,
394
+ "index.html": MINIMAL_HTML,
395
+ });
396
+
397
+ // Start the first compile but do not await it yet.
398
+ const first = compileApp(appDir);
399
+
400
+ // Mutate the source while the first compile is still running, then
401
+ // request another compile. The follow-up must read the updated source
402
+ // rather than silently reusing the first compile's (now stale) output.
403
+ await writeFile(join(appDir, "src", "main.tsx"), `console.log("updated");`);
404
+ const second = compileApp(appDir);
405
+
406
+ const [r1, r2] = await Promise.all([first, second]);
407
+ expect(r1.ok).toBe(true);
408
+ expect(r2.ok).toBe(true);
409
+ expect(r1).not.toBe(r2);
410
+
411
+ const js = await readFile(join(appDir, "dist", "main.js"), "utf-8");
412
+ expect(js).toContain("updated");
413
+ expect(js).not.toContain("original");
414
+ });
358
415
  });
359
416
 
360
417
  // ---------------------------------------------------------------------------
@@ -56,7 +56,7 @@ mock.module("../providers/registry.js", () => ({
56
56
 
57
57
  mock.module("../config/loader.js", () => ({
58
58
  getConfig: () => ({
59
- ui: {},
59
+ ui: {},
60
60
  llm: {
61
61
  default: {
62
62
  provider: "mock-provider",
@@ -205,6 +205,9 @@ mock.module("../agent/loop.js", () => ({
205
205
  getToolTokenBudget() {
206
206
  return 0;
207
207
  }
208
+ getResolvedTools() {
209
+ return [];
210
+ }
208
211
  getActiveModel() {
209
212
  return undefined;
210
213
  }
@@ -213,7 +216,9 @@ mock.module("../agent/loop.js", () => ({
213
216
  _onEvent: (event: AgentEvent) => void,
214
217
  _signal?: AbortSignal,
215
218
  _requestId?: string,
216
- _onCheckpoint?: (checkpoint: CheckpointInfo) => CheckpointDecision,
219
+ _onCheckpoint?: (
220
+ checkpoint: CheckpointInfo,
221
+ ) => CheckpointDecision | Promise<CheckpointDecision>,
217
222
  ): Promise<Message[]> {
218
223
  return [];
219
224
  }
@@ -50,6 +50,7 @@ mock.module("../memory/qdrant-client.js", () => ({
50
50
  deletePoints: async () => {},
51
51
  }),
52
52
  initQdrantClient: () => {},
53
+ resolveQdrantUrl: () => "http://127.0.0.1:6333",
53
54
  }));
54
55
 
55
56
  // ── Test config ────────────────────────────────────────────────────
@@ -96,8 +96,10 @@ describe("generateAndSaveAvatar", () => {
96
96
  expect(result.content).toContain("No image data returned");
97
97
  });
98
98
 
99
- test("generic error returns mapped message", async () => {
100
- mockRouterError = new Error("Network timeout");
99
+ test("router-mapped error message is surfaced verbatim", async () => {
100
+ // avatar-router now maps provider errors before throwing, so the
101
+ // generator just surfaces error.message directly.
102
+ mockRouterError = new Error("Image generation failed: Network timeout");
101
103
 
102
104
  const result = await executeAvatar("a cat");
103
105
 
@@ -101,18 +101,18 @@ describe("resolveBundledDir", () => {
101
101
  const macosDir = join(tempDir, "Contents", "MacOS");
102
102
  const resourcesDir = join(tempDir, "Contents", "Resources");
103
103
  mkdirSync(macosDir, { recursive: true });
104
- mkdirSync(join(resourcesDir, "hook-templates"), { recursive: true });
104
+ mkdirSync(join(resourcesDir, "compact-prompts"), { recursive: true });
105
105
  // Also create at execDir level
106
- mkdirSync(join(macosDir, "hook-templates"), { recursive: true });
106
+ mkdirSync(join(macosDir, "compact-prompts"), { recursive: true });
107
107
 
108
108
  process.execPath = join(macosDir, "vellum-daemon");
109
109
 
110
110
  const result = resolveBundledDir(
111
- "/$bunfs/root/src/hooks",
112
- "../../hook-templates",
113
- "hook-templates",
111
+ "/$bunfs/root/src/context/prompts",
112
+ "..",
113
+ "compact-prompts",
114
114
  );
115
- expect(result).toBe(join(resourcesDir, "hook-templates"));
115
+ expect(result).toBe(join(resourcesDir, "compact-prompts"));
116
116
  });
117
117
 
118
118
  test("works with different bundleName values", () => {
@@ -184,4 +184,73 @@ describe("getCatalog", () => {
184
184
  expect(readLocalCatalogCallCount).toBe(1);
185
185
  expect(fetchCatalogCallCount).toBe(1); // attempted remote fetch
186
186
  });
187
+
188
+ test("preserves merged cache when later remote fetch fails", async () => {
189
+ mockRepoSkillsDir = "/mock/repo/skills";
190
+ mockLocalCatalog = [
191
+ { id: "web-search", name: "Local Web Search", description: "Local" },
192
+ ];
193
+ mockFetchCatalogResult = [
194
+ { id: "web-search", name: "Remote Web Search", description: "Remote" },
195
+ { id: "remote-only", name: "Remote Only", description: "Remote only" },
196
+ ];
197
+
198
+ // Prime the cache with a successful merged fetch.
199
+ const merged = await getCatalog();
200
+ expect(merged).toEqual([
201
+ { id: "web-search", name: "Local Web Search", description: "Local" },
202
+ { id: "remote-only", name: "Remote Only", description: "Remote only" },
203
+ ]);
204
+
205
+ // Expire TTL and make remote fetch fail.
206
+ const originalNow = Date.now;
207
+ Date.now = () => originalNow() + 5 * 60 * 1000 + 1;
208
+ try {
209
+ mockFetchCatalogError = new Error("Network timeout");
210
+ const fallback = await getCatalog();
211
+ // Must retain remote-only skills rather than regressing to bare local.
212
+ expect(fallback).toEqual(merged);
213
+ } finally {
214
+ Date.now = originalNow;
215
+ }
216
+ });
217
+
218
+ test("resets TTL on stale-cache fallback so subsequent calls hit cache", async () => {
219
+ mockFetchCatalogResult = sampleCatalog;
220
+
221
+ // Prime cache.
222
+ await getCatalog();
223
+ expect(fetchCatalogCallCount).toBe(1);
224
+
225
+ // Expire TTL, trigger fallback once.
226
+ const originalNow = Date.now;
227
+ let clock = originalNow() + 5 * 60 * 1000 + 1;
228
+ Date.now = () => clock;
229
+ try {
230
+ mockFetchCatalogError = new Error("Network timeout");
231
+ const first = await getCatalog();
232
+ expect(first).toEqual(sampleCatalog);
233
+ expect(fetchCatalogCallCount).toBe(2);
234
+
235
+ // Advance clock by less than the TTL — subsequent calls must be served
236
+ // from the refreshed cache window without re-entering fetchCatalog().
237
+ clock += 60 * 1000;
238
+ const second = await getCatalog();
239
+ expect(second).toEqual(sampleCatalog);
240
+ expect(fetchCatalogCallCount).toBe(2);
241
+
242
+ clock += 2 * 60 * 1000;
243
+ const third = await getCatalog();
244
+ expect(third).toEqual(sampleCatalog);
245
+ expect(fetchCatalogCallCount).toBe(2);
246
+
247
+ // Once the refreshed TTL elapses, the next call retries the remote.
248
+ clock += 5 * 60 * 1000 + 1;
249
+ const fourth = await getCatalog();
250
+ expect(fourth).toEqual(sampleCatalog);
251
+ expect(fetchCatalogCallCount).toBe(3);
252
+ } finally {
253
+ Date.now = originalNow;
254
+ }
255
+ });
187
256
  });