@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
@@ -2,10 +2,11 @@
2
2
  * Resolves an `Auth` config into a `ResolvedAuth` that adapters consume.
3
3
  *
4
4
  * Resolution rules:
5
- * - api_key → fetch credential from vault → inject as bearer header
6
- * - platform → build managed proxy URL and fetch the platform API key
7
- * - none → pass through with no auth headers
8
- * - oauth_subscription / service_account reject (v2 not yet shipped)
5
+ * - api_key → fetch credential from vault → inject as bearer header
6
+ * - platform → build managed proxy URL and fetch the platform API key
7
+ * - none → pass through with no auth headers
8
+ * - oauth_subscription fetch OAuth token from vault (with auto-refresh) inject as bearer header
9
+ * - service_account → reject (v2 not yet shipped)
9
10
  */
10
11
 
11
12
  import {
@@ -13,7 +14,11 @@ import {
13
14
  resolveManagedProxyContext,
14
15
  } from "../../providers/platform-proxy/context.js";
15
16
  import { getSecureKeyAsync } from "../../security/secure-keys.js";
17
+ import { getLogger } from "../../util/logger.js";
16
18
  import type { Auth, ResolvedAuth } from "./auth.js";
19
+ import { PROVIDERS_REQUIRING_BASE_URL_AND_MODELS } from "./connections.js";
20
+
21
+ const log = getLogger("resolve-auth");
17
22
 
18
23
  export type ResolveAuthError =
19
24
  | { code: "credential_not_found"; credential: string }
@@ -27,6 +32,19 @@ export async function resolveAuth(
27
32
  ): Promise<
28
33
  { ok: true; resolved: ResolvedAuth } | { ok: false; error: ResolveAuthError }
29
34
  > {
35
+ // Defense-in-depth: strip baseUrl for providers that should not accept one.
36
+ // The route layer rejects base_url for non-openai-compatible providers, but
37
+ // this guard catches any code path that bypasses route validation (e.g.
38
+ // corrupted DB rows, direct calls from internal code).
39
+ let safeBaseUrl = opts.baseUrl;
40
+ if (safeBaseUrl && !PROVIDERS_REQUIRING_BASE_URL_AND_MODELS.has(provider)) {
41
+ log.warn(
42
+ { provider, baseUrl: safeBaseUrl },
43
+ `Stripping baseUrl for provider "${provider}" — base_url is only valid for openai-compatible providers.`,
44
+ );
45
+ safeBaseUrl = null;
46
+ }
47
+
30
48
  switch (auth.type) {
31
49
  case "api_key": {
32
50
  const value = await getSecureKeyAsync(auth.credential);
@@ -41,7 +59,7 @@ export async function resolveAuth(
41
59
  resolved: {
42
60
  kind: "header",
43
61
  headers: { Authorization: `Bearer ${value}` },
44
- ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
62
+ ...(safeBaseUrl ? { baseUrl: safeBaseUrl } : {}),
45
63
  },
46
64
  };
47
65
  }
@@ -65,7 +83,32 @@ export async function resolveAuth(
65
83
  case "none":
66
84
  return { ok: true, resolved: { kind: "none" } };
67
85
 
68
- case "oauth_subscription":
86
+ case "oauth_subscription": {
87
+ // Extract the credential prefix from the credential key.
88
+ // The credential field stores "credential/openai-codex/access_token";
89
+ // we need the prefix "credential/openai-codex" for the refresh logic.
90
+ const credentialPrefix = auth.credential.replace(/\/access_token$/, "");
91
+
92
+ const { getValidCodexAccessToken } = await import(
93
+ "./codex-token-refresh.js"
94
+ );
95
+ const token = await getValidCodexAccessToken(credentialPrefix);
96
+
97
+ if (!token) {
98
+ return {
99
+ ok: false,
100
+ error: { code: "credential_not_found", credential: auth.credential },
101
+ };
102
+ }
103
+ return {
104
+ ok: true,
105
+ resolved: {
106
+ kind: "header",
107
+ headers: { Authorization: `Bearer ${token}` },
108
+ },
109
+ };
110
+ }
111
+
69
112
  case "service_account":
70
113
  return {
71
114
  ok: false,
@@ -45,6 +45,13 @@ export interface CatalogModel {
45
45
  supportsVision?: boolean;
46
46
  supportsToolUse?: boolean;
47
47
  pricing?: CatalogModelPricing;
48
+ /**
49
+ * Upper bound for `reasoning_effort` accepted by this model's upstream API.
50
+ * Used by providers (e.g. Fireworks) to clamp Vellum's `xhigh`/`max` tiers
51
+ * down to whatever the model documents. Omit to inherit the provider
52
+ * default.
53
+ */
54
+ maxEffort?: "high" | "xhigh" | "max";
48
55
  /** When set, this model is only visible when the named feature flag is enabled. */
49
56
  featureFlag?: string;
50
57
  }
@@ -393,6 +400,21 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
393
400
  linkLabel: "Open Google AI Studio",
394
401
  },
395
402
  models: [
403
+ {
404
+ id: "gemini-3.5-flash",
405
+ displayName: "Gemini 3.5 Flash",
406
+ contextWindowTokens: 1048576,
407
+ maxOutputTokens: 65536,
408
+ supportsThinking: true,
409
+ supportsCaching: true,
410
+ supportsVision: true,
411
+ supportsToolUse: true,
412
+ pricing: {
413
+ inputPer1mTokens: 1.5,
414
+ outputPer1mTokens: 9.0,
415
+ cacheReadPer1mTokens: 0.15,
416
+ },
417
+ },
396
418
  {
397
419
  id: "gemini-3.1-pro-preview",
398
420
  displayName: "Gemini 3.1 Pro Preview",
@@ -589,6 +611,7 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
589
611
  supportsCaching: true,
590
612
  supportsVision: true,
591
613
  supportsToolUse: true,
614
+ maxEffort: "high",
592
615
  pricing: {
593
616
  inputPer1mTokens: 0.95,
594
617
  outputPer1mTokens: 4.0,
@@ -636,10 +659,11 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
636
659
  displayName: "DeepSeek V4 Pro",
637
660
  contextWindowTokens: 1040000,
638
661
  maxOutputTokens: 131072,
639
- supportsThinking: false,
662
+ supportsThinking: true,
640
663
  supportsCaching: false,
641
664
  supportsVision: false,
642
665
  supportsToolUse: true,
666
+ maxEffort: "max",
643
667
  pricing: { inputPer1mTokens: 1.74, outputPer1mTokens: 3.48 },
644
668
  },
645
669
  ],
@@ -775,6 +799,17 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
775
799
  supportsToolUse: true,
776
800
  pricing: { inputPer1mTokens: 3, outputPer1mTokens: 15 },
777
801
  },
802
+ {
803
+ id: "x-ai/grok-4.3",
804
+ displayName: "Grok 4.3",
805
+ contextWindowTokens: 1000000,
806
+ maxOutputTokens: 16000,
807
+ supportsThinking: true,
808
+ supportsCaching: false,
809
+ supportsVision: true,
810
+ supportsToolUse: true,
811
+ pricing: { inputPer1mTokens: 1.25, outputPer1mTokens: 2.5 },
812
+ },
778
813
  {
779
814
  id: "x-ai/grok-4",
780
815
  displayName: "Grok 4",
@@ -1057,6 +1092,18 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
1057
1092
  supportsToolUse: true,
1058
1093
  pricing: { inputPer1mTokens: 0.8, outputPer1mTokens: 3.2 },
1059
1094
  },
1095
+ // Owl (OpenRouter first-party)
1096
+ {
1097
+ id: "openrouter/owl-alpha",
1098
+ displayName: "Owl Alpha",
1099
+ contextWindowTokens: 1048576,
1100
+ maxOutputTokens: 262144,
1101
+ supportsThinking: false,
1102
+ supportsCaching: false,
1103
+ supportsVision: false,
1104
+ supportsToolUse: true,
1105
+ pricing: { inputPer1mTokens: 0, outputPer1mTokens: 0 },
1106
+ },
1060
1107
  ],
1061
1108
  defaultModel: "x-ai/grok-4.20-beta",
1062
1109
  apiKeyUrl: "https://openrouter.ai/keys",
@@ -66,34 +66,56 @@ export interface OpenAIChatCompletionsProviderOptions {
66
66
  extraCreateParams?: Record<string, unknown>;
67
67
  /** Upper bound for `reasoning_effort` sent on the wire. Defaults to "xhigh"
68
68
  * (OpenAI's current ceiling). Compatibility providers whose APIs only
69
- * document `low|medium|high` (e.g. Fireworks) should set this to "high" so
70
- * Vellum's `xhigh`/`max` tiers don't 4xx upstream. */
71
- maxReasoningEffort?: "high" | "xhigh";
69
+ * document `low|medium|high` should set this to "high" so Vellum's
70
+ * `xhigh`/`max` tiers don't 4xx upstream. Set to "max" for providers like
71
+ * Fireworks DeepSeek V4 that accept the full effort range. Subclasses can
72
+ * override {@link OpenAIChatCompletionsProvider.resolveMaxReasoningEffort}
73
+ * for per-model ceilings. */
74
+ maxReasoningEffort?: "high" | "xhigh" | "max";
72
75
  /** Parse `<think>...</think>` tags from the content stream into thinking
73
76
  * blocks. MiniMax and similar providers embed reasoning inside XML-style
74
77
  * tags in the regular content field rather than using `reasoning_content`. */
75
78
  parseThinkTags?: boolean;
76
79
  }
77
80
 
78
- /** Map our internal effort values to OpenAI's reasoning_effort parameter.
79
- * OpenAI caps at "xhigh", so our "max" tier collapses to "xhigh". `"none"` is
80
- * passed through explicitly because OpenAI defaults `reasoning_effort` to
81
- * "medium" when the field is omitted the user's opt-out is only honored
82
- * when we send it on the wire. */
83
- export const EFFORT_TO_REASONING_EFFORT: Record<
84
- string,
85
- NonNullable<
86
- OpenAI.Chat.Completions.ChatCompletionCreateParams["reasoning_effort"]
87
- >
88
- > = {
81
+ /** Wire-level reasoning_effort values. The OpenAI SDK type doesn't include
82
+ * `"max"`, but Fireworks accepts it for DeepSeek V4; the assignment to
83
+ * `params.reasoning_effort` casts through this union. */
84
+ type ReasoningEffortWire = "none" | "low" | "medium" | "high" | "xhigh" | "max";
85
+
86
+ const REASONING_EFFORT_RANK: Record<ReasoningEffortWire, number> = {
87
+ none: 0,
88
+ low: 1,
89
+ medium: 2,
90
+ high: 3,
91
+ xhigh: 4,
92
+ max: 5,
93
+ };
94
+
95
+ /** Map our internal effort values to a reasoning_effort wire value. `"max"`
96
+ * is emitted raw — providers cap it down to their own ceiling
97
+ * ({@link OpenAIChatCompletionsProviderOptions.maxReasoningEffort}) at send
98
+ * time. `"none"` is passed through explicitly because OpenAI-compatible APIs
99
+ * default `reasoning_effort` to `"medium"` when the field is omitted, so the
100
+ * user's opt-out is only honored when we send it on the wire. */
101
+ export const EFFORT_TO_REASONING_EFFORT: Record<string, ReasoningEffortWire> = {
89
102
  none: "none",
90
103
  low: "low",
91
104
  medium: "medium",
92
105
  high: "high",
93
106
  xhigh: "xhigh",
94
- max: "xhigh",
107
+ max: "max",
95
108
  };
96
109
 
110
+ export function clampReasoningEffort(
111
+ value: ReasoningEffortWire,
112
+ ceiling: "high" | "xhigh" | "max",
113
+ ): ReasoningEffortWire {
114
+ return REASONING_EFFORT_RANK[value] > REASONING_EFFORT_RANK[ceiling]
115
+ ? ceiling
116
+ : value;
117
+ }
118
+
97
119
  const OPENAI_SUPPORTED_IMAGE_TYPES = new Set([
98
120
  "image/jpeg",
99
121
  "image/png",
@@ -122,7 +144,7 @@ export class OpenAIChatCompletionsProvider implements Provider {
122
144
  private model: string;
123
145
  private streamTimeoutMs: number;
124
146
  private extraCreateParams: Record<string, unknown>;
125
- private maxReasoningEffort: "high" | "xhigh";
147
+ private maxReasoningEffort: "high" | "xhigh" | "max";
126
148
  private requestHeaders: Record<string, string>;
127
149
  private parseThinkTags: boolean;
128
150
 
@@ -187,10 +209,13 @@ export class OpenAIChatCompletionsProvider implements Provider {
187
209
  ? EFFORT_TO_REASONING_EFFORT[effort]
188
210
  : undefined;
189
211
  if (reasoningEffort && typeof nestedReasoningEffort !== "string") {
190
- params.reasoning_effort =
191
- reasoningEffort === "xhigh" && this.maxReasoningEffort === "high"
192
- ? "high"
193
- : reasoningEffort;
212
+ const ceiling = this.resolveMaxReasoningEffort(
213
+ modelOverride ?? this.model,
214
+ );
215
+ params.reasoning_effort = clampReasoningEffort(
216
+ reasoningEffort,
217
+ ceiling,
218
+ ) as OpenAI.Chat.Completions.ChatCompletionCreateParams["reasoning_effort"];
194
219
  }
195
220
 
196
221
  if (tools && tools.length > 0) {
@@ -546,6 +571,18 @@ export class OpenAIChatCompletionsProvider implements Provider {
546
571
  return this.extraCreateParams;
547
572
  }
548
573
 
574
+ /**
575
+ * Per-request reasoning_effort ceiling. Defaults to the provider-wide
576
+ * `maxReasoningEffort` from constructor options. Subclasses (e.g. Fireworks)
577
+ * override to consult the model catalog so per-model accepted ranges are
578
+ * respected.
579
+ */
580
+ protected resolveMaxReasoningEffort(
581
+ _model: string,
582
+ ): "high" | "xhigh" | "max" {
583
+ return this.maxReasoningEffort;
584
+ }
585
+
549
586
  /** Convert neutral messages + system prompt to OpenAI message format. */
550
587
  private toOpenAIMessages(
551
588
  messages: Message[],
@@ -23,6 +23,9 @@ export interface OpenAIResponsesProviderOptions {
23
23
  providerLabel?: string;
24
24
  streamTimeoutMs?: number;
25
25
  useNativeWebSearch?: boolean;
26
+ /** When true, target the Codex subscription endpoint and strip fields it
27
+ * rejects (`max_output_tokens`, `metadata`). */
28
+ codexSubscription?: boolean;
26
29
  }
27
30
 
28
31
  /** Map our internal effort values to the Responses API reasoning.effort parameter.
@@ -103,6 +106,7 @@ export class OpenAIResponsesProvider implements Provider {
103
106
  private model: string;
104
107
  private streamTimeoutMs: number;
105
108
  private useNativeWebSearch: boolean;
109
+ private codexSubscription: boolean;
106
110
 
107
111
  constructor(
108
112
  apiKey: string,
@@ -111,9 +115,12 @@ export class OpenAIResponsesProvider implements Provider {
111
115
  ) {
112
116
  this.name = options.providerName ?? "openai";
113
117
  this.providerLabel = options.providerLabel ?? "OpenAI";
118
+ this.codexSubscription = options.codexSubscription ?? false;
114
119
  this.client = new OpenAI({
115
120
  apiKey,
116
- baseURL: options.baseURL,
121
+ baseURL: this.codexSubscription
122
+ ? "https://chatgpt.com/backend-api/codex"
123
+ : options.baseURL,
117
124
  });
118
125
  this.model = model;
119
126
  this.streamTimeoutMs = options.streamTimeoutMs ?? 1_800_000;
@@ -142,7 +149,6 @@ export class OpenAIResponsesProvider implements Provider {
142
149
  const params: Record<string, unknown> = {
143
150
  model: modelOverride ?? this.model,
144
151
  input,
145
- store: false,
146
152
  };
147
153
 
148
154
  if (systemPrompt) {
@@ -152,7 +158,7 @@ export class OpenAIResponsesProvider implements Provider {
152
158
  );
153
159
  }
154
160
 
155
- if (maxTokens) {
161
+ if (maxTokens && !this.codexSubscription) {
156
162
  params.max_output_tokens = maxTokens;
157
163
  }
158
164
 
@@ -1,6 +1,7 @@
1
1
  import { ProviderError } from "../../util/errors.js";
2
2
  import { AnthropicProvider } from "../anthropic/client.js";
3
3
  import {
4
+ clampReasoningEffort,
4
5
  EFFORT_TO_REASONING_EFFORT,
5
6
  OpenAIChatCompletionsProvider,
6
7
  } from "../openai/chat-completions-provider.js";
@@ -200,7 +201,10 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
200
201
  const summaryOverride = extractReasoningSummaryOverride(config);
201
202
  const reasoning: Record<string, unknown> = { enabled: thinkingEnabled };
202
203
  if (mappedEffort) {
203
- reasoning.effort = mappedEffort;
204
+ reasoning.effort = clampReasoningEffort(
205
+ mappedEffort,
206
+ this.resolveMaxReasoningEffort(this.resolveEffectiveModel(options)),
207
+ );
204
208
  }
205
209
  if (thinkingEnabled) {
206
210
  reasoning.summary = summaryOverride ?? "detailed";
@@ -27,6 +27,15 @@ export interface FileContent {
27
27
  filename: string;
28
28
  };
29
29
  extracted_text?: string;
30
+ /**
31
+ * Internal id linking this block to a row in the attachments table.
32
+ * Set when the file block originates from a persisted user-message
33
+ * attachment so downstream consumers (DB joins, inline-chip
34
+ * positioning) can correlate the block back to its attachment id.
35
+ * Stripped by `daemon/handlers/shared.ts` before sending to the
36
+ * model.
37
+ */
38
+ _attachmentId?: string;
30
39
  }
31
40
 
32
41
  export interface ToolUseContent {
@@ -137,6 +146,22 @@ export type ProviderEvent =
137
146
  toolUseId: string;
138
147
  isError: boolean;
139
148
  content?: unknown[];
149
+ /**
150
+ * Finalized input for the server tool call (e.g. the actual query).
151
+ * Anthropic streams `server_tool_use` block input via `input_json_delta`
152
+ * events, so consumers reading the input at `server_tool_start` see `{}`.
153
+ * The provider accumulates the JSON and surfaces it here once the block
154
+ * stops, so downstream handlers can build accurate activity metadata.
155
+ */
156
+ resolvedInput?: Record<string, unknown>;
157
+ /**
158
+ * Provider-specific error code when `isError` is true (e.g. Anthropic's
159
+ * `max_uses_exceeded`, `query_too_long`). Surfaced so user-facing
160
+ * messages can be specific instead of a generic "Search failed".
161
+ */
162
+ errorCode?: string;
163
+ /** Optional human-readable error message from the provider. */
164
+ errorMessage?: string;
140
165
  };
141
166
 
142
167
  export interface SendMessageConfig {
@@ -22,8 +22,29 @@ import type { DiskPressureStatus } from "../../daemon/disk-pressure-guard.js";
22
22
  // Stub the DB-backed override-profile read so unit tests don't need a
23
23
  // real SQLite database. The wake helper calls this on every invocation
24
24
  // to honor the conversation's pinned inference profile.
25
+ // `getConversation` is consumed by `defaultResolveTarget` — most tests
26
+ // pass explicit `deps.resolveTarget` and bypass it, but the
27
+ // trust-context threading test below drives the default resolver and
28
+ // needs the existence/archived check to pass.
25
29
  mock.module("../../memory/conversation-crud.js", () => ({
26
30
  getConversationOverrideProfile: () => undefined,
31
+ getConversation: () => ({ archivedAt: null }),
32
+ }));
33
+
34
+ const mockGetOrCreateConversationCalls: Array<{
35
+ conversationId: string;
36
+ options: unknown;
37
+ }> = [];
38
+ mock.module("../../daemon/conversation-store.js", () => ({
39
+ getOrCreateConversation: (conversationId: string, options?: unknown) => {
40
+ mockGetOrCreateConversationCalls.push({ conversationId, options });
41
+ return Promise.resolve({ __mockConversation: true });
42
+ },
43
+ }));
44
+
45
+ let mockResolverTarget: unknown = null;
46
+ mock.module("../../daemon/wake-target-adapter.js", () => ({
47
+ conversationToWakeTarget: () => mockResolverTarget,
27
48
  }));
28
49
 
29
50
  mock.module("../../config/loader.js", () => ({
@@ -248,6 +269,8 @@ function makeTarget(options: {
248
269
  beforeEach(() => {
249
270
  __resetWakeChainForTests();
250
271
  recordRequestLogCalls.length = 0;
272
+ mockGetOrCreateConversationCalls.length = 0;
273
+ mockResolverTarget = null;
251
274
  mockDiskPressureStatus = {
252
275
  enabled: false,
253
276
  state: "disabled",
@@ -1587,4 +1610,195 @@ describe("wakeAgentForOpportunity", () => {
1587
1610
  // No log row was inserted because JSON.stringify threw.
1588
1611
  expect(recordRequestLogCalls).toHaveLength(0);
1589
1612
  });
1613
+
1614
+ // Regression guard for fork-based memory retrospectives: PR #31260
1615
+ // forked a conversation and waked it with a guardian trustContext,
1616
+ // but the wake's default resolver called
1617
+ // `getOrCreateConversation(conversationId)` with no options, so the
1618
+ // store hydrated with `trustContext === undefined`. `loadFromDb`
1619
+ // fail-closes to `trustClass: "unknown"` and filters out every
1620
+ // guardian-provenance message — so the LLM saw an empty history and
1621
+ // every fork sent `messages: []`. Threading trustContext through
1622
+ // ensures `setTrustContext` + `ensureActorScopedHistory` run during
1623
+ // hydration.
1624
+ const makeDefaultResolverTarget = (conversationId: string): WakeTarget => {
1625
+ const history: Message[] = [];
1626
+ let processing = false;
1627
+ return {
1628
+ conversationId,
1629
+ agentLoop: { run: async (input) => input },
1630
+ getMessages: () => history,
1631
+ pushMessage: () => {},
1632
+ emitAgentEvent: () => {},
1633
+ isProcessing: () => processing,
1634
+ markProcessing: (on) => {
1635
+ processing = on;
1636
+ },
1637
+ persistTailMessage: async () => {},
1638
+ };
1639
+ };
1640
+
1641
+ test("default resolver threads WakeOptions.trustContext into getOrCreateConversation", async () => {
1642
+ mockResolverTarget = makeDefaultResolverTarget("conv-thread-trust");
1643
+ const trustContext = {
1644
+ sourceChannel: "vellum",
1645
+ trustClass: "guardian",
1646
+ } as const;
1647
+
1648
+ await wakeAgentForOpportunity({
1649
+ conversationId: "conv-thread-trust",
1650
+ hint: "consolidate",
1651
+ source: "memory_v2_consolidation",
1652
+ trustContext,
1653
+ });
1654
+
1655
+ expect(mockGetOrCreateConversationCalls).toEqual([
1656
+ { conversationId: "conv-thread-trust", options: { trustContext } },
1657
+ ]);
1658
+ });
1659
+
1660
+ test("default resolver passes trustContext: undefined when WakeOptions.trustContext is omitted", async () => {
1661
+ // Inbound user-turn wakes get trust via processMessage(); the wake
1662
+ // must not synthesize a trust context out of thin air.
1663
+ mockResolverTarget = makeDefaultResolverTarget("conv-no-trust-default");
1664
+
1665
+ await wakeAgentForOpportunity({
1666
+ conversationId: "conv-no-trust-default",
1667
+ hint: "x",
1668
+ source: "unit-test",
1669
+ });
1670
+
1671
+ expect(mockGetOrCreateConversationCalls).toEqual([
1672
+ {
1673
+ conversationId: "conv-no-trust-default",
1674
+ options: { trustContext: undefined },
1675
+ },
1676
+ ]);
1677
+ });
1678
+
1679
+ describe("suppressWakeSurface option", () => {
1680
+ function makeCheckpointTarget(): {
1681
+ target: WakeTarget;
1682
+ persistedTailCalls: Message[];
1683
+ wakeProducedOutputCalls: string[];
1684
+ } {
1685
+ const firstAssistant: Message = {
1686
+ role: "assistant",
1687
+ content: [
1688
+ { type: "tool_use", id: "tu-1", name: "some_tool", input: {} },
1689
+ ],
1690
+ };
1691
+ const toolResult: Message = {
1692
+ role: "user",
1693
+ content: [{ type: "tool_result", tool_use_id: "tu-1", content: "ok" }],
1694
+ };
1695
+ const persistedTailCalls: Message[] = [];
1696
+ const baseline: Message[] = [
1697
+ { role: "user", content: [{ type: "text", text: "hi" }] },
1698
+ ];
1699
+ const history: Message[] = [...baseline];
1700
+ let processing = false;
1701
+ const wakeProducedOutputCalls: string[] = [];
1702
+
1703
+ const target: WakeTarget = {
1704
+ conversationId: "conv-suppress-surface",
1705
+ agentLoop: {
1706
+ run: async (_input, _onEvent, _signal, _requestId, onCheckpoint) => {
1707
+ const runHistory: Message[] = [..._input];
1708
+ runHistory.push(firstAssistant);
1709
+ runHistory.push(toolResult);
1710
+ await onCheckpoint!({
1711
+ turnIndex: 0,
1712
+ toolCount: 1,
1713
+ hasToolUse: true,
1714
+ history: runHistory,
1715
+ });
1716
+ return runHistory;
1717
+ },
1718
+ },
1719
+ getMessages: () => history,
1720
+ pushMessage: (msg) => {
1721
+ history.push(msg);
1722
+ },
1723
+ emitAgentEvent: () => {},
1724
+ isProcessing: () => processing,
1725
+ markProcessing: (on) => {
1726
+ processing = on;
1727
+ },
1728
+ persistTailMessage: async (msg) => {
1729
+ persistedTailCalls.push(msg);
1730
+ },
1731
+ onWakeProducedOutput: (_source, _hint, surfaceId) => {
1732
+ wakeProducedOutputCalls.push(surfaceId);
1733
+ },
1734
+ };
1735
+ return { target, persistedTailCalls, wakeProducedOutputCalls };
1736
+ }
1737
+
1738
+ test(
1739
+ "default (suppressWakeSurface omitted) still injects the ui_surface " +
1740
+ "card and calls onWakeProducedOutput",
1741
+ async () => {
1742
+ const { target, persistedTailCalls, wakeProducedOutputCalls } =
1743
+ makeCheckpointTarget();
1744
+
1745
+ await wakeAgentForOpportunity(
1746
+ {
1747
+ conversationId: "conv-suppress-surface",
1748
+ hint: "do the thing",
1749
+ source: "memory_v2_consolidation",
1750
+ },
1751
+ { resolveTarget: async () => target },
1752
+ );
1753
+
1754
+ // Existing behavior: card injected, broadcast fired exactly once.
1755
+ expect(wakeProducedOutputCalls).toHaveLength(1);
1756
+ const persistedFirst = persistedTailCalls[0];
1757
+ expect(persistedFirst).toBeDefined();
1758
+ const blocks = Array.isArray(persistedFirst!.content)
1759
+ ? persistedFirst!.content
1760
+ : [];
1761
+ const uiBlock = blocks.find(
1762
+ (b: { type?: string }) => b.type === "ui_surface",
1763
+ );
1764
+ expect(uiBlock).toBeDefined();
1765
+ },
1766
+ );
1767
+
1768
+ test(
1769
+ "suppressWakeSurface: true produces output but skips the ui_surface " +
1770
+ "card injection and the onWakeProducedOutput broadcast",
1771
+ async () => {
1772
+ const { target, persistedTailCalls, wakeProducedOutputCalls } =
1773
+ makeCheckpointTarget();
1774
+
1775
+ await wakeAgentForOpportunity(
1776
+ {
1777
+ conversationId: "conv-suppress-surface",
1778
+ hint: "do the thing",
1779
+ source: "memory_v2_consolidation",
1780
+ suppressWakeSurface: true,
1781
+ },
1782
+ { resolveTarget: async () => target },
1783
+ );
1784
+
1785
+ // Tail still persisted (wake produced real output).
1786
+ const persistedFirst = persistedTailCalls[0];
1787
+ expect(persistedFirst).toBeDefined();
1788
+ // First assistant tail message should NOT have a ui_surface block
1789
+ // prepended at the front.
1790
+ const blocks = Array.isArray(persistedFirst!.content)
1791
+ ? persistedFirst!.content
1792
+ : [];
1793
+ const firstBlock = blocks[0] as { type?: string } | undefined;
1794
+ expect(firstBlock?.type).not.toBe("ui_surface");
1795
+ const uiBlock = blocks.find(
1796
+ (b: { type?: string }) => b.type === "ui_surface",
1797
+ );
1798
+ expect(uiBlock).toBeUndefined();
1799
+ // Live broadcast was suppressed.
1800
+ expect(wakeProducedOutputCalls).toHaveLength(0);
1801
+ },
1802
+ );
1803
+ });
1590
1804
  });