@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
@@ -9,6 +9,14 @@
9
9
  "description": "Automatically trigger conversation analysis on the same cadence as memory extraction (batch threshold, idle debounce, end-of-conversation). The analysis agent has full tool access and writes back to memory and skills without user approval.",
10
10
  "defaultEnabled": false
11
11
  },
12
+ {
13
+ "id": "memory-retrospective-fork",
14
+ "scope": "assistant",
15
+ "key": "memory-retrospective-fork",
16
+ "label": "Fork-based memory retrospective",
17
+ "description": "Fork the source conversation through its latest message for memory retrospectives, instead of rendering the slice into a transcript and waking an empty background conversation. Lets the retrospective hit the provider prompt cache and read compaction summary + tail messages natively.",
18
+ "defaultEnabled": false
19
+ },
12
20
  {
13
21
  "id": "user-hosted-enabled",
14
22
  "scope": "client",
@@ -43,7 +51,7 @@
43
51
  },
44
52
  {
45
53
  "id": "settings-developer-nav",
46
- "scope": "client",
54
+ "scope": "assistant",
47
55
  "key": "settings-developer-nav",
48
56
  "label": "Settings Developer Nav",
49
57
  "description": "Control Developer nav visibility in macOS settings",
@@ -89,6 +97,14 @@
89
97
  "description": "Surface credential grant and audit inspection endpoints for reviewing active grants and access logs",
90
98
  "defaultEnabled": false
91
99
  },
100
+ {
101
+ "id": "chatgpt-subscription-auth",
102
+ "scope": "assistant",
103
+ "key": "chatgpt-subscription-auth",
104
+ "label": "ChatGPT Subscription Auth",
105
+ "description": "Enable ChatGPT subscription OAuth as a provider auth type for OpenAI models, using the Codex device-code flow.",
106
+ "defaultEnabled": false
107
+ },
92
108
  {
93
109
  "id": "deploy-to-vercel",
94
110
  "scope": "assistant",
@@ -275,7 +291,7 @@
275
291
  },
276
292
  {
277
293
  "id": "account-deletion",
278
- "scope": "client",
294
+ "scope": "assistant",
279
295
  "key": "account-deletion",
280
296
  "label": "Account Deletion",
281
297
  "description": "Surfaces the user-initiated account deletion flow in client settings.",
@@ -299,7 +315,7 @@
299
315
  },
300
316
  {
301
317
  "id": "pro-plan-adjust",
302
- "scope": "assistant",
318
+ "scope": "client",
303
319
  "key": "pro-plan-adjust",
304
320
  "label": "Pro Plan Adjust",
305
321
  "description": "Show the rich Plan card (current plan, features, Manage/Upgrade CTA) at the top of the macOS Settings \u2192 Billing tab.",
@@ -328,6 +344,86 @@
328
344
  "label": "Velvet Theme",
329
345
  "description": "Show the Velvet theme option in the macOS appearance settings. Velvet is a dark-mode variant with red/pink accent colors.",
330
346
  "defaultEnabled": false
347
+ },
348
+ {
349
+ "id": "query-complexity-routing",
350
+ "scope": "assistant",
351
+ "key": "query-complexity-routing",
352
+ "label": "Query Complexity Routing",
353
+ "description": "Automatically route user messages to the most appropriate inference profile based on query complexity. Simple queries use the speed profile, complex queries escalate to the quality profile. The user is notified of each switch and can opt out by pinning a profile on the conversation.",
354
+ "defaultEnabled": false
355
+ },
356
+ {
357
+ "id": "queue-steering",
358
+ "scope": "assistant",
359
+ "key": "queue-steering",
360
+ "label": "Queue Steering",
361
+ "description": "Enable the 'Push to agent' button on queued messages, allowing users to steer the assistant to a specific queued message by aborting the current generation and promoting the message to the head of the queue.",
362
+ "defaultEnabled": false
363
+ },
364
+ {
365
+ "id": "chat-pull-to-refresh-enabled",
366
+ "scope": "client",
367
+ "key": "chat-pull-to-refresh-enabled",
368
+ "label": "Chat Pull to Refresh",
369
+ "description": "Enable pull-to-refresh gesture in the chat view.",
370
+ "defaultEnabled": false
371
+ },
372
+ {
373
+ "id": "doctor",
374
+ "scope": "client",
375
+ "key": "doctor",
376
+ "label": "Doctor",
377
+ "description": "Enable the Doctor diagnostic tab in Debug settings.",
378
+ "defaultEnabled": false
379
+ },
380
+ {
381
+ "id": "home-page",
382
+ "scope": "client",
383
+ "key": "home-page",
384
+ "label": "Home Page",
385
+ "description": "Enable the Home page as the default landing view.",
386
+ "defaultEnabled": false
387
+ },
388
+ {
389
+ "id": "platform-notifications",
390
+ "scope": "client",
391
+ "key": "platform-notifications",
392
+ "label": "Platform Notifications",
393
+ "description": "Enable the Notifications tab in settings.",
394
+ "defaultEnabled": false
395
+ },
396
+ {
397
+ "id": "rollback-enabled",
398
+ "scope": "assistant",
399
+ "key": "rollback-enabled",
400
+ "label": "Rollback Enabled",
401
+ "description": "Show older versions in the version picker, allowing rollback to previous releases.",
402
+ "defaultEnabled": false
403
+ },
404
+ {
405
+ "id": "self-hosted-assistant",
406
+ "scope": "client",
407
+ "key": "self-hosted-assistant",
408
+ "label": "Self-Hosted Assistant",
409
+ "description": "Enable self-hosted assistant configuration.",
410
+ "defaultEnabled": false
411
+ },
412
+ {
413
+ "id": "settings-sleep-policy",
414
+ "scope": "assistant",
415
+ "key": "settings-sleep-policy",
416
+ "label": "Settings Sleep Policy",
417
+ "description": "Enable sleep policy settings.",
418
+ "defaultEnabled": false
419
+ },
420
+ {
421
+ "id": "velvet",
422
+ "scope": "client",
423
+ "key": "velvet",
424
+ "label": "Velvet",
425
+ "description": "Enable the Velvet design theme.",
426
+ "defaultEnabled": false
331
427
  }
332
428
  ]
333
429
  }
@@ -81,13 +81,27 @@ export function resolveCallSiteConfig(
81
81
  type Mergeable = Record<string, unknown>;
82
82
 
83
83
  /**
84
- * Returns the resolved default profile key for a call site, accounting for
85
- * the `custom-*` user-profile fallback when the managed profile is unavailable.
84
+ * Returns the effective default profile key the resolver would actually
85
+ * select for a call site when no per-turn `overrideProfile` is supplied.
86
+ *
87
+ * Mirrors the layering in `resolveCallSiteConfig`:
88
+ * - For `mainAgent`, the workspace's `activeProfile` sits ABOVE the
89
+ * call-site catalog default (and above any static `llm.callSites.mainAgent`
90
+ * override), so a non-disabled `activeProfile` wins.
91
+ * - For other call sites, the catalog default sits ABOVE `activeProfile`,
92
+ * so the catalog default (with `custom-*` fallback) wins.
86
93
  */
87
94
  export function resolveDefaultProfileKey(
88
95
  callSite: LLMCallSite,
89
96
  llm: z.infer<typeof LLMSchema>,
90
97
  ): string | undefined {
98
+ if (callSite === "mainAgent" && llm.activeProfile != null) {
99
+ const active = llm.profiles?.[llm.activeProfile];
100
+ if (active != null && active.status !== "disabled") {
101
+ return llm.activeProfile;
102
+ }
103
+ }
104
+
91
105
  const dflt = CALL_SITE_DEFAULTS[callSite];
92
106
  if (dflt?.profile == null) return undefined;
93
107
  const target = llm.profiles?.[dflt.profile];
@@ -23,6 +23,7 @@ describe("MemoryV2ConfigSchema", () => {
23
23
  bm25_k1: 1.2,
24
24
  bm25_b: 0.4,
25
25
  consolidation_interval_hours: 4,
26
+ consolidation_max_buffer_lines: 100,
26
27
  max_page_chars: 5000,
27
28
  consolidation_prompt_path: null,
28
29
  rerank: {
@@ -36,6 +37,9 @@ describe("MemoryV2ConfigSchema", () => {
36
37
  enabled: true,
37
38
  max_page_ids: 25,
38
39
  router_prompt_path: null,
40
+ batch_size: null,
41
+ tier1_size: null,
42
+ tier2_size: null,
39
43
  },
40
44
  });
41
45
  });
@@ -300,6 +300,27 @@ const CATALOG_RECORD: CatalogRecord = {
300
300
  "Builds the personalized artifact in a background conversation with tool access.",
301
301
  domain: "agentLoop",
302
302
  },
303
+ homeGreeting: {
304
+ id: "homeGreeting",
305
+ displayName: "Home Greeting",
306
+ description:
307
+ "Generates the personalized greeting shown on the Home page in the assistant's tone/persona.",
308
+ domain: "ui",
309
+ },
310
+ homeSuggestedPrompts: {
311
+ id: "homeSuggestedPrompts",
312
+ displayName: "Home Suggested Prompts",
313
+ description:
314
+ "Generates contextual conversation-starter suggestions for the Home page.",
315
+ domain: "ui",
316
+ },
317
+ queryComplexityRouter: {
318
+ id: "queryComplexityRouter",
319
+ displayName: "Query Complexity Router",
320
+ description:
321
+ "Classifies user message complexity to route to the appropriate inference profile.",
322
+ domain: "agentLoop",
323
+ },
303
324
  };
304
325
 
305
326
  // Source of truth for call-site display metadata. API responses and usage
@@ -76,6 +76,9 @@ export const LLMCallSiteEnum = z.enum([
76
76
  "trustRuleSuggestion",
77
77
  "proactiveArtifactDecision",
78
78
  "proactiveArtifactBuild",
79
+ "homeGreeting",
80
+ "homeSuggestedPrompts",
81
+ "queryComplexityRouter",
79
82
  ]);
80
83
  export type LLMCallSite = z.infer<typeof LLMCallSiteEnum>;
81
84
 
@@ -194,6 +194,19 @@ export const MemoryV2ConfigSchema = z
194
194
  .describe(
195
195
  "Hours between scheduled consolidation runs that synthesize buffered memories into concept pages",
196
196
  ),
197
+ consolidation_max_buffer_lines: z
198
+ .number({
199
+ error: "memory.v2.consolidation_max_buffer_lines must be a number",
200
+ })
201
+ .int("memory.v2.consolidation_max_buffer_lines must be an integer")
202
+ .positive(
203
+ "memory.v2.consolidation_max_buffer_lines must be a positive integer",
204
+ )
205
+ .nullable()
206
+ .default(100)
207
+ .describe(
208
+ "Size-based trigger for consolidation. When `memory/buffer.md` reaches this many non-empty lines, consolidation runs even if the time-based interval hasn't elapsed. Defaults to 100. Set to `null` to disable the size trigger and rely solely on `consolidation_interval_hours`.",
209
+ ),
197
210
  max_page_chars: z
198
211
  .number({ error: "memory.v2.max_page_chars must be a number" })
199
212
  .int("memory.v2.max_page_chars must be an integer")
@@ -282,8 +295,42 @@ export const MemoryV2ConfigSchema = z
282
295
  .describe(
283
296
  "Optional path to a file whose contents replace the bundled router prompt. Absolute paths are used as-is, a leading `~/` is expanded to the home directory, otherwise the path is resolved under the workspace root. The loaded contents may include `{{ASSISTANT_NAME}}`, `{{USER_NAME}}`, and `{{PAGE_INDEX}}`, which are substituted at runtime. If the file is missing, unreadable, or empty, the bundled prompt is used and a warning is logged.",
284
297
  ),
298
+ batch_size: z
299
+ .number()
300
+ .int()
301
+ .min(1)
302
+ .nullable()
303
+ .default(null)
304
+ .describe(
305
+ "Target batch size for parallel page-index routing. `null` (default) sends the entire page index in one call — identical to v3 behavior. When set, pages are split into `ceil(N / batch_size)` batches by stable FNV-1a hash on slug (so adding/removing a single page only invalidates one batch's KV cache), routed in parallel, and the selected slugs are unioned. A failure in one batch does not abort the turn as long as at least one batch succeeds.",
306
+ ),
307
+ tier1_size: z
308
+ .number()
309
+ .int()
310
+ .min(1)
311
+ .nullable()
312
+ .default(null)
313
+ .describe(
314
+ "Pool size for the tier-1 'recently modified' batch. `null` (default) disables tier 1 entirely — all pages flow through tier 3 batching. When set, the top-N concept pages by file mtime become their own dedicated parallel batch with mtime-desc ordering; everything else is partitioned into tier 3 batches by `batch_size`. Synthetic entries (skills, CLI commands) have mtime=0 and naturally rank below real concept pages so they don't crowd tier 1.",
315
+ ),
316
+ tier2_size: z
317
+ .number()
318
+ .int()
319
+ .min(1)
320
+ .nullable()
321
+ .default(null)
322
+ .describe(
323
+ "Pool size for the tier-2 'useful' batch. `null` (default) disables tier 2 — pages skip straight from tier 1 to tier 3. When set, the top-M pages by injection-frequency EMA (excluding tier 1) become their own parallel batch ordered by score desc. Pages with score 0 (never selected since EMA tracking began) are ineligible for tier 2 and stay in tier 3 regardless of `tier2_size`. Score is the time-decayed sum `Σ exp(-λ(now - tᵢ))` with 3-day half-life, computed on read from `memory_v2_injection_events`.",
324
+ ),
325
+ })
326
+ .default({
327
+ enabled: true,
328
+ max_page_ids: 25,
329
+ router_prompt_path: null,
330
+ batch_size: null,
331
+ tier1_size: null,
332
+ tier2_size: null,
285
333
  })
286
- .default({ enabled: true, max_page_ids: 25, router_prompt_path: null })
287
334
  .describe(
288
335
  "LLM router configuration. When enabled, a single router LLM call replaces spreading activation for per-turn page selection.",
289
336
  ),
@@ -155,6 +155,11 @@ export interface CompactionRunResult {
155
155
  thresholdTokens: number;
156
156
  compactedMessages: number;
157
157
  compactedPersistedMessages: number;
158
+ /**
159
+ * Number of recent ("tail") messages preserved verbatim alongside the
160
+ * summary. Omitted on no-op / skipped results — defaults to 0 at render.
161
+ */
162
+ preservedTailMessages?: number;
158
163
  summaryCalls: number;
159
164
  summaryInputTokens: number;
160
165
  summaryOutputTokens: number;
@@ -327,7 +332,7 @@ export function renderImageManifest(entries: ManifestEntry[]): string {
327
332
  * runtime emitted — typically
328
333
  * `2026-04-02 (Thursday) 01:52:33 -05:00 (America/Chicago)`).
329
334
  */
330
- function extractTurnContextTimestamp(message: Message): string | null {
335
+ export function extractTurnContextTimestamp(message: Message): string | null {
331
336
  if (message.role !== "user") return null;
332
337
  for (const block of message.content) {
333
338
  if (block.type !== "text") continue;
@@ -841,6 +846,7 @@ export async function runAssistantDrivenCompaction(
841
846
  thresholdTokens,
842
847
  compactedMessages: compactableMessages.length,
843
848
  compactedPersistedMessages,
849
+ preservedTailMessages: args.messages.length - tailIndex,
844
850
  summaryCalls: 1,
845
851
  summaryInputTokens: response.usage.inputTokens,
846
852
  summaryOutputTokens: response.usage.outputTokens,
@@ -1090,6 +1096,7 @@ export async function runEmergencyCompaction(
1090
1096
  thresholdTokens,
1091
1097
  compactedMessages: compactedCount,
1092
1098
  compactedPersistedMessages: Math.max(0, compactedCount - nonPersistedAway),
1099
+ preservedTailMessages: keptTail.length,
1093
1100
  summaryCalls: 1,
1094
1101
  summaryInputTokens: response.usage.inputTokens,
1095
1102
  summaryOutputTokens: response.usage.outputTokens,
@@ -52,6 +52,24 @@ const IMAGE_MAX_PIXELS = 1_200_000;
52
52
  const IMAGE_TOKENS_PER_PIXEL = 1 / 750;
53
53
  const IMAGE_MAX_TOKENS = 1_600;
54
54
 
55
+ // Gemini prices images differently: any side ≤384px counts as a single 258-token
56
+ // tile; anything larger is resized so the longest side is ≤3072px and then
57
+ // split into 768x768 tiles at 258 tokens each. A 4000x4000 image clamps to
58
+ // 3072x3072 → ceil(3072/768)^2 = 16 tiles = 4,128 tokens. Without the clamp
59
+ // we'd over-count it as 36 tiles (~9,288 tokens) and trigger spurious
60
+ // compaction. The clamped 16-tile, 4,128-token figure is also the per-image
61
+ // ceiling we fall back to when dimensions are unparseable (e.g. HEIC/HEIF
62
+ // from iOS attachments) — the generic 1,600 cap can under-count Gemini
63
+ // images by ~2.5x.
64
+ // See: https://ai.google.dev/gemini-api/docs/tokens#multimodal-tokens
65
+ const GEMINI_IMAGE_SMALL_THRESHOLD = 384;
66
+ const GEMINI_IMAGE_TILE_SIZE = 768;
67
+ const GEMINI_IMAGE_TOKENS_PER_TILE = 258;
68
+ const GEMINI_IMAGE_MAX_DIMENSION = 3072;
69
+ const GEMINI_IMAGE_MAX_TOKENS =
70
+ Math.ceil(GEMINI_IMAGE_MAX_DIMENSION / GEMINI_IMAGE_TILE_SIZE) ** 2 *
71
+ GEMINI_IMAGE_TOKENS_PER_TILE;
72
+
55
73
  // Anthropic renders each PDF page as an image (~1,568 tokens at standard
56
74
  // resolution) plus any extracted text. Typical PDF pages are 50-150 KB.
57
75
  // Using ~100 KB/page and ~1,600 tokens/page gives ~0.016 tokens/byte.
@@ -129,16 +147,41 @@ function estimateImageTokensByDimensions(
129
147
  return Math.ceil(scaledWidth * scaledHeight * IMAGE_TOKENS_PER_PIXEL);
130
148
  }
131
149
 
150
+ function estimateGeminiImageTokens(width: number, height: number): number {
151
+ if (
152
+ width <= GEMINI_IMAGE_SMALL_THRESHOLD &&
153
+ height <= GEMINI_IMAGE_SMALL_THRESHOLD
154
+ ) {
155
+ return GEMINI_IMAGE_TOKENS_PER_TILE;
156
+ }
157
+ // Gemini resizes images so the longest side is ≤3072px before tiling.
158
+ const clampedWidth = Math.min(width, GEMINI_IMAGE_MAX_DIMENSION);
159
+ const clampedHeight = Math.min(height, GEMINI_IMAGE_MAX_DIMENSION);
160
+ const tilesWide = Math.ceil(clampedWidth / GEMINI_IMAGE_TILE_SIZE);
161
+ const tilesHigh = Math.ceil(clampedHeight / GEMINI_IMAGE_TILE_SIZE);
162
+ return tilesWide * tilesHigh * GEMINI_IMAGE_TOKENS_PER_TILE;
163
+ }
164
+
132
165
  function estimateImageTokens(
133
166
  block: Extract<ContentBlock, { type: "image" }>,
167
+ options?: TokenEstimatorOptions,
134
168
  ): number {
135
169
  const dims = parseImageDimensions(block.source.data, block.source.media_type);
136
170
  if (dims) {
171
+ if (options?.providerName === "gemini") {
172
+ return estimateGeminiImageTokens(dims.width, dims.height);
173
+ }
137
174
  return estimateImageTokensByDimensions(dims.width, dims.height);
138
175
  }
139
- // Dimensions unparseable (corrupt header, exotic format): use the per-image
140
- // cap rather than the raw base64 length, which over-counts by 30-100x for
141
- // non-Anthropic providers and trips spurious compaction.
176
+ // Dimensions unparseable (corrupt header, or formats parseImageDimensions
177
+ // doesn't recognize like HEIC/HEIF coming from iOS attachments). Fall back
178
+ // to the per-provider per-image ceiling rather than the raw base64 length,
179
+ // which over-counts by 30-100x. Gemini's tile pricing tops out well above
180
+ // the universal 1,600-token cap, so use its max-tile budget instead to
181
+ // avoid under-counting large iPhone screenshots.
182
+ if (options?.providerName === "gemini") {
183
+ return GEMINI_IMAGE_MAX_TOKENS;
184
+ }
142
185
  return IMAGE_MAX_TOKENS;
143
186
  }
144
187
 
@@ -186,7 +229,7 @@ export function estimateContentBlockTokens(
186
229
  return (
187
230
  IMAGE_BLOCK_OVERHEAD_TOKENS +
188
231
  estimateTextTokens(block.source.media_type) +
189
- estimateImageTokens(block)
232
+ estimateImageTokens(block, options)
190
233
  );
191
234
  case "file":
192
235
  return (
@@ -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");
@@ -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
  });