@vellumai/assistant 0.8.3 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/docker-entrypoint.sh +0 -1
  2. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  3. package/openapi.yaml +610 -16
  4. package/package.json +1 -1
  5. package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
  6. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  7. package/src/__tests__/agent-loop.test.ts +88 -3
  8. package/src/__tests__/anthropic-provider.test.ts +272 -0
  9. package/src/__tests__/approval-cascade.test.ts +1 -1
  10. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  11. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  12. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  13. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  14. package/src/__tests__/compaction-events.test.ts +1 -1
  15. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  16. package/src/__tests__/config-watcher.test.ts +1 -1
  17. package/src/__tests__/context-token-estimator.test.ts +91 -1
  18. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  19. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +25 -7
  22. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  23. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  24. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  25. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  26. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  27. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  28. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  29. package/src/__tests__/conversation-pairing.test.ts +2 -2
  30. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  31. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  32. package/src/__tests__/conversation-queue.test.ts +1 -1
  33. package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
  34. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  35. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  36. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  37. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  38. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  39. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  40. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  41. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  42. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  43. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  44. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  45. package/src/__tests__/dm-backfill.test.ts +64 -0
  46. package/src/__tests__/dm-persistence.test.ts +33 -0
  47. package/src/__tests__/document-find-replace.test.ts +501 -0
  48. package/src/__tests__/first-greeting.test.ts +23 -2
  49. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  50. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  51. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  52. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  53. package/src/__tests__/host-file-proxy.test.ts +8 -1
  54. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  55. package/src/__tests__/identity-routes.test.ts +57 -0
  56. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  57. package/src/__tests__/injector-chain.test.ts +2 -0
  58. package/src/__tests__/injector-document-comments.test.ts +378 -0
  59. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  60. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  61. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  62. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  63. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  64. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  65. package/src/__tests__/llm-resolver.test.ts +85 -1
  66. package/src/__tests__/log-export-routes.test.ts +99 -2
  67. package/src/__tests__/message-queue-steer.test.ts +114 -0
  68. package/src/__tests__/openai-provider.test.ts +105 -0
  69. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  70. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  71. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  72. package/src/__tests__/platform.test.ts +0 -3
  73. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  74. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  75. package/src/__tests__/process-message-display-content.test.ts +21 -16
  76. package/src/__tests__/server-history-render.test.ts +83 -4
  77. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  78. package/src/__tests__/system-prompt.test.ts +51 -28
  79. package/src/__tests__/terminal-tools.test.ts +11 -1
  80. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  81. package/src/__tests__/thread-backfill.test.ts +370 -22
  82. package/src/__tests__/tool-executor.test.ts +90 -1
  83. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  84. package/src/__tests__/twilio-routes.test.ts +1 -1
  85. package/src/__tests__/web-fetch.test.ts +2 -2
  86. package/src/__tests__/workspace-git-service.test.ts +88 -5
  87. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  88. package/src/agent/attachments.ts +1 -0
  89. package/src/agent/loop.ts +57 -20
  90. package/src/background-wake/next-wake.test.ts +289 -0
  91. package/src/background-wake/next-wake.ts +172 -0
  92. package/src/browser/operations.ts +15 -0
  93. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  94. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  95. package/src/cli/commands/conversations.ts +128 -1
  96. package/src/cli/commands/inference-providers.ts +147 -1
  97. package/src/cli/commands/memory-v2.ts +308 -0
  98. package/src/cli/commands/notifications.ts +24 -2
  99. package/src/cli/utils/conversation-id.ts +17 -5
  100. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  101. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  102. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  103. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  104. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  105. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  106. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  107. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  108. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  109. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  110. package/src/config/bundled-tool-registry.ts +22 -12
  111. package/src/config/call-site-defaults.ts +19 -0
  112. package/src/config/feature-flag-registry.json +99 -3
  113. package/src/config/llm-resolver.ts +16 -2
  114. package/src/config/schemas/__tests__/memory-v2.test.ts +4 -0
  115. package/src/config/schemas/call-site-catalog.ts +21 -0
  116. package/src/config/schemas/llm.ts +3 -0
  117. package/src/config/schemas/memory-v2.ts +48 -1
  118. package/src/context/compactor.ts +8 -1
  119. package/src/context/token-estimator.ts +47 -4
  120. package/src/context/window-manager.ts +25 -0
  121. package/src/credential-health/credential-health-service.ts +34 -19
  122. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  123. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  124. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  125. package/src/daemon/conversation-agent-loop-handlers.ts +153 -23
  126. package/src/daemon/conversation-agent-loop.ts +223 -54
  127. package/src/daemon/conversation-lifecycle.ts +142 -116
  128. package/src/daemon/conversation-messaging.ts +3 -0
  129. package/src/daemon/conversation-process.ts +273 -0
  130. package/src/daemon/conversation-queue-manager.ts +14 -0
  131. package/src/daemon/conversation-runtime-assembly.ts +135 -75
  132. package/src/daemon/conversation-slash.ts +37 -5
  133. package/src/daemon/conversation-surfaces.ts +45 -2
  134. package/src/daemon/conversation-tool-setup.ts +7 -0
  135. package/src/daemon/conversation.ts +42 -5
  136. package/src/daemon/first-greeting.ts +10 -0
  137. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  138. package/src/daemon/handlers/config-a2a.ts +160 -0
  139. package/src/daemon/handlers/config-model.test.ts +1 -0
  140. package/src/daemon/handlers/conversations.ts +79 -0
  141. package/src/daemon/handlers/shared.ts +92 -29
  142. package/src/daemon/host-bash-proxy.ts +1 -1
  143. package/src/daemon/host-cu-proxy.ts +1 -1
  144. package/src/daemon/host-file-proxy.ts +1 -1
  145. package/src/daemon/host-transfer-proxy.ts +1 -1
  146. package/src/daemon/lifecycle.ts +18 -4
  147. package/src/daemon/message-protocol.ts +4 -0
  148. package/src/daemon/message-types/conversations.ts +8 -0
  149. package/src/daemon/message-types/document-comments.ts +50 -0
  150. package/src/daemon/message-types/messages.ts +68 -1
  151. package/src/daemon/message-types/surfaces.ts +3 -1
  152. package/src/daemon/message-types/web-activity.ts +57 -0
  153. package/src/daemon/plugin-source-watcher.ts +135 -3
  154. package/src/daemon/process-message.ts +69 -12
  155. package/src/daemon/query-complexity-router.ts +75 -0
  156. package/src/daemon/trust-context.ts +6 -0
  157. package/src/documents/document-comments-store.test.ts +338 -0
  158. package/src/documents/document-comments-store.ts +237 -0
  159. package/src/documents/document-store.ts +202 -0
  160. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  161. package/src/heartbeat/heartbeat-service.ts +1 -0
  162. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  163. package/src/home/feed-types.ts +6 -1
  164. package/src/home/home-content-refresh.ts +52 -0
  165. package/src/home/home-greeting-cache.ts +69 -0
  166. package/src/home/home-greeting.ts +94 -0
  167. package/src/home/suggested-prompts.ts +177 -9
  168. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  169. package/src/memory/__tests__/memory-retrospective-job.test.ts +320 -6
  170. package/src/memory/conversation-crud.ts +133 -43
  171. package/src/memory/db-init.ts +16 -0
  172. package/src/memory/delivery-crud.ts +41 -0
  173. package/src/memory/delivery-status.ts +141 -15
  174. package/src/memory/external-conversation-store.ts +32 -1
  175. package/src/memory/jobs-worker.ts +21 -1
  176. package/src/memory/memory-retrospective-constants.ts +28 -0
  177. package/src/memory/memory-retrospective-enqueue.ts +3 -2
  178. package/src/memory/memory-retrospective-job.ts +408 -18
  179. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  180. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  181. package/src/memory/migrations/100-core-tables.ts +1 -0
  182. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  183. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  184. package/src/memory/migrations/253-document-comments.ts +47 -0
  185. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  186. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  187. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  188. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  189. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  190. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  191. package/src/memory/migrations/index.ts +17 -0
  192. package/src/memory/migrations/registry.ts +25 -0
  193. package/src/memory/onboarding-events-store.ts +7 -0
  194. package/src/memory/schema/calls.ts +1 -0
  195. package/src/memory/schema/conversations.ts +3 -0
  196. package/src/memory/schema/infrastructure.ts +1 -0
  197. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  198. package/src/memory/v2/__tests__/injection.test.ts +31 -14
  199. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  200. package/src/memory/v2/__tests__/router.test.ts +489 -1
  201. package/src/memory/v2/consolidation-job.ts +14 -0
  202. package/src/memory/v2/injection-events.ts +101 -0
  203. package/src/memory/v2/injection.ts +21 -10
  204. package/src/memory/v2/page-index.ts +209 -7
  205. package/src/memory/v2/page-store.ts +18 -0
  206. package/src/memory/v2/router.ts +209 -55
  207. package/src/messaging/providers/index.ts +7 -1
  208. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  209. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  210. package/src/messaging/providers/slack/adapter.ts +178 -25
  211. package/src/messaging/providers/slack/api.test.ts +54 -0
  212. package/src/messaging/providers/slack/api.ts +119 -3
  213. package/src/messaging/providers/slack/client.ts +12 -0
  214. package/src/messaging/providers/slack/deep-link.ts +20 -1
  215. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  216. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  217. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  218. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  219. package/src/messaging/providers/slack/send.test.ts +77 -0
  220. package/src/messaging/providers/slack/send.ts +8 -2
  221. package/src/messaging/providers/slack/types.ts +14 -0
  222. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
  223. package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
  224. package/src/notifications/conversation-seed-composer.ts +14 -2
  225. package/src/notifications/deferred-emit.ts +135 -0
  226. package/src/notifications/emit-signal.ts +9 -1
  227. package/src/notifications/home-feed-side-effect.ts +60 -30
  228. package/src/oauth/connect-orchestrator.ts +3 -0
  229. package/src/oauth/credential-token-resolver.ts +2 -0
  230. package/src/oauth/manual-token-connection.ts +19 -0
  231. package/src/oauth/oauth-store.ts +12 -0
  232. package/src/oauth/seed-providers.ts +22 -0
  233. package/src/permissions/prompter.ts +5 -2
  234. package/src/permissions/secret-prompter.ts +4 -1
  235. package/src/plugins/defaults/injectors.ts +82 -9
  236. package/src/prompts/__tests__/system-prompt.test.ts +46 -2
  237. package/src/prompts/normalize-onboarding.ts +40 -0
  238. package/src/prompts/sections.ts +32 -14
  239. package/src/prompts/system-prompt.ts +105 -68
  240. package/src/prompts/template-detection.ts +37 -0
  241. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  242. package/src/prompts/templates/BOOTSTRAP.md +8 -0
  243. package/src/prompts/templates/VOICE.md +3 -0
  244. package/src/prompts/templates/system-sections.ts +53 -3
  245. package/src/providers/anthropic/client.ts +132 -5
  246. package/src/providers/fireworks/client.ts +20 -2
  247. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  248. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  249. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  250. package/src/providers/inference/adapter-factory.ts +15 -1
  251. package/src/providers/inference/auth.ts +3 -3
  252. package/src/providers/inference/codex-token-refresh.ts +128 -0
  253. package/src/providers/inference/resolve-auth.ts +49 -6
  254. package/src/providers/model-catalog.ts +48 -1
  255. package/src/providers/openai/chat-completions-provider.ts +57 -20
  256. package/src/providers/openai/responses-provider.ts +9 -3
  257. package/src/providers/openrouter/client.ts +5 -1
  258. package/src/providers/types.ts +25 -0
  259. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  260. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  261. package/src/runtime/agent-wake.ts +151 -56
  262. package/src/runtime/auth/route-policy.ts +7 -3
  263. package/src/runtime/background-job-runner.ts +26 -0
  264. package/src/runtime/channel-reply-delivery.ts +182 -47
  265. package/src/runtime/channel-retry-sweep.ts +141 -16
  266. package/src/runtime/http-types.ts +7 -4
  267. package/src/runtime/pending-interactions.ts +51 -8
  268. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  269. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +55 -1
  270. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  271. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  272. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  273. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  274. package/src/runtime/routes/approval-routes.ts +4 -1
  275. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  276. package/src/runtime/routes/content-source-routes.ts +78 -0
  277. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  278. package/src/runtime/routes/conversation-query-routes.ts +60 -1
  279. package/src/runtime/routes/conversation-routes.ts +281 -76
  280. package/src/runtime/routes/document-comments-routes.ts +287 -0
  281. package/src/runtime/routes/documents-routes.ts +33 -0
  282. package/src/runtime/routes/home-feed-routes.ts +6 -3
  283. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  284. package/src/runtime/routes/host-browser-routes.ts +8 -1
  285. package/src/runtime/routes/identity-routes.ts +21 -0
  286. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  287. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  288. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  289. package/src/runtime/routes/index.ts +12 -4
  290. package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
  291. package/src/runtime/routes/integrations/a2a.ts +60 -1
  292. package/src/runtime/routes/log-export-routes.ts +39 -0
  293. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  294. package/src/runtime/routes/notification-routes.ts +19 -2
  295. package/src/runtime/routes/question-routes.ts +4 -1
  296. package/src/runtime/routes/sanity-routes.ts +159 -0
  297. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  298. package/src/runtime/services/conversation-serializer.ts +30 -4
  299. package/src/schedule/integration-status.ts +3 -1
  300. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  301. package/src/security/oauth2-device-code.ts +307 -0
  302. package/src/security/oauth2.ts +26 -9
  303. package/src/security/secure-keys.ts +5 -0
  304. package/src/skills/catalog-install.ts +6 -2
  305. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  306. package/src/tools/browser/browser-execution.ts +93 -0
  307. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  308. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  309. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  310. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  311. package/src/tools/browser/cdp-client/factory.ts +87 -3
  312. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  313. package/src/tools/browser/cdp-client/types.ts +36 -0
  314. package/src/tools/browser/pinned-tabs.ts +90 -0
  315. package/src/tools/document/document-comment-tool.test.ts +379 -0
  316. package/src/tools/document/document-comment-tool.ts +156 -0
  317. package/src/tools/document/document-tool.ts +128 -2
  318. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  319. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  320. package/src/tools/network/domain-normalize.ts +17 -0
  321. package/src/tools/network/web-fetch.ts +213 -64
  322. package/src/tools/network/web-search.ts +191 -66
  323. package/src/tools/terminal/safe-env.ts +3 -2
  324. package/src/tools/tool-approval-handler.ts +19 -12
  325. package/src/tools/types.ts +4 -0
  326. package/src/tools/ui-surface/definitions.ts +3 -1
  327. package/src/types/onboarding-context.ts +4 -0
  328. package/src/util/__tests__/favicon.test.ts +84 -0
  329. package/src/util/favicon.ts +40 -0
  330. package/src/util/platform.ts +0 -5
  331. package/src/workspace/git-service.ts +75 -4
  332. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  333. package/src/workspace/migrations/registry.ts +2 -0
  334. package/src/config/bundled-skills/document/SKILL.md +0 -54
  335. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  336. package/src/daemon/seed-files.ts +0 -18
  337. package/src/runtime/routes/interface-routes.ts +0 -43
  338. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  339. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  340. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  341. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  342. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Unit tests for content-source-routes.ts.
3
+ *
4
+ * Drives the handler function directly (bypassing the router) and mocks
5
+ * out node:fs writes so no real I/O occurs.
6
+ */
7
+
8
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Module mocks — must appear before any imports of the module under test
12
+ // ---------------------------------------------------------------------------
13
+
14
+ mock.module("../../../util/logger.js", () => ({
15
+ getLogger: () =>
16
+ new Proxy({} as Record<string, unknown>, {
17
+ get: () => () => {},
18
+ }),
19
+ }));
20
+
21
+ const FAKE_WORKSPACE = "/tmp/content-source-routes-test-workspace";
22
+
23
+ mock.module("../../../util/platform.js", () => ({
24
+ getWorkspaceDir: () => FAKE_WORKSPACE,
25
+ }));
26
+
27
+ const writtenFiles = new Map<string, string>();
28
+
29
+ mock.module("node:fs", () => ({
30
+ mkdirSync: () => {},
31
+ writeFileSync: (path: string, content: string) => {
32
+ writtenFiles.set(path, content);
33
+ },
34
+ }));
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Imports after mocks
38
+ // ---------------------------------------------------------------------------
39
+
40
+ import { ROUTES } from "../content-source-routes.js";
41
+ import type { RouteDefinition, RouteHandlerArgs } from "../types.js";
42
+
43
+ afterAll(() => {
44
+ mock.restore();
45
+ });
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Helpers
49
+ // ---------------------------------------------------------------------------
50
+
51
+ function findHandler(operationId: string): RouteDefinition["handler"] {
52
+ const route = ROUTES.find((r) => r.operationId === operationId);
53
+ if (!route) throw new Error(`Route ${operationId} not found`);
54
+ return route.handler;
55
+ }
56
+
57
+ function makeArgs(body?: Record<string, unknown>): RouteHandlerArgs {
58
+ return { body };
59
+ }
60
+
61
+ const handler = findHandler("content_source_set");
62
+
63
+ beforeEach(() => {
64
+ writtenFiles.clear();
65
+ });
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // URL validation
69
+ // ---------------------------------------------------------------------------
70
+
71
+ describe("content_source_set — URL validation", () => {
72
+ test("https URL is accepted and sidecar written", () => {
73
+ const result = handler(makeArgs({ url: "https://myblog.com/posts" }));
74
+ expect(result).toEqual({ ok: true });
75
+
76
+ const expectedPath = `${FAKE_WORKSPACE}/data/content-source.json`;
77
+ expect(writtenFiles.has(expectedPath)).toBe(true);
78
+ const written = JSON.parse(writtenFiles.get(expectedPath)!);
79
+ expect(written.url).toBe("https://myblog.com/posts");
80
+ });
81
+
82
+ test("http URL is accepted and sidecar written", () => {
83
+ const result = handler(makeArgs({ url: "http://intranet.example.com" }));
84
+ expect(result).toEqual({ ok: true });
85
+ });
86
+
87
+ test("URL with leading/trailing whitespace is trimmed", () => {
88
+ const result = handler(makeArgs({ url: " https://blog.example.com " }));
89
+ expect(result).toEqual({ ok: true });
90
+
91
+ const expectedPath = `${FAKE_WORKSPACE}/data/content-source.json`;
92
+ const written = JSON.parse(writtenFiles.get(expectedPath)!);
93
+ expect(written.url).toBe("https://blog.example.com/");
94
+ });
95
+
96
+ test("bare hostname without protocol returns invalid_url", () => {
97
+ const result = handler(makeArgs({ url: "myblog.com" }));
98
+ expect(result).toEqual({ ok: false, error: "invalid_url" });
99
+ expect(writtenFiles.size).toBe(0);
100
+ });
101
+
102
+ test("ftp:// URL is rejected", () => {
103
+ const result = handler(makeArgs({ url: "ftp://files.example.com" }));
104
+ expect(result).toEqual({ ok: false, error: "invalid_url" });
105
+ expect(writtenFiles.size).toBe(0);
106
+ });
107
+
108
+ test("empty string returns invalid_url", () => {
109
+ const result = handler(makeArgs({ url: "" }));
110
+ expect(result).toEqual({ ok: false, error: "invalid_url" });
111
+ expect(writtenFiles.size).toBe(0);
112
+ });
113
+
114
+ test("whitespace-only string returns invalid_url", () => {
115
+ const result = handler(makeArgs({ url: " " }));
116
+ expect(result).toEqual({ ok: false, error: "invalid_url" });
117
+ expect(writtenFiles.size).toBe(0);
118
+ });
119
+
120
+ test("javascript: URL is rejected", () => {
121
+ const result = handler(makeArgs({ url: "javascript:alert(1)" }));
122
+ expect(result).toEqual({ ok: false, error: "invalid_url" });
123
+ expect(writtenFiles.size).toBe(0);
124
+ });
125
+
126
+ test("missing url field returns invalid_url", () => {
127
+ const result = handler(makeArgs({}));
128
+ expect(result).toEqual({ ok: false, error: "invalid_url" });
129
+ });
130
+ });
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Sidecar content verification
134
+ // ---------------------------------------------------------------------------
135
+
136
+ describe("content_source_set — sidecar contents", () => {
137
+ test("writes url to data/content-source.json", () => {
138
+ handler(makeArgs({ url: "https://example.com/blog" }));
139
+
140
+ const expectedPath = `${FAKE_WORKSPACE}/data/content-source.json`;
141
+ expect(writtenFiles.has(expectedPath)).toBe(true);
142
+
143
+ const written = JSON.parse(writtenFiles.get(expectedPath)!);
144
+ expect(Object.keys(written)).toEqual(["url"]);
145
+ });
146
+
147
+ test("no sidecar written on invalid URL", () => {
148
+ handler(makeArgs({ url: "not-a-url" }));
149
+ expect(writtenFiles.size).toBe(0);
150
+ });
151
+ });
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // Policy key verification
155
+ // ---------------------------------------------------------------------------
156
+
157
+ describe("route policy key", () => {
158
+ test("content_source_set uses policyKey: secrets", () => {
159
+ const route = ROUTES.find((r) => r.operationId === "content_source_set");
160
+ expect(route?.policyKey).toBe("secrets");
161
+ });
162
+ });
@@ -141,7 +141,11 @@ function seedConversationAndMessage(args: {
141
141
  .run();
142
142
  }
143
143
 
144
- function seedRequestLog(messageId: string, id: string): void {
144
+ function seedRequestLog(
145
+ messageId: string,
146
+ id: string,
147
+ options: { agentLoopExitReason?: string | null } = {},
148
+ ): void {
145
149
  getDb()
146
150
  .insert(llmRequestLogs)
147
151
  .values({
@@ -154,6 +158,9 @@ function seedRequestLog(messageId: string, id: string): void {
154
158
  choices: [{ message: { content: "hi" } }],
155
159
  }),
156
160
  createdAt: 1_700_000_000_000,
161
+ ...(options.agentLoopExitReason != null
162
+ ? { agentLoopExitReason: options.agentLoopExitReason }
163
+ : {}),
157
164
  })
158
165
  .run();
159
166
  }
@@ -317,6 +324,53 @@ describe("GET /v1/messages/:id/llm-context — conversationTotalEstimatedCostUsd
317
324
  });
318
325
  });
319
326
 
327
+ describe("GET /v1/messages/:id/llm-context — agentLoopExitReason", () => {
328
+ beforeEach(() => {
329
+ clearTables();
330
+ });
331
+
332
+ test("surfaces the stamped agent_loop_exit_reason on the terminal log", async () => {
333
+ const messageId = "msg-with-exit";
334
+ seedConversationAndMessage({
335
+ conversationId: "conv-1",
336
+ messageId,
337
+ source: "user",
338
+ conversationType: "standard",
339
+ });
340
+ // Two logs in the same turn — only the terminal one is stamped.
341
+ seedRequestLog(messageId, "log-non-terminal");
342
+ seedRequestLog(messageId, "log-terminal", {
343
+ agentLoopExitReason: "no_tool_calls",
344
+ });
345
+
346
+ const body = (await dispatchLlmContext(messageId)) as {
347
+ logs: Array<{ id: string; agentLoopExitReason: string | null }>;
348
+ };
349
+
350
+ const byId = new Map(body.logs.map((l) => [l.id, l.agentLoopExitReason]));
351
+ expect(byId.get("log-non-terminal")).toBeNull();
352
+ expect(byId.get("log-terminal")).toBe("no_tool_calls");
353
+ });
354
+
355
+ test("returns null when no log in the turn has been stamped", async () => {
356
+ const messageId = "msg-no-exit";
357
+ seedConversationAndMessage({
358
+ conversationId: "conv-1",
359
+ messageId,
360
+ source: "user",
361
+ conversationType: "standard",
362
+ });
363
+ seedRequestLog(messageId, "log-unstamped");
364
+
365
+ const body = (await dispatchLlmContext(messageId)) as {
366
+ logs: Array<{ id: string; agentLoopExitReason: string | null }>;
367
+ };
368
+
369
+ expect(body.logs).toHaveLength(1);
370
+ expect(body.logs[0]!.agentLoopExitReason).toBeNull();
371
+ });
372
+ });
373
+
320
374
  describe("PUT /v1/config/llm/profiles/:name", () => {
321
375
  beforeEach(() => {
322
376
  savedRawConfig = null;
@@ -145,3 +145,17 @@ describe("memory_v2_list_concept_pages handler", () => {
145
145
  expect(result.pages.map((p) => p.slug)).toEqual(["valid-page"]);
146
146
  });
147
147
  });
148
+
149
+ describe("memory_v2_ema_scores route registration", () => {
150
+ test("route is registered with the expected operationId and POST endpoint", () => {
151
+ const route = ROUTES.find((r) => r.operationId === "memory_v2_ema_scores");
152
+ expect(route).toBeDefined();
153
+ expect(route!.method).toBe("POST");
154
+ expect(route!.endpoint).toBe("memory/v2/ema-scores");
155
+ expect(route!.tags).toContain("memory");
156
+ // Schema rejects unknown keys so accidental request-body fields fail
157
+ // loudly during route adapter parsing rather than silently propagating.
158
+ expect(() => route!.requestBody!.parse({ extra: true })).toThrow();
159
+ expect(() => route!.requestBody!.parse({})).not.toThrow();
160
+ });
161
+ });
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Tests for the `memory_v2_simulate_router` route handler.
3
+ *
4
+ * The route is a read-only playground for previewing router config knob
5
+ * changes (`tier1_size`, `tier2_size`, `batch_size`) against the live page
6
+ * index. Tests assert:
7
+ * 1. The handler returns selected slugs with `sourceBySlug` populated.
8
+ * 2. Overrides are reflected in the response's `effectiveConfig`.
9
+ * 3. The handler never calls `recordInjectionEvents` — the simulate path
10
+ * must not touch the EMA event log.
11
+ *
12
+ * Workspace lives in a `mkdtemp` directory per test; `~/.vellum/` is never
13
+ * touched. The provider and DB are stubbed so no network or SQLite I/O
14
+ * fires.
15
+ */
16
+
17
+ import { mkdtempSync, rmSync } from "node:fs";
18
+ import { tmpdir } from "node:os";
19
+ import { join } from "node:path";
20
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
21
+
22
+ import type {
23
+ Message,
24
+ Provider,
25
+ ProviderResponse,
26
+ SendMessageOptions,
27
+ ToolDefinition,
28
+ ToolUseContent,
29
+ } from "../../../providers/types.js";
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Mocks (installed before the route module is imported)
33
+ // ---------------------------------------------------------------------------
34
+
35
+ mock.module("../../../util/logger.js", () => ({
36
+ getLogger: () =>
37
+ new Proxy({} as Record<string, unknown>, {
38
+ get: () => () => {},
39
+ }),
40
+ }));
41
+
42
+ // Skill store: empty by default so the page index only contains test pages.
43
+ mock.module("../../../memory/v2/skill-store.js", () => ({
44
+ SKILL_SLUG_PREFIX: "skills/",
45
+ listSkillEntries: () => [],
46
+ seedV2SkillEntries: async () => undefined,
47
+ }));
48
+
49
+ // NOW.md loader: return a fixed string. The route reads NOW from the
50
+ // workspace at call time; stubbing keeps the test independent of disk state.
51
+ mock.module("../../../memory/v2/now-text.js", () => ({
52
+ loadNowText: async () => "2026-05-22 14:00 PT",
53
+ }));
54
+
55
+ // Injection-events module. `recordInjectionEvents` must never be called by
56
+ // the simulate path — record any call so the assertion catches a regression.
57
+ // `computeInjectionScores` is called twice per simulate (once inside runRouter
58
+ // for tier 2, once in the handler for the response payload); always returns
59
+ // zero scores since the test workspace has no event history.
60
+ const recordCalls: Array<{ slugs: readonly string[]; at: number }> = [];
61
+ mock.module("../../../memory/v2/injection-events.js", () => ({
62
+ recordInjectionEvents: (
63
+ _db: unknown,
64
+ slugs: readonly string[],
65
+ at: number,
66
+ ) => {
67
+ recordCalls.push({ slugs, at });
68
+ },
69
+ computeInjectionScores: (
70
+ _db: unknown,
71
+ slugs: readonly string[],
72
+ _now: number,
73
+ ): Map<string, number> => new Map(slugs.map((s) => [s, 0])),
74
+ }));
75
+
76
+ // Database handle: the simulate route only passes this through to
77
+ // `runRouter` and `computeInjectionScores`. Both are stubbed above, so a
78
+ // sentinel object is sufficient.
79
+ mock.module("../../../memory/db-connection.js", () => ({
80
+ getDb: () => ({ __stub: true }),
81
+ }));
82
+
83
+ // Config loader. The simulate route reads `memory.v2.enabled` (must be
84
+ // true) and the full `memory.v2.router` block (overrides merged on top).
85
+ const liveRouterConfig = {
86
+ enabled: true,
87
+ max_page_ids: 25,
88
+ router_prompt_path: null,
89
+ batch_size: null,
90
+ tier1_size: null,
91
+ tier2_size: null,
92
+ };
93
+ const mockConfigValue = {
94
+ memory: {
95
+ v2: {
96
+ enabled: true,
97
+ router: liveRouterConfig,
98
+ },
99
+ },
100
+ };
101
+ mock.module("../../../config/loader.js", () => ({
102
+ loadConfig: () => mockConfigValue,
103
+ getConfig: () => mockConfigValue,
104
+ getConfigReadOnly: () => mockConfigValue,
105
+ invalidateConfigCache: () => {},
106
+ API_KEY_PROVIDERS: [],
107
+ }));
108
+
109
+ // Provider stub. Default returns [1, 2] (selects first two pages).
110
+ let providerStub: Provider | null = null;
111
+ interface ProviderCall {
112
+ messages: Message[];
113
+ tools: ToolDefinition[] | undefined;
114
+ systemPrompt: string | undefined;
115
+ options: SendMessageOptions | undefined;
116
+ }
117
+ const providerCalls: ProviderCall[] = [];
118
+ mock.module("../../../providers/provider-send-message.js", () => ({
119
+ getConfiguredProvider: async () => providerStub,
120
+ extractToolUse: (response: ProviderResponse) =>
121
+ response.content.find((b): b is ToolUseContent => b.type === "tool_use"),
122
+ }));
123
+
124
+ // Platform helpers. `getWorkspaceDir` must return the per-test tmp dir so
125
+ // the route's page index points at the test workspace.
126
+ let workspaceDir = "";
127
+ mock.module("../../../util/platform.js", () => ({
128
+ getWorkspaceDir: () => workspaceDir,
129
+ }));
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // Import under test (after all mocks above)
133
+ // ---------------------------------------------------------------------------
134
+
135
+ const { handleSimulateRouter } = await import("../memory-v2-routes.js");
136
+ const { writePage } = await import("../../../memory/v2/page-store.js");
137
+ const { invalidatePageIndex } =
138
+ await import("../../../memory/v2/page-index.js");
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Helpers
142
+ // ---------------------------------------------------------------------------
143
+
144
+ function makeProvider(pageIds: number[]): Provider {
145
+ return {
146
+ name: "stub",
147
+ sendMessage: async (messages, tools, systemPrompt, options) => {
148
+ providerCalls.push({ messages, tools, systemPrompt, options });
149
+ return {
150
+ model: "stub-model",
151
+ stopReason: "tool_use",
152
+ usage: { inputTokens: 0, outputTokens: 0 },
153
+ content: [
154
+ {
155
+ type: "tool_use",
156
+ id: "tu-1",
157
+ name: "select_pages_to_inject",
158
+ input: { page_ids: pageIds },
159
+ },
160
+ ],
161
+ };
162
+ },
163
+ };
164
+ }
165
+
166
+ function makePage(slug: string, summary: string) {
167
+ return {
168
+ slug,
169
+ frontmatter: {
170
+ edges: [],
171
+ ref_files: [],
172
+ ref_urls: [],
173
+ summary,
174
+ },
175
+ body: "",
176
+ };
177
+ }
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // Per-test setup / teardown
181
+ // ---------------------------------------------------------------------------
182
+
183
+ beforeEach(() => {
184
+ workspaceDir = mkdtempSync(join(tmpdir(), "memory-v2-simulate-test-"));
185
+ recordCalls.length = 0;
186
+ providerCalls.length = 0;
187
+ providerStub = null;
188
+ // Reset live router config to defaults between tests.
189
+ liveRouterConfig.batch_size = null;
190
+ liveRouterConfig.tier1_size = null;
191
+ liveRouterConfig.tier2_size = null;
192
+ liveRouterConfig.max_page_ids = 25;
193
+ invalidatePageIndex();
194
+ });
195
+
196
+ afterEach(() => {
197
+ invalidatePageIndex();
198
+ rmSync(workspaceDir, { recursive: true, force: true });
199
+ });
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // Tests
203
+ // ---------------------------------------------------------------------------
204
+
205
+ describe("handleSimulateRouter", () => {
206
+ test("returns selectedSlugs + sourceBySlug populated for each pick", async () => {
207
+ await writePage(workspaceDir, makePage("alice", "A"));
208
+ await writePage(workspaceDir, makePage("bob", "B"));
209
+ await writePage(workspaceDir, makePage("carol", "C"));
210
+ providerStub = makeProvider([3, 1]);
211
+
212
+ const result = await handleSimulateRouter({
213
+ body: { query: "what's relevant?" },
214
+ });
215
+
216
+ expect(result.failureReason).toBeNull();
217
+ expect(result.selectedSlugs).toEqual(["carol", "alice"]);
218
+ expect(result.sourceBySlug["carol"]).toBe("tier3:0");
219
+ expect(result.sourceBySlug["alice"]).toBe("tier3:0");
220
+ expect(result.scores["carol"]).toBe(0);
221
+ expect(result.scores["alice"]).toBe(0);
222
+ expect(result.totalCandidatePages).toBe(3);
223
+ });
224
+
225
+ test("propagates overrides into effectiveConfig and reports them in overrides", async () => {
226
+ await writePage(workspaceDir, makePage("alice", "A"));
227
+ providerStub = makeProvider([1]);
228
+
229
+ const result = await handleSimulateRouter({
230
+ body: {
231
+ query: "test",
232
+ configOverrides: {
233
+ tier1_size: 50,
234
+ batch_size: 25,
235
+ },
236
+ },
237
+ });
238
+
239
+ expect(result.effectiveConfig.tier1_size).toBe(50);
240
+ expect(result.effectiveConfig.tier2_size).toBeNull(); // not overridden, inherits live
241
+ expect(result.effectiveConfig.batch_size).toBe(25);
242
+ expect(result.effectiveConfig.max_page_ids).toBe(25);
243
+ expect(result.overrides).toEqual({ tier1_size: 50, batch_size: 25 });
244
+ });
245
+
246
+ test("never writes to the EMA event log (recordInjectionEvents not called)", async () => {
247
+ await writePage(workspaceDir, makePage("alice", "A"));
248
+ await writePage(workspaceDir, makePage("bob", "B"));
249
+ providerStub = makeProvider([1, 2]);
250
+
251
+ await handleSimulateRouter({
252
+ body: { query: "should not record" },
253
+ });
254
+
255
+ expect(recordCalls).toEqual([]);
256
+ });
257
+
258
+ test("rejects an empty query at the schema layer", async () => {
259
+ await expect(
260
+ handleSimulateRouter({ body: { query: "" } }),
261
+ ).rejects.toThrow();
262
+ });
263
+
264
+ test("rejects negative tier size at the schema layer", async () => {
265
+ await expect(
266
+ handleSimulateRouter({
267
+ body: { query: "test", configOverrides: { tier1_size: -5 } },
268
+ }),
269
+ ).rejects.toThrow();
270
+ });
271
+ });