@vellumai/assistant 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (503) hide show
  1. package/ARCHITECTURE.md +11 -12
  2. package/docker-entrypoint.sh +13 -2
  3. package/docker-init-apt-root.sh +79 -6
  4. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  5. package/openapi.yaml +945 -36
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
  8. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  9. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +272 -0
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  14. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  15. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  16. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  17. package/src/__tests__/compaction-events.test.ts +1 -1
  18. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  19. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  20. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  22. package/src/__tests__/config-watcher.test.ts +1 -1
  23. package/src/__tests__/context-token-estimator.test.ts +112 -57
  24. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  27. package/src/__tests__/conversation-agent-loop.test.ts +77 -3
  28. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  29. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  30. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  32. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  33. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  34. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  35. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  36. package/src/__tests__/conversation-pairing.test.ts +2 -2
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  39. package/src/__tests__/conversation-queue.test.ts +1 -1
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
  41. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  42. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  43. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  44. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  45. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  46. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  47. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  48. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  49. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  50. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  51. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  52. package/src/__tests__/date-context.test.ts +45 -0
  53. package/src/__tests__/dm-backfill.test.ts +64 -0
  54. package/src/__tests__/dm-persistence.test.ts +33 -0
  55. package/src/__tests__/document-find-replace.test.ts +501 -0
  56. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  57. package/src/__tests__/first-greeting.test.ts +23 -2
  58. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  59. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  60. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  61. package/src/__tests__/heartbeat-service.test.ts +24 -164
  62. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  63. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  64. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  66. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  67. package/src/__tests__/host-file-proxy.test.ts +8 -1
  68. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  69. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  70. package/src/__tests__/identity-routes.test.ts +57 -0
  71. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  72. package/src/__tests__/injector-background-turn.test.ts +153 -0
  73. package/src/__tests__/injector-chain.test.ts +7 -0
  74. package/src/__tests__/injector-document-comments.test.ts +378 -0
  75. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  76. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  77. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  78. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  79. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  80. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  81. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  82. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  83. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  84. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  85. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  86. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  87. package/src/__tests__/llm-resolver.test.ts +340 -3
  88. package/src/__tests__/log-export-routes.test.ts +99 -2
  89. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  90. package/src/__tests__/message-queue-steer.test.ts +114 -0
  91. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  92. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  93. package/src/__tests__/notification-deep-link.test.ts +15 -0
  94. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  95. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  96. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  97. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  98. package/src/__tests__/openai-provider.test.ts +323 -3
  99. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  100. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  101. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  102. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  103. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  104. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  105. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  106. package/src/__tests__/platform.test.ts +0 -3
  107. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  108. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  109. package/src/__tests__/plugin-types.test.ts +2 -2
  110. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  111. package/src/__tests__/process-message-display-content.test.ts +21 -16
  112. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  113. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  115. package/src/__tests__/server-history-render.test.ts +83 -4
  116. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  117. package/src/__tests__/system-prompt.test.ts +57 -101
  118. package/src/__tests__/terminal-tools.test.ts +11 -1
  119. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  120. package/src/__tests__/thread-backfill.test.ts +370 -22
  121. package/src/__tests__/tool-executor.test.ts +90 -1
  122. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  123. package/src/__tests__/twilio-routes.test.ts +1 -1
  124. package/src/__tests__/web-fetch.test.ts +2 -2
  125. package/src/__tests__/workspace-git-service.test.ts +88 -5
  126. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  127. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  128. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  129. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  130. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  131. package/src/a2a/__tests__/task-store.test.ts +246 -0
  132. package/src/a2a/agent-card.ts +58 -0
  133. package/src/a2a/feature-gate.ts +8 -0
  134. package/src/a2a/protocol-constants.ts +21 -0
  135. package/src/a2a/protocol-errors.ts +50 -0
  136. package/src/a2a/protocol-types.ts +162 -0
  137. package/src/a2a/task-store.ts +168 -0
  138. package/src/agent/attachments.ts +1 -0
  139. package/src/agent/loop.ts +208 -22
  140. package/src/background-wake/next-wake.test.ts +289 -0
  141. package/src/background-wake/next-wake.ts +172 -0
  142. package/src/browser/operations.ts +15 -0
  143. package/src/channels/config.ts +9 -0
  144. package/src/channels/types.ts +14 -0
  145. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  146. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  147. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  148. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  149. package/src/cli/commands/conversations.ts +128 -1
  150. package/src/cli/commands/inference-providers.ts +147 -1
  151. package/src/cli/commands/memory-v2.ts +308 -0
  152. package/src/cli/commands/notifications.ts +89 -37
  153. package/src/cli/commands/plugins.ts +67 -0
  154. package/src/cli/commands/schedules.ts +297 -5
  155. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  156. package/src/cli/lib/install-from-github.ts +8 -9
  157. package/src/cli/lib/search-plugins.ts +163 -0
  158. package/src/cli/program.ts +14 -0
  159. package/src/cli/utils/conversation-id.ts +17 -5
  160. package/src/config/assistant-feature-flags.ts +24 -54
  161. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  162. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  163. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  164. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  165. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  166. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  167. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  168. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  169. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  170. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  171. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  172. package/src/config/bundled-tool-registry.ts +22 -12
  173. package/src/config/call-site-defaults.ts +124 -0
  174. package/src/config/feature-flag-registry.json +111 -23
  175. package/src/config/llm-resolver.ts +66 -1
  176. package/src/config/schema.ts +2 -0
  177. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
  178. package/src/config/schemas/call-site-catalog.ts +21 -0
  179. package/src/config/schemas/channels.ts +9 -0
  180. package/src/config/schemas/conversations.ts +10 -0
  181. package/src/config/schemas/heartbeat.ts +14 -0
  182. package/src/config/schemas/llm.ts +4 -3
  183. package/src/config/schemas/memory-retrospective.ts +1 -1
  184. package/src/config/schemas/memory-v2.ts +51 -4
  185. package/src/config/schemas/memory.ts +3 -1
  186. package/src/config/seed-inference-profiles.ts +99 -29
  187. package/src/context/compactor.ts +80 -13
  188. package/src/context/token-estimator.ts +72 -31
  189. package/src/context/window-manager.ts +25 -0
  190. package/src/credential-health/credential-health-service.ts +34 -19
  191. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  192. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  193. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  194. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  195. package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
  196. package/src/daemon/conversation-agent-loop.ts +252 -56
  197. package/src/daemon/conversation-lifecycle.ts +142 -116
  198. package/src/daemon/conversation-messaging.ts +3 -0
  199. package/src/daemon/conversation-process.ts +273 -0
  200. package/src/daemon/conversation-queue-manager.ts +14 -0
  201. package/src/daemon/conversation-runtime-assembly.ts +144 -75
  202. package/src/daemon/conversation-slash.ts +37 -5
  203. package/src/daemon/conversation-surfaces.ts +45 -2
  204. package/src/daemon/conversation-tool-setup.ts +7 -0
  205. package/src/daemon/conversation.ts +42 -12
  206. package/src/daemon/date-context.ts +40 -0
  207. package/src/daemon/first-greeting.ts +10 -0
  208. package/src/daemon/guardian-action-generators.ts +1 -125
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  212. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  213. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  214. package/src/daemon/handlers/config-a2a.ts +449 -0
  215. package/src/daemon/handlers/config-model.test.ts +1 -0
  216. package/src/daemon/handlers/conversations.ts +80 -0
  217. package/src/daemon/handlers/shared.ts +92 -29
  218. package/src/daemon/host-app-control-proxy.ts +69 -18
  219. package/src/daemon/host-bash-proxy.ts +1 -1
  220. package/src/daemon/host-cu-proxy.ts +1 -1
  221. package/src/daemon/host-file-proxy.ts +1 -1
  222. package/src/daemon/host-proxy-preactivation.ts +85 -18
  223. package/src/daemon/host-transfer-proxy.ts +1 -1
  224. package/src/daemon/lifecycle.ts +67 -65
  225. package/src/daemon/memory-v2-startup.ts +49 -13
  226. package/src/daemon/message-protocol.ts +4 -0
  227. package/src/daemon/message-types/conversations.ts +8 -0
  228. package/src/daemon/message-types/document-comments.ts +50 -0
  229. package/src/daemon/message-types/messages.ts +68 -1
  230. package/src/daemon/message-types/notifications.ts +21 -0
  231. package/src/daemon/message-types/surfaces.ts +3 -1
  232. package/src/daemon/message-types/web-activity.ts +57 -0
  233. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  234. package/src/daemon/pkb-reminder-builder.ts +4 -19
  235. package/src/daemon/plugin-source-watcher.ts +135 -3
  236. package/src/daemon/process-message.ts +72 -12
  237. package/src/daemon/query-complexity-router.ts +75 -0
  238. package/src/daemon/skill-memory-refresh.ts +5 -1
  239. package/src/daemon/trust-context.ts +6 -0
  240. package/src/daemon/wake-target-adapter.ts +2 -0
  241. package/src/documents/document-comments-store.test.ts +338 -0
  242. package/src/documents/document-comments-store.ts +237 -0
  243. package/src/documents/document-store.ts +202 -0
  244. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  245. package/src/export/transcript-formatter.ts +54 -20
  246. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
  247. package/src/heartbeat/heartbeat-service.ts +35 -191
  248. package/src/home/__tests__/feed-types.test.ts +40 -0
  249. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  250. package/src/home/feed-types.ts +20 -3
  251. package/src/home/home-content-refresh.ts +52 -0
  252. package/src/home/home-greeting-cache.ts +69 -0
  253. package/src/home/home-greeting.ts +94 -0
  254. package/src/home/suggested-prompts.ts +177 -9
  255. package/src/ipc/cli-client.ts +147 -45
  256. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  257. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  258. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  259. package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
  260. package/src/memory/conversation-crud.ts +133 -43
  261. package/src/memory/conversation-queries.ts +87 -1
  262. package/src/memory/conversation-title-service.ts +26 -4
  263. package/src/memory/db-init.ts +22 -0
  264. package/src/memory/delivery-crud.ts +41 -0
  265. package/src/memory/delivery-status.ts +141 -15
  266. package/src/memory/external-conversation-store.ts +32 -1
  267. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  268. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  269. package/src/memory/graph/tools.ts +6 -37
  270. package/src/memory/invite-store.ts +53 -0
  271. package/src/memory/jobs-worker.ts +21 -1
  272. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  273. package/src/memory/llm-request-log-store.ts +92 -1
  274. package/src/memory/memory-retrospective-constants.ts +28 -0
  275. package/src/memory/memory-retrospective-enqueue.ts +4 -22
  276. package/src/memory/memory-retrospective-job.ts +438 -21
  277. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  278. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  279. package/src/memory/migrations/100-core-tables.ts +1 -0
  280. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  281. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  282. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  283. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  284. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  285. package/src/memory/migrations/253-document-comments.ts +47 -0
  286. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  287. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  288. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  289. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  290. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  291. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  292. package/src/memory/migrations/index.ts +20 -0
  293. package/src/memory/migrations/registry.ts +33 -0
  294. package/src/memory/onboarding-events-store.ts +7 -0
  295. package/src/memory/schema/a2a.ts +15 -0
  296. package/src/memory/schema/calls.ts +1 -0
  297. package/src/memory/schema/conversations.ts +3 -0
  298. package/src/memory/schema/index.ts +1 -0
  299. package/src/memory/schema/inference.ts +2 -0
  300. package/src/memory/schema/infrastructure.ts +2 -0
  301. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  302. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  303. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  304. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  305. package/src/memory/v2/__tests__/injection.test.ts +221 -17
  306. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  307. package/src/memory/v2/__tests__/router.test.ts +489 -1
  308. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  309. package/src/memory/v2/activation-store.ts +14 -16
  310. package/src/memory/v2/cli-command-content.ts +19 -0
  311. package/src/memory/v2/cli-command-store.ts +304 -0
  312. package/src/memory/v2/consolidation-job.ts +14 -0
  313. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  314. package/src/memory/v2/injection-events.ts +101 -0
  315. package/src/memory/v2/injection.ts +69 -29
  316. package/src/memory/v2/page-index.ts +246 -19
  317. package/src/memory/v2/page-store.ts +18 -0
  318. package/src/memory/v2/router.ts +209 -55
  319. package/src/memory/v2/static-context.ts +4 -4
  320. package/src/memory/v2/types.ts +23 -0
  321. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  322. package/src/messaging/providers/a2a/deliver.ts +156 -0
  323. package/src/messaging/providers/gmail/client.ts +9 -2
  324. package/src/messaging/providers/index.ts +18 -3
  325. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  327. package/src/messaging/providers/slack/adapter.ts +178 -25
  328. package/src/messaging/providers/slack/api.test.ts +54 -0
  329. package/src/messaging/providers/slack/api.ts +119 -3
  330. package/src/messaging/providers/slack/client.ts +12 -0
  331. package/src/messaging/providers/slack/deep-link.ts +20 -1
  332. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  333. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  334. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  335. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  336. package/src/messaging/providers/slack/send.test.ts +77 -0
  337. package/src/messaging/providers/slack/send.ts +8 -2
  338. package/src/messaging/providers/slack/types.ts +14 -0
  339. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  340. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  341. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  342. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
  343. package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
  344. package/src/notifications/adapters/macos.ts +12 -2
  345. package/src/notifications/broadcaster.ts +29 -4
  346. package/src/notifications/conversation-seed-composer.ts +14 -2
  347. package/src/notifications/copy-composer.ts +17 -64
  348. package/src/notifications/decision-engine.ts +111 -44
  349. package/src/notifications/deferred-emit.ts +135 -0
  350. package/src/notifications/deterministic-checks.ts +96 -0
  351. package/src/notifications/emit-signal.ts +10 -1
  352. package/src/notifications/home-feed-side-effect.ts +136 -27
  353. package/src/notifications/signal.ts +0 -4
  354. package/src/notifications/types.ts +8 -0
  355. package/src/oauth/connect-orchestrator.ts +3 -0
  356. package/src/oauth/credential-token-resolver.ts +2 -0
  357. package/src/oauth/manual-token-connection.ts +19 -0
  358. package/src/oauth/oauth-store.ts +12 -0
  359. package/src/oauth/platform-connection.test.ts +43 -3
  360. package/src/oauth/platform-connection.ts +13 -4
  361. package/src/oauth/seed-providers.ts +22 -0
  362. package/src/permissions/prompter.ts +5 -2
  363. package/src/permissions/secret-prompter.ts +4 -1
  364. package/src/plugins/defaults/injectors.ts +118 -26
  365. package/src/plugins/external-plugin-loader.ts +82 -10
  366. package/src/plugins/types.ts +16 -7
  367. package/src/prompts/__tests__/system-prompt.test.ts +44 -45
  368. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  369. package/src/prompts/normalize-onboarding.ts +40 -0
  370. package/src/prompts/sections.ts +32 -14
  371. package/src/prompts/system-prompt.ts +105 -76
  372. package/src/prompts/template-detection.ts +37 -0
  373. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  374. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  375. package/src/prompts/templates/VOICE.md +3 -0
  376. package/src/prompts/templates/system-sections.ts +51 -10
  377. package/src/providers/__tests__/inference.test.ts +2 -0
  378. package/src/providers/anthropic/client.ts +132 -5
  379. package/src/providers/call-site-routing.ts +24 -6
  380. package/src/providers/connection-resolution.ts +63 -13
  381. package/src/providers/fireworks/client.ts +20 -2
  382. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  383. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  384. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  385. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  386. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  387. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  388. package/src/providers/inference/adapter-factory.ts +24 -21
  389. package/src/providers/inference/auth.ts +15 -3
  390. package/src/providers/inference/backfill.ts +14 -1
  391. package/src/providers/inference/codex-token-refresh.ts +128 -0
  392. package/src/providers/inference/connections.ts +85 -5
  393. package/src/providers/inference/resolve-auth.ts +50 -5
  394. package/src/providers/model-catalog.ts +244 -242
  395. package/src/providers/model-intents.ts +3 -3
  396. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  397. package/src/providers/openai/chat-completions-provider.ts +215 -25
  398. package/src/providers/openai/responses-provider.ts +9 -3
  399. package/src/providers/openrouter/client.ts +46 -4
  400. package/src/providers/platform-proxy/constants.ts +3 -4
  401. package/src/providers/provider-catalog-visibility.ts +3 -1
  402. package/src/providers/provider-send-message.ts +27 -12
  403. package/src/providers/registry.ts +30 -1
  404. package/src/providers/types.ts +25 -0
  405. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  406. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  407. package/src/runtime/agent-wake.ts +212 -57
  408. package/src/runtime/auth/route-policy.ts +20 -3
  409. package/src/runtime/background-job-runner.ts +26 -0
  410. package/src/runtime/channel-reply-delivery.ts +182 -47
  411. package/src/runtime/channel-retry-sweep.ts +141 -16
  412. package/src/runtime/http-server.ts +7 -16
  413. package/src/runtime/http-types.ts +7 -51
  414. package/src/runtime/pending-interactions.ts +51 -8
  415. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  416. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  417. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
  418. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  419. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  420. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  421. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  422. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  423. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  424. package/src/runtime/routes/approval-routes.ts +4 -1
  425. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  426. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  427. package/src/runtime/routes/consolidation-routes.ts +100 -0
  428. package/src/runtime/routes/content-source-routes.ts +78 -0
  429. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  430. package/src/runtime/routes/conversation-query-routes.ts +130 -12
  431. package/src/runtime/routes/conversation-routes.ts +288 -76
  432. package/src/runtime/routes/document-comments-routes.ts +287 -0
  433. package/src/runtime/routes/documents-routes.ts +33 -0
  434. package/src/runtime/routes/home-feed-routes.ts +6 -3
  435. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  436. package/src/runtime/routes/host-browser-routes.ts +8 -1
  437. package/src/runtime/routes/identity-routes.ts +21 -0
  438. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  439. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  440. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  441. package/src/runtime/routes/index.ts +14 -4
  442. package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
  443. package/src/runtime/routes/integrations/a2a.ts +294 -0
  444. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  445. package/src/runtime/routes/log-export-routes.ts +39 -0
  446. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  447. package/src/runtime/routes/notification-routes.ts +19 -2
  448. package/src/runtime/routes/question-routes.ts +4 -1
  449. package/src/runtime/routes/sanity-routes.ts +159 -0
  450. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  451. package/src/runtime/routes/subagents-routes.ts +41 -0
  452. package/src/runtime/services/conversation-serializer.ts +30 -4
  453. package/src/schedule/integration-status.ts +3 -1
  454. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  455. package/src/security/oauth2-device-code.ts +307 -0
  456. package/src/security/oauth2.ts +26 -9
  457. package/src/security/secure-keys.ts +5 -0
  458. package/src/skills/catalog-install.ts +6 -2
  459. package/src/subagent/manager.ts +2 -0
  460. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  461. package/src/tools/browser/browser-execution.ts +93 -0
  462. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  463. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  464. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  465. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  466. package/src/tools/browser/cdp-client/factory.ts +87 -3
  467. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  468. package/src/tools/browser/cdp-client/types.ts +36 -0
  469. package/src/tools/browser/pinned-tabs.ts +90 -0
  470. package/src/tools/document/document-comment-tool.test.ts +379 -0
  471. package/src/tools/document/document-comment-tool.ts +156 -0
  472. package/src/tools/document/document-tool.ts +128 -2
  473. package/src/tools/memory/register.ts +1 -9
  474. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  475. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  476. package/src/tools/network/domain-normalize.ts +17 -0
  477. package/src/tools/network/web-fetch.ts +213 -64
  478. package/src/tools/network/web-search.ts +191 -66
  479. package/src/tools/registry.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +3 -2
  481. package/src/tools/tool-approval-handler.ts +19 -12
  482. package/src/tools/types.ts +41 -2
  483. package/src/tools/ui-surface/definitions.ts +3 -1
  484. package/src/types/onboarding-context.ts +4 -0
  485. package/src/util/__tests__/favicon.test.ts +84 -0
  486. package/src/util/favicon.ts +40 -0
  487. package/src/util/platform.ts +0 -5
  488. package/src/workspace/git-service.ts +75 -4
  489. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  490. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  491. package/src/workspace/migrations/registry.ts +4 -0
  492. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  493. package/src/config/bundled-skills/document/SKILL.md +0 -54
  494. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  495. package/src/daemon/seed-files.ts +0 -18
  496. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  497. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
  498. package/src/runtime/routes/interface-routes.ts +0 -43
  499. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  500. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  501. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  502. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  503. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -51,6 +51,11 @@ export interface ContextWindowResult {
51
51
  thresholdTokens: number;
52
52
  compactedMessages: number;
53
53
  compactedPersistedMessages: number;
54
+ /**
55
+ * Number of recent ("tail") messages preserved verbatim alongside the
56
+ * summary. Omitted on no-op / skipped results — defaults to 0 at render.
57
+ */
58
+ preservedTailMessages?: number;
54
59
  summaryCalls: number;
55
60
  summaryInputTokens: number;
56
61
  summaryOutputTokens: number;
@@ -216,6 +221,26 @@ export class ContextWindowManager {
216
221
  return getConfig().compaction;
217
222
  }
218
223
 
224
+ get maxInputTokens(): number {
225
+ return this.config.maxInputTokens;
226
+ }
227
+
228
+ /**
229
+ * Estimate the prompt-token cost of `messages` using the same path as the
230
+ * auto-compaction pre-check. Clears the system-prompt cache so the next
231
+ * turn re-resolves it (the system prompt is lazy and may have changed).
232
+ */
233
+ estimateInputTokens(messages: Message[]): number {
234
+ try {
235
+ return estimatePromptTokens(messages, this.systemPrompt, {
236
+ providerName: this.estimationProviderName,
237
+ toolTokenBudget: this.toolTokenBudget,
238
+ });
239
+ } finally {
240
+ this.clearSystemPromptCache();
241
+ }
242
+ }
243
+
219
244
  /**
220
245
  * Cheap pre-check — estimate the current token count and compare against
221
246
  * `compaction.autoThreshold`. Callers pass the estimate back through
@@ -315,9 +315,11 @@ async function checkManagedProvider(
315
315
  const client = await VellumPlatformClient.create();
316
316
  if (!client?.platformAssistantId) return results;
317
317
 
318
+ // Query without a status filter so we can distinguish "never
319
+ // connected" (empty result) from "previously connected but now
320
+ // inactive" (non-empty result with no ACTIVE entries).
318
321
  const params = new URLSearchParams();
319
322
  params.set("provider", providerRow.provider);
320
- params.set("status", "ACTIVE");
321
323
 
322
324
  const path = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/connections/?${params.toString()}`;
323
325
  const response = await client.fetch(path);
@@ -331,19 +333,30 @@ async function checkManagedProvider(
331
333
  }
332
334
 
333
335
  const body = (await response.json()) as unknown;
334
- const connections = (
336
+ const allConnections = (
335
337
  Array.isArray(body)
336
338
  ? body
337
339
  : ((body as Record<string, unknown>).results ?? [])
338
- ) as Array<{ id: string; account_label?: string }>;
340
+ ) as Array<{ id: string; account_label?: string; status?: string }>;
341
+
342
+ if (allConnections.length === 0) {
343
+ // No connections of any status — the user has never connected this
344
+ // provider. The suggested-prompts system handles prompting them to
345
+ // connect; this is not a health issue.
346
+ return results;
347
+ }
348
+
349
+ const connections = allConnections.filter(
350
+ (c) => (c.status ?? "ACTIVE").toUpperCase() === "ACTIVE",
351
+ );
339
352
 
340
353
  if (connections.length === 0) {
341
- // No active managed connections report as missing so the
342
- // heartbeat can notify the user.
354
+ // Connections exist but none are active the user previously
355
+ // connected and the connection was revoked/deactivated.
343
356
  results.push({
344
357
  connectionId: `managed:${providerRow.provider}`,
345
358
  provider: providerRow.provider,
346
- accountInfo: null,
359
+ accountInfo: allConnections[0]?.account_label ?? null,
347
360
  status: "missing_token",
348
361
  details: `No active managed connection for ${providerRow.provider}. Reconnect on the Vellum platform.`,
349
362
  missingScopes: [],
@@ -550,9 +563,20 @@ export async function checkAllCredentials(): Promise<CredentialHealthReport> {
550
563
  for (const providerRow of providers) {
551
564
  if (!(await isManagedProvider(providerRow))) continue;
552
565
 
553
- // If the provider is in managed mode and also has BYO connections,
554
- // remove the stale BYO results — managed mode takes priority.
555
- if (byoProviders.has(providerRow.provider)) {
566
+ let managedResults: CredentialHealthResult[] = [];
567
+ try {
568
+ managedResults = await checkManagedProvider(providerRow);
569
+ } catch (err) {
570
+ log.warn(
571
+ { err, provider: providerRow.provider },
572
+ "Failed to check managed provider health",
573
+ );
574
+ }
575
+
576
+ // Only replace BYO results with managed results when the managed
577
+ // check returned something. If managed returned empty (user never
578
+ // connected via managed mode), keep any existing BYO results.
579
+ if (managedResults.length > 0 && byoProviders.has(providerRow.provider)) {
556
580
  const beforeLen = results.length;
557
581
  const filtered = results.filter(
558
582
  (r) => r.provider !== providerRow.provider,
@@ -562,16 +586,7 @@ export async function checkAllCredentials(): Promise<CredentialHealthReport> {
562
586
  results.push(...filtered);
563
587
  }
564
588
  }
565
-
566
- try {
567
- const managedResults = await checkManagedProvider(providerRow);
568
- results.push(...managedResults);
569
- } catch (err) {
570
- log.warn(
571
- { err, provider: providerRow.provider },
572
- "Failed to check managed provider health",
573
- );
574
- }
589
+ results.push(...managedResults);
575
590
  }
576
591
 
577
592
  const unhealthy = results.filter((r) => r.status !== "healthy");
@@ -97,7 +97,6 @@ mock.module("../../memory/auto-analysis-enqueue.js", () => ({
97
97
  },
98
98
  }));
99
99
 
100
- let memoryRetroEnabled = false;
101
100
  const memoryRetroCalls: Array<{
102
101
  conversationId: string;
103
102
  trigger: string;
@@ -108,7 +107,6 @@ mock.module("../../memory/memory-retrospective-enqueue.js", () => ({
108
107
  conversationId: string;
109
108
  trigger: string;
110
109
  }) => {
111
- if (!memoryRetroEnabled) return;
112
110
  memoryRetroCalls.push(args);
113
111
  },
114
112
  // Also export sibling functions other modules import from this file, so
@@ -205,7 +203,6 @@ describe("disposeConversation — auto-analysis enqueue", () => {
205
203
  autoAnalyzeCalls.length = 0;
206
204
  memoryRetroCalls.length = 0;
207
205
  autoAnalyzeEnabled = true;
208
- memoryRetroEnabled = false;
209
206
  autoAnalysisConversations.clear();
210
207
  v2Enabled = false;
211
208
  });
@@ -390,13 +387,11 @@ describe("disposeConversation — memory-retrospective lifecycle safety net", ()
390
387
  autoAnalyzeCalls.length = 0;
391
388
  memoryRetroCalls.length = 0;
392
389
  autoAnalyzeEnabled = false;
393
- memoryRetroEnabled = false;
394
390
  autoAnalysisConversations.clear();
395
391
  v2Enabled = false;
396
392
  });
397
393
 
398
- test("guardian conversation + flag on — enqueues memory-retrospective with trigger 'lifecycle'", () => {
399
- memoryRetroEnabled = true;
394
+ test("guardian conversation — enqueues memory-retrospective with trigger 'lifecycle'", () => {
400
395
  const ctx = makeDisposeContext({
401
396
  conversationId: "conv-retro",
402
397
  trustClass: "guardian",
@@ -411,20 +406,7 @@ describe("disposeConversation — memory-retrospective lifecycle safety net", ()
411
406
  });
412
407
  });
413
408
 
414
- test("flag off — no memory-retrospective enqueue", () => {
415
- memoryRetroEnabled = false;
416
- const ctx = makeDisposeContext({
417
- conversationId: "conv-retro-off",
418
- trustClass: "guardian",
419
- });
420
-
421
- disposeConversation(ctx);
422
-
423
- expect(memoryRetroCalls).toHaveLength(0);
424
- });
425
-
426
- test("untrusted actor — no memory-retrospective enqueue even when flag is on", () => {
427
- memoryRetroEnabled = true;
409
+ test("untrusted actor — no memory-retrospective enqueue", () => {
428
410
  const ctx = makeDisposeContext({
429
411
  conversationId: "conv-retro-untrusted",
430
412
  trustClass: "unknown",
@@ -443,8 +425,7 @@ describe("disposeConversation — memory-retrospective lifecycle safety net", ()
443
425
  // outside the `!isAutoAnalysis` guard, so it fired even for auto-analysis
444
426
  // conversations. Mirrors the indexer-time gate in `indexer.ts` and
445
427
  // matches the existing graph_extract recursion-guard semantics.
446
- test("auto-analysis conversation — does NOT enqueue memory-retrospective even with flag on", () => {
447
- memoryRetroEnabled = true;
428
+ test("auto-analysis conversation — does NOT enqueue memory-retrospective", () => {
448
429
  autoAnalysisConversations.add("conv-auto-retro");
449
430
  const ctx = makeDisposeContext({
450
431
  conversationId: "conv-auto-retro",
@@ -49,11 +49,8 @@ mock.module("../../runtime/assistant-event-hub.js", () => ({
49
49
 
50
50
  // Dynamic imports after mock.module calls so the stubs take effect
51
51
  // before the modules under test are loaded.
52
- const {
53
- HOST_TOOL_NAMES,
54
- HOST_TOOL_TO_CAPABILITY,
55
- isToolActiveForContext,
56
- } = await import("../conversation-tool-setup.js");
52
+ const { HOST_TOOL_NAMES, HOST_TOOL_TO_CAPABILITY, isToolActiveForContext } =
53
+ await import("../conversation-tool-setup.js");
57
54
  type SkillProjectionContext =
58
55
  import("../conversation-tool-setup.js").SkillProjectionContext;
59
56
  type SkillProjectionCache =
@@ -75,6 +72,66 @@ beforeEach(() => {
75
72
  mockClientCountByCapability.clear();
76
73
  });
77
74
 
75
+ describe("isToolActiveForContext — Slack task_progress UI exception", () => {
76
+ test("ui_show and ui_update are active for Slack task_progress turns", () => {
77
+ const ctx = makeCtx({
78
+ hasNoClient: false,
79
+ channelCapabilities: {
80
+ channel: "slack",
81
+ supportsDynamicUi: false,
82
+ },
83
+ });
84
+
85
+ expect(isToolActiveForContext("ui_show", ctx)).toBe(true);
86
+ expect(isToolActiveForContext("ui_update", ctx)).toBe(true);
87
+ });
88
+
89
+ test("ui_dismiss remains hidden for Slack without dynamic UI support", () => {
90
+ expect(
91
+ isToolActiveForContext(
92
+ "ui_dismiss",
93
+ makeCtx({
94
+ hasNoClient: false,
95
+ channelCapabilities: {
96
+ channel: "slack",
97
+ supportsDynamicUi: false,
98
+ },
99
+ }),
100
+ ),
101
+ ).toBe(false);
102
+ });
103
+
104
+ test("Slack task_progress UI tools still require a connected client path", () => {
105
+ expect(
106
+ isToolActiveForContext(
107
+ "ui_show",
108
+ makeCtx({
109
+ hasNoClient: true,
110
+ channelCapabilities: {
111
+ channel: "slack",
112
+ supportsDynamicUi: false,
113
+ },
114
+ }),
115
+ ),
116
+ ).toBe(false);
117
+ });
118
+
119
+ test("other non-dynamic channels still hide UI surface tools", () => {
120
+ expect(
121
+ isToolActiveForContext(
122
+ "ui_show",
123
+ makeCtx({
124
+ hasNoClient: false,
125
+ channelCapabilities: {
126
+ channel: "telegram",
127
+ supportsDynamicUi: false,
128
+ },
129
+ }),
130
+ ),
131
+ ).toBe(false);
132
+ });
133
+ });
134
+
78
135
  describe("isToolActiveForContext — host tool capability gating", () => {
79
136
  // macOS transport: SSE-based interactive approval required.
80
137
  test("host_bash is active for macOS with a connected client", () => {
@@ -331,7 +388,10 @@ describe("isToolActiveForContext — cross-client exposure for host_file_*", ()
331
388
  expect(
332
389
  isToolActiveForContext(
333
390
  tool,
334
- makeCtx({ hasNoClient: true, transportInterface: "chrome-extension" }),
391
+ makeCtx({
392
+ hasNoClient: true,
393
+ transportInterface: "chrome-extension",
394
+ }),
335
395
  ),
336
396
  ).toBe(false);
337
397
  });
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Tests that the native Anthropic `server_tool_complete` agent event produces
3
+ * a structured `WebSearchMetadata` payload on the emitted `tool_result` server
4
+ * message while keeping the back-compat `result: string` byte-identical.
5
+ *
6
+ * Mirrors the mocked-dependency collector pattern used in
7
+ * tool-result-metadata-plumbing.test.ts.
8
+ */
9
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
10
+
11
+ // ── Mock platform (must precede imports that read it) ─────────────────────────
12
+ mock.module("../../util/logger.js", () => ({
13
+ getLogger: () =>
14
+ new Proxy({} as Record<string, unknown>, {
15
+ get: () => () => {},
16
+ }),
17
+ }));
18
+
19
+ mock.module("../../config/loader.js", () => ({
20
+ getConfig: () => ({
21
+ skills: {
22
+ entries: {},
23
+ load: { extraDirs: [], watch: false, watchDebounceMs: 0 },
24
+ install: { nodeManager: "npm" },
25
+ allowBundled: null,
26
+ remoteProviders: {
27
+ skillssh: { enabled: true },
28
+ clawhub: { enabled: true },
29
+ },
30
+ remotePolicy: {
31
+ blockSuspicious: true,
32
+ blockMalware: true,
33
+ maxSkillsShRisk: "medium",
34
+ },
35
+ },
36
+ }),
37
+ loadConfig: () => ({}),
38
+ }));
39
+
40
+ mock.module("../../memory/conversation-crud.js", () => ({
41
+ addMessage: () => ({ id: "mock-msg-id" }),
42
+ getMessageById: () => null,
43
+ updateMessageContent: () => {},
44
+ provenanceFromTrustContext: () => ({}),
45
+ }));
46
+
47
+ mock.module("../../memory/llm-request-log-store.js", () => ({
48
+ recordRequestLog: () => {},
49
+ backfillMessageIdOnLogs: () => {},
50
+ }));
51
+
52
+ // ── Imports (after mocks) ─────────────────────────────────────────────────────
53
+ import type {
54
+ EventHandlerDeps,
55
+ EventHandlerState,
56
+ } from "../conversation-agent-loop-handlers.js";
57
+ import {
58
+ createEventHandlerState,
59
+ dispatchAgentEvent,
60
+ } from "../conversation-agent-loop-handlers.js";
61
+ import type { ServerMessage } from "../message-protocol.js";
62
+
63
+ type ToolResultEvent = Extract<ServerMessage, { type: "tool_result" }>;
64
+
65
+ // ── Helpers ───────────────────────────────────────────────────────────────────
66
+
67
+ function createCollectorDeps(providerName = "anthropic"): {
68
+ deps: EventHandlerDeps;
69
+ events: ServerMessage[];
70
+ } {
71
+ const events: ServerMessage[] = [];
72
+ const deps = {
73
+ ctx: {
74
+ conversationId: "conv-native-meta",
75
+ provider: { name: providerName },
76
+ traceEmitter: { emit: () => {} },
77
+ streamThinking: false,
78
+ emitActivityState: () => {},
79
+ markWorkspaceTopLevelDirty: () => {},
80
+ currentTurnSurfaces: [],
81
+ } as unknown as EventHandlerDeps["ctx"],
82
+ onEvent: (msg: ServerMessage) => events.push(msg),
83
+ reqId: "req-native-meta",
84
+ isFirstMessage: false,
85
+ shouldGenerateTitle: false,
86
+ rlog: new Proxy({} as Record<string, unknown>, {
87
+ get: () => () => {},
88
+ }) as unknown as EventHandlerDeps["rlog"],
89
+ turnChannelContext: {
90
+ userMessageChannel: "vellum",
91
+ assistantMessageChannel: "vellum",
92
+ } as EventHandlerDeps["turnChannelContext"],
93
+ turnInterfaceContext: {
94
+ userMessageInterface: "macos",
95
+ assistantMessageInterface: "macos",
96
+ } as EventHandlerDeps["turnInterfaceContext"],
97
+ } as EventHandlerDeps;
98
+ return { deps, events };
99
+ }
100
+
101
+ // ── Tests ─────────────────────────────────────────────────────────────────────
102
+
103
+ describe("native server_tool_complete metadata", () => {
104
+ let state: EventHandlerState;
105
+
106
+ beforeEach(() => {
107
+ state = createEventHandlerState();
108
+ });
109
+
110
+ test("builds structured WebSearchMetadata and keeps result text byte-identical", async () => {
111
+ const { deps, events } = createCollectorDeps();
112
+ const toolUseId = "tu1";
113
+
114
+ await dispatchAgentEvent(state, deps, {
115
+ type: "server_tool_start",
116
+ name: "web_search",
117
+ toolUseId,
118
+ input: { query: "Air Jordan auction" },
119
+ });
120
+
121
+ await dispatchAgentEvent(state, deps, {
122
+ type: "server_tool_complete",
123
+ toolUseId,
124
+ isError: false,
125
+ content: [
126
+ {
127
+ type: "web_search_result",
128
+ title: "X",
129
+ url: "https://www.cnn.com/a",
130
+ },
131
+ {
132
+ type: "web_search_result",
133
+ title: "Y",
134
+ url: "https://www.nbcnews.com/b",
135
+ },
136
+ ],
137
+ });
138
+
139
+ const toolResultEvent = events.find(
140
+ (e): e is ToolResultEvent => e.type === "tool_result",
141
+ );
142
+ expect(toolResultEvent).toBeDefined();
143
+
144
+ const meta = toolResultEvent?.activityMetadata?.webSearch;
145
+ expect(meta).toBeDefined();
146
+ expect(meta?.query).toBe("Air Jordan auction");
147
+ expect(meta?.provider).toBe("anthropic-native");
148
+ expect(meta?.resultCount).toBe(2);
149
+ expect(meta?.results[0]?.domain).toBe("www.cnn.com");
150
+ expect(meta?.results[1]?.domain).toBe("www.nbcnews.com");
151
+ expect(meta?.results[0]?.rank).toBe(1);
152
+ expect(meta?.results[1]?.rank).toBe(2);
153
+ expect(meta?.durationMs).toBeGreaterThanOrEqual(0);
154
+
155
+ // Favicons are synthesized from the result domain so clients can render
156
+ // a per-result icon without an extra round-trip.
157
+ expect(meta?.results[0]?.faviconUrl).toContain("google.com/s2/favicons");
158
+ expect(meta?.results[1]?.faviconUrl).toContain("google.com/s2/favicons");
159
+
160
+ // Back-compat: result text must be byte-identical to the legacy format.
161
+ expect(toolResultEvent?.result).toBe(
162
+ "X\nhttps://www.cnn.com/a\n\nY\nhttps://www.nbcnews.com/b",
163
+ );
164
+
165
+ // Per-toolUseId scratch maps must be cleaned up after completion.
166
+ expect(state.serverToolStartedAt.has(toolUseId)).toBe(false);
167
+ expect(state.serverToolInputs.has(toolUseId)).toBe(false);
168
+ });
169
+
170
+ test("prefers `resolvedInput.query` over the input captured at server_tool_start", async () => {
171
+ // Mirrors the live Anthropic flow: server_tool_start fires with `input: {}`
172
+ // because the SDK streams server-tool input via `input_json_delta`, then
173
+ // the provider populates `resolvedInput` on `server_tool_complete` from
174
+ // the accumulated JSON. Without this plumb-through, `meta.query` is empty.
175
+ const { deps, events } = createCollectorDeps();
176
+ const toolUseId = "tu_resolved";
177
+
178
+ await dispatchAgentEvent(state, deps, {
179
+ type: "server_tool_start",
180
+ name: "web_search",
181
+ toolUseId,
182
+ input: {},
183
+ });
184
+
185
+ await dispatchAgentEvent(state, deps, {
186
+ type: "server_tool_complete",
187
+ toolUseId,
188
+ isError: false,
189
+ resolvedInput: { query: "OpenAI Dev Day recap" },
190
+ content: [
191
+ {
192
+ type: "web_search_result",
193
+ title: "Recap",
194
+ url: "https://example.com/a",
195
+ },
196
+ ],
197
+ });
198
+
199
+ const toolResultEvent = events.find(
200
+ (e): e is ToolResultEvent => e.type === "tool_result",
201
+ );
202
+ expect(toolResultEvent?.activityMetadata?.webSearch?.query).toBe(
203
+ "OpenAI Dev Day recap",
204
+ );
205
+ });
206
+
207
+ test("fires per-toolUseId for parallel server tool calls with independent queries and timings", async () => {
208
+ const { deps, events } = createCollectorDeps();
209
+
210
+ await dispatchAgentEvent(state, deps, {
211
+ type: "server_tool_start",
212
+ name: "web_search",
213
+ toolUseId: "tu_a",
214
+ input: { query: "alpha" },
215
+ });
216
+ await dispatchAgentEvent(state, deps, {
217
+ type: "server_tool_start",
218
+ name: "web_search",
219
+ toolUseId: "tu_b",
220
+ input: { query: "beta" },
221
+ });
222
+
223
+ // Hold long enough that the two completions can't share a duration.
224
+ await new Promise((r) => setTimeout(r, 20));
225
+
226
+ await dispatchAgentEvent(state, deps, {
227
+ type: "server_tool_complete",
228
+ toolUseId: "tu_a",
229
+ isError: false,
230
+ content: [
231
+ {
232
+ type: "web_search_result",
233
+ title: "A1",
234
+ url: "https://a.example.com/1",
235
+ },
236
+ ],
237
+ });
238
+
239
+ await new Promise((r) => setTimeout(r, 20));
240
+
241
+ await dispatchAgentEvent(state, deps, {
242
+ type: "server_tool_complete",
243
+ toolUseId: "tu_b",
244
+ isError: false,
245
+ content: [
246
+ {
247
+ type: "web_search_result",
248
+ title: "B1",
249
+ url: "https://b.example.com/1",
250
+ },
251
+ ],
252
+ });
253
+
254
+ const toolResults = events.filter(
255
+ (e): e is ToolResultEvent => e.type === "tool_result",
256
+ );
257
+ expect(toolResults).toHaveLength(2);
258
+
259
+ const byId = new Map(
260
+ toolResults.map((r) => [r.toolUseId, r] as const),
261
+ );
262
+ expect(byId.get("tu_a")?.activityMetadata?.webSearch?.query).toBe("alpha");
263
+ expect(byId.get("tu_b")?.activityMetadata?.webSearch?.query).toBe("beta");
264
+
265
+ const durA = byId.get("tu_a")?.activityMetadata?.webSearch?.durationMs ?? 0;
266
+ const durB = byId.get("tu_b")?.activityMetadata?.webSearch?.durationMs ?? 0;
267
+ // Each duration is computed against its own startedAt — tu_b should run
268
+ // longer because we slept once more between its start and its complete.
269
+ expect(durB).toBeGreaterThanOrEqual(durA);
270
+ });
271
+
272
+ test("forwards provider error codes instead of generic 'Search failed'", async () => {
273
+ const { deps, events } = createCollectorDeps();
274
+ const toolUseId = "tu_err_code";
275
+
276
+ await dispatchAgentEvent(state, deps, {
277
+ type: "server_tool_start",
278
+ name: "web_search",
279
+ toolUseId,
280
+ input: { query: "over the limit" },
281
+ });
282
+
283
+ await dispatchAgentEvent(state, deps, {
284
+ type: "server_tool_complete",
285
+ toolUseId,
286
+ isError: true,
287
+ errorCode: "max_uses_exceeded",
288
+ content: [],
289
+ });
290
+
291
+ const toolResultEvent = events.find(
292
+ (e): e is ToolResultEvent => e.type === "tool_result",
293
+ );
294
+ expect(toolResultEvent?.activityMetadata?.webSearch?.errorMessage).toBe(
295
+ "max_uses_exceeded",
296
+ );
297
+ });
298
+
299
+ test("does NOT emit activityMetadata for non-Anthropic providers", async () => {
300
+ // OpenAI's responses provider shares `server_tool_start`/`server_tool_complete`
301
+ // for `web_search_call`, but its results live inside the assistant text
302
+ // stream — emitting "anthropic-native" metadata for an OpenAI search would
303
+ // mis-label the provider and ship an empty `results` array.
304
+ const { deps, events } = createCollectorDeps("openai");
305
+ const toolUseId = "tu_openai";
306
+
307
+ await dispatchAgentEvent(state, deps, {
308
+ type: "server_tool_start",
309
+ name: "web_search",
310
+ toolUseId,
311
+ input: {},
312
+ });
313
+
314
+ await dispatchAgentEvent(state, deps, {
315
+ type: "server_tool_complete",
316
+ toolUseId,
317
+ isError: false,
318
+ });
319
+
320
+ const toolResultEvent = events.find(
321
+ (e): e is ToolResultEvent => e.type === "tool_result",
322
+ );
323
+ expect(toolResultEvent).toBeDefined();
324
+ expect(toolResultEvent?.activityMetadata).toBeUndefined();
325
+ // The back-compat `result: string` channel still fires (empty for OpenAI
326
+ // since the results are woven into the text stream, not structured).
327
+ expect(toolResultEvent?.result).toBe("");
328
+ });
329
+
330
+ test("sets errorMessage when the search errored", async () => {
331
+ const { deps, events } = createCollectorDeps();
332
+ const toolUseId = "tu_err";
333
+
334
+ await dispatchAgentEvent(state, deps, {
335
+ type: "server_tool_start",
336
+ name: "web_search",
337
+ toolUseId,
338
+ input: { query: "broken" },
339
+ });
340
+
341
+ await dispatchAgentEvent(state, deps, {
342
+ type: "server_tool_complete",
343
+ toolUseId,
344
+ isError: true,
345
+ content: [],
346
+ });
347
+
348
+ const toolResultEvent = events.find(
349
+ (e): e is ToolResultEvent => e.type === "tool_result",
350
+ );
351
+ const meta = toolResultEvent?.activityMetadata?.webSearch;
352
+ expect(meta?.errorMessage).toBe("Search failed");
353
+ expect(meta?.resultCount).toBe(0);
354
+ expect(meta?.results).toEqual([]);
355
+ expect(toolResultEvent?.isError).toBe(true);
356
+ });
357
+ });