@vellumai/assistant 0.8.1 → 0.8.2

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 (506) hide show
  1. package/ARCHITECTURE.md +2 -7
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +5 -0
  5. package/docker-init-apt-root.sh +94 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +325 -3
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  21. package/src/__tests__/anthropic-provider.test.ts +45 -0
  22. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  23. package/src/__tests__/app-executors.test.ts +220 -4
  24. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  25. package/src/__tests__/bundled-asset.test.ts +6 -6
  26. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  27. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  28. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  29. package/src/__tests__/clawhub.test.ts +75 -16
  30. package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
  31. package/src/__tests__/config-schema.test.ts +21 -0
  32. package/src/__tests__/config-set-route.test.ts +80 -0
  33. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  34. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  35. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  38. package/src/__tests__/context-token-estimator.test.ts +1 -0
  39. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  42. package/src/__tests__/conversation-agent-loop.test.ts +2 -0
  43. package/src/__tests__/conversation-error.test.ts +42 -3
  44. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  45. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  46. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  47. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  48. package/src/__tests__/conversation-pairing.test.ts +54 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-queue.test.ts +4 -1
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
  53. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  54. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  55. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  56. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  59. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  60. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  61. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  62. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  63. package/src/__tests__/dm-backfill.test.ts +121 -10
  64. package/src/__tests__/document-tool-security.test.ts +258 -0
  65. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  66. package/src/__tests__/edit-propagation.test.ts +33 -0
  67. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  68. package/src/__tests__/external-plugin-loader.test.ts +60 -36
  69. package/src/__tests__/filing-service.test.ts +140 -0
  70. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  71. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  72. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  73. package/src/__tests__/helpers/wait-for.ts +21 -0
  74. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  75. package/src/__tests__/history-repair.test.ts +73 -0
  76. package/src/__tests__/host-app-control-proxy.test.ts +266 -10
  77. package/src/__tests__/image-credentials.test.ts +1 -1
  78. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  79. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  80. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  81. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  82. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  83. package/src/__tests__/injector-chain.test.ts +10 -8
  84. package/src/__tests__/install-skill-routing.test.ts +155 -37
  85. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
  86. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  87. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  88. package/src/__tests__/llm-catalog-parity.test.ts +55 -13
  89. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
  90. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  91. package/src/__tests__/llm-usage-store.test.ts +114 -0
  92. package/src/__tests__/managed-profile-guard.test.ts +31 -29
  93. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  94. package/src/__tests__/managed-store.test.ts +84 -192
  95. package/src/__tests__/media-generate-image.test.ts +1 -1
  96. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  97. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  98. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  99. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  100. package/src/__tests__/openai-provider.test.ts +24 -0
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  102. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  103. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  104. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
  105. package/src/__tests__/platform.test.ts +2 -0
  106. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  107. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  108. package/src/__tests__/plugin-external-api.test.ts +68 -0
  109. package/src/__tests__/plugin-registry.test.ts +0 -77
  110. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  111. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  112. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  113. package/src/__tests__/plugin-types.test.ts +3 -13
  114. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  115. package/src/__tests__/process-message-display-content.test.ts +421 -0
  116. package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
  117. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  118. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
  119. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  120. package/src/__tests__/schedule-routes.test.ts +50 -3
  121. package/src/__tests__/schedule-store.test.ts +94 -0
  122. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  123. package/src/__tests__/schema-transforms.test.ts +20 -0
  124. package/src/__tests__/search-skills-unified.test.ts +0 -5
  125. package/src/__tests__/server-history-render.test.ts +43 -0
  126. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  127. package/src/__tests__/skill-load-tool.test.ts +27 -89
  128. package/src/__tests__/skill-memory.test.ts +23 -3
  129. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  130. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  131. package/src/__tests__/skills-install-extract.test.ts +49 -38
  132. package/src/__tests__/skills-install-staging.test.ts +159 -0
  133. package/src/__tests__/skills-uninstall.test.ts +9 -41
  134. package/src/__tests__/skills.test.ts +51 -58
  135. package/src/__tests__/slack-channel-config.test.ts +9 -0
  136. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  137. package/src/__tests__/system-prompt.test.ts +737 -63
  138. package/src/__tests__/terminal-tools.test.ts +28 -1
  139. package/src/__tests__/thread-backfill.test.ts +557 -27
  140. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  141. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  142. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  143. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  145. package/src/__tests__/tool-executor.test.ts +16 -4
  146. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  147. package/src/__tests__/turn-events-store.test.ts +256 -0
  148. package/src/__tests__/twilio-routes.test.ts +4 -0
  149. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  150. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  151. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  152. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  153. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  154. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  155. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  156. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  157. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  158. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  159. package/src/acp/resolve-agent.ts +1 -1
  160. package/src/agent/image-optimize.ts +13 -5
  161. package/src/calls/voice-session-bridge.ts +61 -42
  162. package/src/channels/types.ts +108 -0
  163. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  164. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  165. package/src/cli/commands/__tests__/schedules.test.ts +491 -0
  166. package/src/cli/commands/changelog.ts +106 -42
  167. package/src/cli/commands/conversations.ts +102 -17
  168. package/src/cli/commands/default-action.ts +10 -53
  169. package/src/cli/commands/notifications.ts +329 -317
  170. package/src/cli/commands/plugins.ts +185 -0
  171. package/src/cli/commands/schedules.ts +391 -0
  172. package/src/cli/commands/telemetry.ts +40 -0
  173. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  174. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  177. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  178. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  179. package/src/cli/lib/cli-colors.ts +12 -0
  180. package/src/cli/lib/confirm-prompt.ts +79 -0
  181. package/src/cli/lib/install-from-github.ts +304 -0
  182. package/src/cli/lib/list-installed-plugins.ts +137 -0
  183. package/src/cli/lib/uninstall-plugin.ts +82 -0
  184. package/src/cli/lib/unknown-command.ts +111 -0
  185. package/src/cli/program.ts +38 -2
  186. package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
  187. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  188. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  189. package/src/config/bundled-skills/document/SKILL.md +23 -3
  190. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  191. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  192. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  193. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  194. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  195. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  196. package/src/config/bundled-tool-registry.ts +6 -0
  197. package/src/config/feature-flag-registry.json +41 -1
  198. package/src/config/loader.ts +64 -38
  199. package/src/config/schema.ts +7 -10
  200. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  201. package/src/config/schemas/channels.ts +8 -0
  202. package/src/config/schemas/compaction.ts +28 -0
  203. package/src/config/schemas/heartbeat.ts +9 -0
  204. package/src/config/schemas/llm-request-logs.ts +31 -7
  205. package/src/config/schemas/llm.ts +3 -0
  206. package/src/config/schemas/memory-retrieval.ts +18 -0
  207. package/src/config/schemas/tools.ts +14 -0
  208. package/src/config/skills.ts +3 -96
  209. package/src/context/compactor.ts +1047 -0
  210. package/src/context/token-estimator.ts +2 -2
  211. package/src/context/window-manager.ts +197 -1520
  212. package/src/credential-execution/managed-catalog.ts +37 -0
  213. package/src/credential-health/credential-health-service.ts +280 -19
  214. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
  215. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  216. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  217. package/src/daemon/approval-generators.ts +8 -6
  218. package/src/daemon/config-watcher.ts +94 -31
  219. package/src/daemon/conversation-agent-loop.ts +169 -9
  220. package/src/daemon/conversation-error.ts +171 -37
  221. package/src/daemon/conversation-lifecycle.ts +53 -40
  222. package/src/daemon/conversation-messaging.ts +25 -6
  223. package/src/daemon/conversation-process.ts +49 -12
  224. package/src/daemon/conversation-runtime-assembly.ts +16 -1
  225. package/src/daemon/conversation-slash.ts +12 -5
  226. package/src/daemon/conversation-store.ts +11 -4
  227. package/src/daemon/conversation-tool-setup.ts +39 -7
  228. package/src/daemon/conversation.ts +33 -1
  229. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  230. package/src/daemon/first-greeting.ts +22 -2
  231. package/src/daemon/handlers/config-model.ts +6 -5
  232. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  233. package/src/daemon/handlers/shared.ts +14 -5
  234. package/src/daemon/handlers/skills.ts +111 -108
  235. package/src/daemon/history-repair.ts +28 -1
  236. package/src/daemon/host-app-control-proxy.ts +98 -23
  237. package/src/daemon/lifecycle.ts +45 -35
  238. package/src/daemon/meet-host-supervisor.ts +5 -4
  239. package/src/daemon/memory-v2-startup.ts +49 -0
  240. package/src/daemon/message-protocol.ts +1 -0
  241. package/src/daemon/message-types/conversations.ts +25 -0
  242. package/src/daemon/message-types/messages.ts +61 -0
  243. package/src/daemon/message-types/subagents.ts +1 -0
  244. package/src/daemon/message-types/sync.ts +1 -0
  245. package/src/daemon/pkb-reminder-builder.test.ts +1 -1
  246. package/src/daemon/pkb-reminder-builder.ts +1 -1
  247. package/src/daemon/plugin-source-watcher.ts +146 -0
  248. package/src/daemon/process-message.ts +21 -3
  249. package/src/daemon/server.ts +11 -2
  250. package/src/daemon/skill-memory-refresh.ts +29 -0
  251. package/src/documents/document-store.ts +221 -3
  252. package/src/embedded/plugin-api.ts +40 -0
  253. package/src/filing/filing-service.ts +39 -0
  254. package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
  255. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  256. package/src/heartbeat/heartbeat-service.ts +41 -0
  257. package/src/home/__tests__/feed-types.test.ts +40 -0
  258. package/src/home/feed-types.ts +22 -0
  259. package/src/home/post-connect-feed.ts +1 -0
  260. package/src/index.ts +18 -1
  261. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  262. package/src/mcp/client.ts +20 -4
  263. package/src/media/image-credentials.ts +3 -3
  264. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  265. package/src/memory/__tests__/conversation-queries.test.ts +263 -0
  266. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  267. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  268. package/src/memory/__tests__/message-content.test.ts +35 -0
  269. package/src/memory/bookmark-crud.ts +42 -10
  270. package/src/memory/context-search/sources/conversations.ts +62 -2
  271. package/src/memory/context-search/sources/workspace.ts +4 -0
  272. package/src/memory/conversation-crud.ts +63 -19
  273. package/src/memory/conversation-queries.ts +110 -10
  274. package/src/memory/db-init.ts +6 -0
  275. package/src/memory/delivery-crud.ts +152 -5
  276. package/src/memory/embedding-backend.ts +4 -4
  277. package/src/memory/external-conversation-store.ts +66 -5
  278. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
  279. package/src/memory/graph/conversation-graph-memory.ts +31 -15
  280. package/src/memory/graph/tools.ts +3 -3
  281. package/src/memory/indexer.ts +34 -29
  282. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  283. package/src/memory/jobs/embed-concept-page.ts +20 -11
  284. package/src/memory/jobs-worker.ts +6 -1
  285. package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
  286. package/src/memory/llm-request-log-source.ts +19 -52
  287. package/src/memory/llm-usage-store.ts +125 -5
  288. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  289. package/src/memory/message-content.ts +1 -1
  290. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  291. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  292. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  293. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  294. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  295. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  296. package/src/memory/migrations/index.ts +6 -0
  297. package/src/memory/migrations/registry.ts +8 -0
  298. package/src/memory/onboarding-events-store.ts +106 -0
  299. package/src/memory/schema/bookmarks.ts +0 -2
  300. package/src/memory/schema/calls.ts +1 -0
  301. package/src/memory/schema/inference.ts +1 -3
  302. package/src/memory/schema/infrastructure.ts +12 -0
  303. package/src/memory/turn-events-store.ts +127 -2
  304. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  305. package/src/memory/v2/__tests__/injection.test.ts +98 -8
  306. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  307. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  308. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  309. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  310. package/src/memory/v2/__tests__/router.test.ts +15 -0
  311. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  312. package/src/memory/v2/injection.ts +32 -6
  313. package/src/memory/v2/migration.ts +49 -19
  314. package/src/memory/v2/page-index.ts +35 -5
  315. package/src/memory/v2/prompts/router.ts +11 -8
  316. package/src/memory/v2/prompts/sweep.ts +2 -2
  317. package/src/memory/v2/qdrant.ts +135 -7
  318. package/src/memory/v2/router.ts +9 -8
  319. package/src/memory/v2/skill-store.ts +120 -35
  320. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  321. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  322. package/src/messaging/providers/slack/adapter.ts +43 -5
  323. package/src/messaging/providers/slack/client.ts +27 -0
  324. package/src/messaging/providers/slack/deep-link.ts +65 -0
  325. package/src/messaging/providers/slack/download.ts +104 -0
  326. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  327. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  328. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  329. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  330. package/src/messaging/providers/slack/types.ts +20 -1
  331. package/src/notifications/conversation-pairing.ts +2 -1
  332. package/src/notifications/decision-engine.ts +2 -1
  333. package/src/notifications/emit-signal.ts +20 -1
  334. package/src/notifications/home-feed-side-effect.ts +54 -0
  335. package/src/notifications/signal.ts +3 -1
  336. package/src/oauth/connection-resolver.ts +8 -4
  337. package/src/oauth/platform-connection.ts +6 -2
  338. package/src/oauth/seed-providers.ts +10 -1
  339. package/src/permissions/checker.ts +2 -0
  340. package/src/permissions/ipc-risk-types.ts +1 -0
  341. package/src/permissions/question-prompter.test.ts +416 -0
  342. package/src/permissions/question-prompter.ts +294 -0
  343. package/src/platform/client.test.ts +1 -1
  344. package/src/platform/client.ts +1 -1
  345. package/src/plugin-api/constants.ts +26 -0
  346. package/src/plugin-api/index.ts +34 -1
  347. package/src/plugin-api/types.ts +104 -22
  348. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  349. package/src/plugins/defaults/compaction.ts +0 -4
  350. package/src/plugins/defaults/empty-response.ts +0 -2
  351. package/src/plugins/defaults/history-repair.ts +0 -2
  352. package/src/plugins/defaults/injectors.ts +36 -3
  353. package/src/plugins/defaults/llm-call.ts +0 -2
  354. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  355. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  356. package/src/plugins/defaults/persistence.ts +0 -2
  357. package/src/plugins/defaults/title-generate.ts +0 -5
  358. package/src/plugins/defaults/token-estimate.ts +0 -2
  359. package/src/plugins/defaults/tool-error.ts +0 -7
  360. package/src/plugins/defaults/tool-execute.ts +0 -2
  361. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  362. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  363. package/src/plugins/external-api.ts +104 -0
  364. package/src/plugins/external-plugin-loader.ts +105 -32
  365. package/src/plugins/feature-gate.ts +22 -0
  366. package/src/plugins/pipeline.ts +37 -0
  367. package/src/plugins/registry.ts +48 -80
  368. package/src/plugins/types.ts +31 -26
  369. package/src/plugins/user-loader.ts +21 -2
  370. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  371. package/src/proactive-artifact/job.test.ts +37 -5
  372. package/src/prompts/__tests__/system-prompt.test.ts +12 -0
  373. package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
  374. package/src/prompts/normalize-onboarding.ts +27 -0
  375. package/src/prompts/sections.ts +302 -0
  376. package/src/prompts/system-prompt.ts +63 -166
  377. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  378. package/src/prompts/templates/system-sections.ts +173 -0
  379. package/src/providers/__tests__/inference.test.ts +22 -7
  380. package/src/providers/anthropic/client.ts +28 -28
  381. package/src/providers/connection-resolution.ts +7 -0
  382. package/src/providers/inference/adapter-factory.ts +41 -4
  383. package/src/providers/inference/connections.ts +74 -29
  384. package/src/providers/inference/resolve-auth.ts +12 -4
  385. package/src/providers/model-catalog.ts +294 -12
  386. package/src/providers/openai/chat-completions-provider.ts +10 -2
  387. package/src/providers/openrouter/client.ts +7 -0
  388. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
  389. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  390. package/src/providers/provider-availability.ts +17 -2
  391. package/src/providers/provider-catalog-visibility.ts +36 -0
  392. package/src/providers/registry.ts +22 -14
  393. package/src/providers/retry.ts +47 -1
  394. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  395. package/src/runtime/agent-wake.ts +42 -14
  396. package/src/runtime/auth/route-policy.ts +8 -1
  397. package/src/runtime/btw-sidechain.ts +2 -0
  398. package/src/runtime/http-types.ts +19 -0
  399. package/src/runtime/migrations/origin-mode.ts +1 -1
  400. package/src/runtime/pending-interactions.ts +1 -0
  401. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  402. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  403. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
  404. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  405. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  406. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  407. package/src/runtime/routes/acp-routes.ts +5 -3
  408. package/src/runtime/routes/auth-routes.ts +1 -1
  409. package/src/runtime/routes/bookmark-routes.ts +5 -3
  410. package/src/runtime/routes/btw-routes.ts +5 -1
  411. package/src/runtime/routes/channel-availability-routes.ts +121 -0
  412. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  413. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  414. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  415. package/src/runtime/routes/conversation-query-routes.ts +40 -35
  416. package/src/runtime/routes/conversation-routes.ts +90 -11
  417. package/src/runtime/routes/documents-routes.ts +25 -86
  418. package/src/runtime/routes/group-routes.ts +5 -0
  419. package/src/runtime/routes/inbound-conversation.ts +28 -8
  420. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  421. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  422. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  423. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  424. package/src/runtime/routes/index.ts +6 -0
  425. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  426. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  427. package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
  428. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  429. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  430. package/src/runtime/routes/integrations/twilio.ts +6 -13
  431. package/src/runtime/routes/notification-routes.ts +1 -1
  432. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  433. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  434. package/src/runtime/routes/question-routes.ts +259 -0
  435. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  436. package/src/runtime/routes/schedule-routes.ts +4 -7
  437. package/src/runtime/routes/subagents-routes.ts +57 -18
  438. package/src/runtime/routes/telemetry-routes.ts +27 -0
  439. package/src/runtime/routes/tts-routes.ts +27 -2
  440. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  441. package/src/runtime/routes/workspace-routes.ts +28 -0
  442. package/src/runtime/services/conversation-serializer.ts +39 -7
  443. package/src/runtime/sync/resource-sync-events.ts +93 -1
  444. package/src/schedule/schedule-store.ts +27 -2
  445. package/src/schedule/scheduler.ts +9 -1
  446. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  447. package/src/security/untrusted-content.ts +93 -8
  448. package/src/skills/catalog-files.ts +1 -1
  449. package/src/skills/catalog-install.ts +233 -116
  450. package/src/skills/clawhub.ts +70 -13
  451. package/src/skills/managed-store.ts +4 -119
  452. package/src/skills/skillssh-registry.ts +27 -48
  453. package/src/subagent/manager.ts +15 -7
  454. package/src/telemetry/types.ts +113 -1
  455. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  456. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  457. package/src/tools/apps/executors.ts +58 -7
  458. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  459. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  460. package/src/tools/browser/browser-execution.ts +15 -11
  461. package/src/tools/computer-use/definitions.ts +3 -3
  462. package/src/tools/credentials/vault.ts +1 -1
  463. package/src/tools/document/document-tool.ts +124 -1
  464. package/src/tools/filesystem/edit.ts +1 -1
  465. package/src/tools/filesystem/list.ts +1 -1
  466. package/src/tools/filesystem/read.ts +1 -1
  467. package/src/tools/filesystem/write.ts +5 -2
  468. package/src/tools/host-filesystem/transfer.ts +1 -1
  469. package/src/tools/host-terminal/host-shell.ts +1 -1
  470. package/src/tools/permission-checker.ts +1 -1
  471. package/src/tools/registry.ts +17 -7
  472. package/src/tools/schedule/create.ts +2 -2
  473. package/src/tools/schema-transforms.ts +7 -2
  474. package/src/tools/side-effects.ts +1 -0
  475. package/src/tools/skills/delete-managed.ts +4 -4
  476. package/src/tools/skills/execute.ts +1 -1
  477. package/src/tools/skills/scaffold-managed.ts +3 -2
  478. package/src/tools/subagent/notify-parent.ts +1 -1
  479. package/src/tools/system/request-permission.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +60 -1
  481. package/src/tools/tool-manifest.ts +2 -0
  482. package/src/tools/types.ts +72 -21
  483. package/src/tools/ui-surface/definitions.ts +6 -5
  484. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  485. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  486. package/src/types/onboarding-context.ts +2 -0
  487. package/src/util/errors.ts +17 -0
  488. package/src/util/platform.ts +10 -0
  489. package/src/watcher/__tests__/engine.test.ts +22 -0
  490. package/src/watcher/engine.ts +6 -2
  491. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  492. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  493. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  494. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  495. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  496. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  497. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  498. package/src/workspace/migrations/registry.ts +8 -0
  499. package/src/workspace/migrations/runner.ts +39 -9
  500. package/src/workspace/migrations/types.ts +4 -0
  501. package/examples/plugins/echo/bun.lock +0 -25
  502. package/src/__tests__/context-window-manager.test.ts +0 -2481
  503. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  504. package/src/context/prompts/compact.md +0 -26
  505. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  506. /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
@@ -67,6 +67,43 @@ export interface FetchManagedCatalogResult {
67
67
  error?: string;
68
68
  }
69
69
 
70
+ // ---------------------------------------------------------------------------
71
+ // Managed connection cache — provides a synchronous view of platform-managed
72
+ // connections for use in the system prompt (which is built synchronously).
73
+ // Refresh is triggered at daemon startup and periodically via setInterval.
74
+ // ---------------------------------------------------------------------------
75
+
76
+ export interface CachedManagedConnection {
77
+ provider: string;
78
+ accountInfo: string | null;
79
+ }
80
+
81
+ let cachedConnections: CachedManagedConnection[] = [];
82
+
83
+ /**
84
+ * Return the last successfully fetched managed connections.
85
+ * Returns an empty array before the first successful refresh.
86
+ */
87
+ export function getCachedManagedConnections(): CachedManagedConnection[] {
88
+ return cachedConnections;
89
+ }
90
+
91
+ /**
92
+ * Fetch managed connections from the platform and update the in-memory cache.
93
+ * Best-effort: errors are logged and the cache retains its previous value.
94
+ */
95
+ export async function refreshManagedConnectionCache(): Promise<void> {
96
+ const result = await fetchManagedCatalog();
97
+ if (result.ok) {
98
+ cachedConnections = result.descriptors
99
+ .filter((d) => d.status === "active" || d.status === "ACTIVE")
100
+ .map((d) => ({
101
+ provider: d.provider,
102
+ accountInfo: d.accountInfo,
103
+ }));
104
+ }
105
+ }
106
+
70
107
  /**
71
108
  * Fetch the managed credential catalog from the platform.
72
109
  *
@@ -13,11 +13,14 @@
13
13
 
14
14
  import { isTokenExpired } from "@vellumai/credential-storage";
15
15
 
16
+ import type { Services } from "../config/schemas/services.js";
16
17
  import { getConnectionAccessTokenResult } from "../oauth/credential-token-resolver.js";
17
18
  import {
18
19
  getProvider,
19
20
  listActiveConnectionsByProvider,
20
21
  listProviders,
22
+ type OAuthConnectionRow,
23
+ type OAuthProviderRow,
21
24
  } from "../oauth/oauth-store.js";
22
25
  import { getLogger } from "../util/logger.js";
23
26
 
@@ -271,6 +274,207 @@ async function checkConnection(
271
274
  };
272
275
  }
273
276
 
277
+ // ── Managed provider checks ──────────────────────────────────────────
278
+
279
+ /**
280
+ * Check whether a provider is configured in managed mode.
281
+ * Uses dynamic imports to avoid circular dependencies (same pattern as
282
+ * `integration-status.ts`).
283
+ */
284
+ async function isManagedProvider(
285
+ providerRow: OAuthProviderRow,
286
+ ): Promise<boolean> {
287
+ const managedKey = providerRow.managedServiceConfigKey;
288
+ if (!managedKey) return false;
289
+
290
+ try {
291
+ const { ServicesSchema, getServiceMode } =
292
+ await import("../config/schemas/services.js");
293
+ if (!(managedKey in ServicesSchema.shape)) return false;
294
+
295
+ const { getConfig } = await import("../config/loader.js");
296
+ const services: Services = getConfig().services;
297
+ return getServiceMode(services, managedKey as keyof Services) === "managed";
298
+ } catch {
299
+ return false;
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Fetch active managed connections from the platform and ping each one.
305
+ * Returns health results for managed connections, or an empty array if
306
+ * the platform is unreachable or the provider is not managed.
307
+ */
308
+ async function checkManagedProvider(
309
+ providerRow: OAuthProviderRow,
310
+ ): Promise<CredentialHealthResult[]> {
311
+ const results: CredentialHealthResult[] = [];
312
+
313
+ try {
314
+ const { VellumPlatformClient } = await import("../platform/client.js");
315
+ const client = await VellumPlatformClient.create();
316
+ if (!client?.platformAssistantId) return results;
317
+
318
+ const params = new URLSearchParams();
319
+ params.set("provider", providerRow.provider);
320
+ params.set("status", "ACTIVE");
321
+
322
+ const path = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/connections/?${params.toString()}`;
323
+ const response = await client.fetch(path);
324
+
325
+ if (!response.ok) {
326
+ log.warn(
327
+ { status: response.status, provider: providerRow.provider },
328
+ "Failed to list managed connections for health check",
329
+ );
330
+ return results;
331
+ }
332
+
333
+ const body = (await response.json()) as unknown;
334
+ const connections = (
335
+ Array.isArray(body)
336
+ ? body
337
+ : ((body as Record<string, unknown>).results ?? [])
338
+ ) as Array<{ id: string; account_label?: string }>;
339
+
340
+ if (connections.length === 0) {
341
+ // No active managed connections — report as missing so the
342
+ // heartbeat can notify the user.
343
+ results.push({
344
+ connectionId: `managed:${providerRow.provider}`,
345
+ provider: providerRow.provider,
346
+ accountInfo: null,
347
+ status: "missing_token",
348
+ details: `No active managed connection for ${providerRow.provider}. Reconnect on the Vellum platform.`,
349
+ missingScopes: [],
350
+ canAutoRecover: false,
351
+ });
352
+ return results;
353
+ }
354
+
355
+ // Ping each managed connection via the platform proxy
356
+ for (const conn of connections) {
357
+ const base: Omit<
358
+ CredentialHealthResult,
359
+ "status" | "details" | "canAutoRecover"
360
+ > = {
361
+ connectionId: conn.id,
362
+ provider: providerRow.provider,
363
+ accountInfo: conn.account_label ?? null,
364
+ missingScopes: [],
365
+ };
366
+
367
+ if (!providerRow.pingUrl) {
368
+ // No ping URL configured — assume healthy if connection exists
369
+ results.push({
370
+ ...base,
371
+ status: "healthy",
372
+ details: `${providerRow.provider} managed connection is active (no ping URL configured).`,
373
+ canAutoRecover: true,
374
+ });
375
+ continue;
376
+ }
377
+
378
+ // Ping via platform proxy
379
+ try {
380
+ const { PlatformOAuthConnection } =
381
+ await import("../oauth/platform-connection.js");
382
+ const platformConn = new PlatformOAuthConnection({
383
+ id: conn.id,
384
+ provider: providerRow.provider,
385
+ externalId: providerRow.provider,
386
+ accountInfo: conn.account_label ?? null,
387
+ client,
388
+ connectionId: conn.id,
389
+ baseUrl: undefined,
390
+ });
391
+
392
+ // Decompose the absolute pingUrl into base URL + relative path.
393
+ // OAuthConnectionRequest.path is documented as relative, but
394
+ // provider definitions store full absolute URLs.
395
+ const parsedPingUrl = new URL(providerRow.pingUrl);
396
+ const pingBaseUrl = `${parsedPingUrl.protocol}//${parsedPingUrl.host}`;
397
+ const pingPath = parsedPingUrl.pathname + parsedPingUrl.search;
398
+
399
+ const parsedHeaders = safeJsonParse<Record<string, string>>(
400
+ providerRow.pingHeaders,
401
+ {},
402
+ );
403
+ const parsedBody = safeJsonParse<unknown>(providerRow.pingBody, null);
404
+
405
+ const pingResp = await platformConn.request({
406
+ method: providerRow.pingMethod ?? "GET",
407
+ path: pingPath,
408
+ baseUrl: pingBaseUrl,
409
+ ...(Object.keys(parsedHeaders).length > 0
410
+ ? { headers: parsedHeaders }
411
+ : {}),
412
+ ...(parsedBody != null ? { body: parsedBody } : {}),
413
+ signal: AbortSignal.timeout(PING_TIMEOUT_MS),
414
+ });
415
+
416
+ if (pingResp.status >= 200 && pingResp.status < 300) {
417
+ results.push({
418
+ ...base,
419
+ status: "healthy",
420
+ details: `${providerRow.provider} managed credential is healthy.`,
421
+ canAutoRecover: true,
422
+ });
423
+ } else if (pingResp.status === 401 || pingResp.status === 403) {
424
+ results.push({
425
+ ...base,
426
+ status: "revoked",
427
+ details: `${providerRow.provider} managed token was rejected (${pingResp.status}). Reconnect on the Vellum platform.`,
428
+ canAutoRecover: false,
429
+ });
430
+ } else {
431
+ results.push({
432
+ ...base,
433
+ status: "ping_failed",
434
+ details: `${providerRow.provider} managed liveness check returned ${pingResp.status}.`,
435
+ canAutoRecover: false,
436
+ });
437
+ }
438
+ } catch (err) {
439
+ const msg = err instanceof Error ? err.message : String(err);
440
+ // CredentialRequiredError means the platform can't materialize
441
+ // the token — treat as revoked.
442
+ if (
443
+ err &&
444
+ typeof err === "object" &&
445
+ "name" in err &&
446
+ (err as { name: string }).name === "CredentialRequiredError"
447
+ ) {
448
+ results.push({
449
+ ...base,
450
+ status: "revoked",
451
+ details: `${providerRow.provider} managed connection is no longer valid. Reconnect on the Vellum platform.`,
452
+ canAutoRecover: false,
453
+ });
454
+ } else {
455
+ log.debug(
456
+ { provider: providerRow.provider, connectionId: conn.id, err: msg },
457
+ "Managed credential ping failed",
458
+ );
459
+ results.push({
460
+ ...base,
461
+ status: "ping_failed",
462
+ details: `${providerRow.provider} managed liveness check failed: ${msg}`,
463
+ canAutoRecover: false,
464
+ });
465
+ }
466
+ }
467
+ }
468
+ } catch (err) {
469
+ log.warn(
470
+ { err, provider: providerRow.provider },
471
+ "Failed to check managed provider health",
472
+ );
473
+ }
474
+
475
+ return results;
476
+ }
477
+
274
478
  // ── Public API ────────────────────────────────────────────────────────
275
479
 
276
480
  /**
@@ -279,6 +483,9 @@ async function checkConnection(
279
483
  * Iterates every registered provider, looks up active connections, and
280
484
  * validates each one. Returns a structured report with overall results
281
485
  * and a filtered list of unhealthy credentials.
486
+ *
487
+ * Checks both BYO (local SQLite) and managed (platform-hosted)
488
+ * connections.
282
489
  */
283
490
  export async function checkAllCredentials(): Promise<CredentialHealthReport> {
284
491
  const checkedAt = Date.now();
@@ -292,6 +499,10 @@ export async function checkAllCredentials(): Promise<CredentialHealthReport> {
292
499
  return { checkedAt, results, unhealthy: [] };
293
500
  }
294
501
 
502
+ // Track which providers have BYO connections so we skip the managed
503
+ // check for them (they're already covered by the BYO path).
504
+ const byoProviders = new Set<string>();
505
+
295
506
  for (const providerRow of providers) {
296
507
  let connections;
297
508
  try {
@@ -304,6 +515,10 @@ export async function checkAllCredentials(): Promise<CredentialHealthReport> {
304
515
  continue;
305
516
  }
306
517
 
518
+ if (connections.length > 0) {
519
+ byoProviders.add(providerRow.provider);
520
+ }
521
+
307
522
  for (const conn of connections) {
308
523
  try {
309
524
  const result = await checkConnection({
@@ -329,6 +544,36 @@ export async function checkAllCredentials(): Promise<CredentialHealthReport> {
329
544
  }
330
545
  }
331
546
 
547
+ // Check managed connections. If a provider is currently in managed mode,
548
+ // evaluate it via the managed path even if stale BYO rows exist — the
549
+ // user may have switched from BYO to managed.
550
+ for (const providerRow of providers) {
551
+ if (!(await isManagedProvider(providerRow))) continue;
552
+
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)) {
556
+ const beforeLen = results.length;
557
+ const filtered = results.filter(
558
+ (r) => r.provider !== providerRow.provider,
559
+ );
560
+ if (filtered.length !== beforeLen) {
561
+ results.length = 0;
562
+ results.push(...filtered);
563
+ }
564
+ }
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
+ }
575
+ }
576
+
332
577
  const unhealthy = results.filter((r) => r.status !== "healthy");
333
578
  if (unhealthy.length > 0) {
334
579
  log.info(
@@ -351,34 +596,50 @@ export async function checkAllCredentials(): Promise<CredentialHealthReport> {
351
596
  * result for the most recent active connection, or null if no connection
352
597
  * exists.
353
598
  *
599
+ * Checks BYO connections first; if none exist, falls back to checking
600
+ * managed connections on the platform.
601
+ *
354
602
  * Used by the watcher engine for pre-poll gating.
355
603
  */
356
604
  export async function checkCredentialForProvider(
357
605
  provider: string,
358
606
  ): Promise<CredentialHealthResult | null> {
359
- let connections;
607
+ const providerRow = getProvider(provider);
608
+ if (!providerRow) return null;
609
+
610
+ // Check managed mode first — if the provider is currently configured for
611
+ // managed mode, evaluate via the platform regardless of stale BYO rows.
612
+ if (await isManagedProvider(providerRow)) {
613
+ const managedResults = await checkManagedProvider(providerRow);
614
+ if (managedResults.length > 0) return managedResults[0]!;
615
+ return null;
616
+ }
617
+
618
+ // Fall back to BYO (local) connection check.
619
+ let connections: OAuthConnectionRow[];
360
620
  try {
361
621
  connections = listActiveConnectionsByProvider(provider);
362
622
  } catch {
363
- return null;
623
+ connections = [];
364
624
  }
365
- if (connections.length === 0) return null;
366
625
 
367
- const conn = connections[0]!;
368
- const providerRow = getProvider(conn.provider);
369
- if (!providerRow) return null;
626
+ if (connections.length > 0) {
627
+ const conn = connections[0]!;
628
+
629
+ return checkConnection({
630
+ connectionId: conn.id,
631
+ provider: conn.provider,
632
+ accountInfo: conn.accountInfo,
633
+ expiresAt: conn.expiresAt,
634
+ hasRefreshToken: !!conn.hasRefreshToken,
635
+ grantedScopesRaw: conn.grantedScopes,
636
+ defaultScopesRaw: providerRow.defaultScopes,
637
+ pingUrl: providerRow.pingUrl,
638
+ pingMethod: providerRow.pingMethod,
639
+ pingHeaders: providerRow.pingHeaders,
640
+ pingBody: providerRow.pingBody,
641
+ });
642
+ }
370
643
 
371
- return checkConnection({
372
- connectionId: conn.id,
373
- provider: conn.provider,
374
- accountInfo: conn.accountInfo,
375
- expiresAt: conn.expiresAt,
376
- hasRefreshToken: !!conn.hasRefreshToken,
377
- grantedScopesRaw: conn.grantedScopes,
378
- defaultScopesRaw: providerRow.defaultScopes,
379
- pingUrl: providerRow.pingUrl,
380
- pingMethod: providerRow.pingMethod,
381
- pingHeaders: providerRow.pingHeaders,
382
- pingBody: providerRow.pingBody,
383
- });
644
+ return null;
384
645
  }
@@ -62,8 +62,11 @@ const autoAnalysisConversations = new Set<string>();
62
62
  // flip this to true.
63
63
  let v2Enabled = false;
64
64
 
65
+ const realLoader = await import("../../config/loader.js");
65
66
  mock.module("../../config/loader.js", () => ({
67
+ ...realLoader,
66
68
  getConfig: () => ({ memory: { v2: { enabled: v2Enabled } } }),
69
+ loadConfig: () => ({ memory: { v2: { enabled: v2Enabled } } }),
67
70
  }));
68
71
 
69
72
  mock.module("../../memory/auto-analysis-guard.js", () => ({
@@ -81,7 +84,10 @@ mock.module("../../memory/jobs-store.js", () => ({
81
84
  },
82
85
  }));
83
86
 
87
+ const realAutoAnalysisEnqueue =
88
+ await import("../../memory/auto-analysis-enqueue.js");
84
89
  mock.module("../../memory/auto-analysis-enqueue.js", () => ({
90
+ ...realAutoAnalysisEnqueue,
85
91
  enqueueAutoAnalysisIfEnabled: (args: {
86
92
  conversationId: string;
87
93
  trigger: "batch" | "idle" | "lifecycle";
@@ -114,15 +120,23 @@ mock.module("../../memory/memory-retrospective-enqueue.js", () => ({
114
120
 
115
121
  // Stub all side-effecting cleanup helpers that disposeConversation chains
116
122
  // into after the enqueue block. We assert on enqueue behavior only.
123
+ const realBrowserScreencast =
124
+ await import("../../tools/browser/browser-screencast.js");
117
125
  mock.module("../../tools/browser/browser-screencast.js", () => ({
126
+ ...realBrowserScreencast,
118
127
  unregisterConversationSender: () => {},
119
128
  }));
120
129
 
130
+ const realConversationNotifiers = await import("../conversation-notifiers.js");
121
131
  mock.module("../conversation-notifiers.js", () => ({
132
+ ...realConversationNotifiers,
122
133
  unregisterCallNotifiers: () => {},
123
134
  }));
124
135
 
136
+ const realConversationSkillTools =
137
+ await import("../conversation-skill-tools.js");
125
138
  mock.module("../conversation-skill-tools.js", () => ({
139
+ ...realConversationSkillTools,
126
140
  resetSkillToolProjection: () => {},
127
141
  }));
128
142
 
@@ -424,4 +438,24 @@ describe("disposeConversation — memory-retrospective lifecycle safety net", ()
424
438
  expect(memoryRetroCalls).toHaveLength(0);
425
439
  expect(autoAnalyzeCalls).toHaveLength(0);
426
440
  });
441
+
442
+ // Regression test: the retrospective lifecycle enqueue was previously
443
+ // outside the `!isAutoAnalysis` guard, so it fired even for auto-analysis
444
+ // conversations. Mirrors the indexer-time gate in `indexer.ts` and
445
+ // matches the existing graph_extract recursion-guard semantics.
446
+ test("auto-analysis conversation — does NOT enqueue memory-retrospective even with flag on", () => {
447
+ memoryRetroEnabled = true;
448
+ autoAnalysisConversations.add("conv-auto-retro");
449
+ const ctx = makeDisposeContext({
450
+ conversationId: "conv-auto-retro",
451
+ trustClass: "guardian",
452
+ });
453
+
454
+ disposeConversation(ctx);
455
+
456
+ expect(memoryRetroCalls).toHaveLength(0);
457
+ // graph_extract is also recursion-guarded by the same `!isAutoAnalysis`
458
+ // block, so it should be skipped here too.
459
+ expect(memoryJobCalls).toHaveLength(0);
460
+ });
427
461
  });
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Tests for the `config.tools.exclude` filter applied inside
3
+ * `createResolveToolsCallback`. Excluded tool names must not appear in the
4
+ * tool list resolved per turn, nor in the executor's `allowedToolNames`.
5
+ */
6
+
7
+ import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
8
+
9
+ import * as configLoader from "../../config/loader.js";
10
+ import type { AssistantConfig } from "../../config/schema.js";
11
+ import type { ToolDefinition } from "../../providers/types.js";
12
+ import {
13
+ __clearRegistryForTesting,
14
+ registerMcpTools,
15
+ } from "../../tools/registry.js";
16
+ import type { Tool } from "../../tools/types.js";
17
+ import { createResolveToolsCallback } from "../conversation-tool-setup.js";
18
+
19
+ type SkillProjectionContext =
20
+ import("../conversation-tool-setup.js").SkillProjectionContext;
21
+ type SkillProjectionCache =
22
+ import("../conversation-skill-tools.js").SkillProjectionCache;
23
+
24
+ function def(name: string): ToolDefinition {
25
+ return { name, description: name, input_schema: { type: "object" } };
26
+ }
27
+
28
+ function mcpTool(name: string): Tool {
29
+ return {
30
+ name,
31
+ description: name,
32
+ origin: "mcp",
33
+ getDefinition: () => def(name),
34
+ } as unknown as Tool;
35
+ }
36
+
37
+ function makeCtx(
38
+ overrides: Partial<SkillProjectionContext> = {},
39
+ ): SkillProjectionContext {
40
+ return {
41
+ skillProjectionState: new Map(),
42
+ skillProjectionCache: { fingerprints: new Map() } as SkillProjectionCache,
43
+ coreToolNames: new Set<string>(),
44
+ toolsDisabledDepth: 0,
45
+ ...overrides,
46
+ };
47
+ }
48
+
49
+ function withExclude(exclude: string[]) {
50
+ const stub: Partial<AssistantConfig> = { tools: { exclude } };
51
+ return spyOn(configLoader, "getConfig").mockReturnValue(
52
+ stub as AssistantConfig,
53
+ );
54
+ }
55
+
56
+ let getConfigSpy: ReturnType<typeof withExclude> | undefined;
57
+
58
+ beforeEach(() => {
59
+ __clearRegistryForTesting();
60
+ });
61
+
62
+ afterEach(() => {
63
+ getConfigSpy?.mockRestore();
64
+ getConfigSpy = undefined;
65
+ __clearRegistryForTesting();
66
+ });
67
+
68
+ describe("createResolveToolsCallback — config.tools.exclude", () => {
69
+ test("excluded core tool is omitted from the resolved tool list", () => {
70
+ getConfigSpy = withExclude(["bash"]);
71
+ const resolver = createResolveToolsCallback(
72
+ [def("bash"), def("file_read")],
73
+ makeCtx(),
74
+ );
75
+ const result = resolver!([]);
76
+ expect(result.map((d) => d.name)).toEqual(["file_read"]);
77
+ });
78
+
79
+ test("excluded core tool is removed from ctx.allowedToolNames", () => {
80
+ getConfigSpy = withExclude(["bash"]);
81
+ const ctx = makeCtx();
82
+ const resolver = createResolveToolsCallback(
83
+ [def("bash"), def("file_read")],
84
+ ctx,
85
+ );
86
+ resolver!([]);
87
+ expect(ctx.allowedToolNames?.has("bash")).toBe(false);
88
+ expect(ctx.allowedToolNames?.has("file_read")).toBe(true);
89
+ });
90
+
91
+ test("excluded MCP tool is omitted from the resolved tool list", () => {
92
+ registerMcpTools([
93
+ mcpTool("mcp__server__navigate"),
94
+ mcpTool("mcp__server__click"),
95
+ ]);
96
+ getConfigSpy = withExclude(["mcp__server__navigate"]);
97
+ const resolver = createResolveToolsCallback(
98
+ [def("mcp__server__navigate"), def("mcp__server__click")],
99
+ makeCtx(),
100
+ );
101
+ const result = resolver!([]);
102
+ expect(result.map((d) => d.name)).toEqual(["mcp__server__click"]);
103
+ });
104
+
105
+ test("unknown name in exclude list is silently ignored", () => {
106
+ getConfigSpy = withExclude(["does_not_exist"]);
107
+ const resolver = createResolveToolsCallback([def("file_read")], makeCtx());
108
+ expect(() => resolver!([])).not.toThrow();
109
+ expect(resolver!([]).map((d) => d.name)).toEqual(["file_read"]);
110
+ });
111
+
112
+ test("empty exclude list leaves the tool set unchanged", () => {
113
+ getConfigSpy = withExclude([]);
114
+ const resolver = createResolveToolsCallback(
115
+ [def("bash"), def("file_read")],
116
+ makeCtx(),
117
+ );
118
+ expect(
119
+ resolver!([])
120
+ .map((d) => d.name)
121
+ .sort(),
122
+ ).toEqual(["bash", "file_read"]);
123
+ });
124
+
125
+ test("excluded tool stays excluded under disk-pressure cleanup mode", () => {
126
+ // `bash` is a cleanup-safe tool and would normally survive cleanup mode;
127
+ // the exclude filter must still suppress it.
128
+ getConfigSpy = withExclude(["bash"]);
129
+ const ctx = makeCtx({ diskPressureCleanupModeActive: true });
130
+ const resolver = createResolveToolsCallback(
131
+ [def("bash"), def("file_read")],
132
+ ctx,
133
+ );
134
+ const result = resolver!([]);
135
+ expect(result.map((d) => d.name)).not.toContain("bash");
136
+ expect(ctx.allowedToolNames?.has("bash")).toBe(false);
137
+ });
138
+ });
@@ -463,6 +463,80 @@ describe("isToolActiveForContext — cross-client exposure for host_browser", ()
463
463
  });
464
464
  });
465
465
 
466
+ describe("isToolActiveForContext — ask_question macOS gating", () => {
467
+ test("ask_question is active for web client with a connected client", () => {
468
+ expect(
469
+ isToolActiveForContext(
470
+ "ask_question",
471
+ makeCtx({
472
+ hasNoClient: false,
473
+ channelCapabilities: {
474
+ channel: "web",
475
+ supportsDynamicUi: true,
476
+ clientOS: "web",
477
+ },
478
+ }),
479
+ ),
480
+ ).toBe(true);
481
+ });
482
+
483
+ test("ask_question is NOT active when clientOS is macos", () => {
484
+ // The macOS client has no UI handler for question_request yet; the tool
485
+ // is hidden to avoid a 5-minute prompter timeout.
486
+ expect(
487
+ isToolActiveForContext(
488
+ "ask_question",
489
+ makeCtx({
490
+ hasNoClient: false,
491
+ channelCapabilities: {
492
+ channel: "macos",
493
+ supportsDynamicUi: true,
494
+ clientOS: "macos",
495
+ },
496
+ }),
497
+ ),
498
+ ).toBe(false);
499
+ });
500
+
501
+ test("ask_question is active when channelCapabilities is undefined (backwards-compat)", () => {
502
+ expect(
503
+ isToolActiveForContext("ask_question", makeCtx({ hasNoClient: false })),
504
+ ).toBe(true);
505
+ });
506
+
507
+ test("ask_question is NOT active when hasNoClient is true regardless of clientOS", () => {
508
+ expect(
509
+ isToolActiveForContext(
510
+ "ask_question",
511
+ makeCtx({
512
+ hasNoClient: true,
513
+ channelCapabilities: {
514
+ channel: "web",
515
+ supportsDynamicUi: true,
516
+ clientOS: "web",
517
+ },
518
+ }),
519
+ ),
520
+ ).toBe(false);
521
+ });
522
+
523
+ test("other client-capability tools (app_open) are NOT affected by the macos gate", () => {
524
+ expect(
525
+ isToolActiveForContext(
526
+ "app_open",
527
+ makeCtx({
528
+ hasNoClient: false,
529
+ channelCapabilities: {
530
+ channel: "macos",
531
+ supportsDynamicUi: true,
532
+ clientOS: "macos",
533
+ },
534
+ }),
535
+ ),
536
+ ).toBe(true);
537
+ });
538
+ });
539
+
466
540
  describe("HOST_TOOL_NAMES derivation", () => {
467
541
  test("HOST_TOOL_NAMES is derived from HOST_TOOL_TO_CAPABILITY", () => {
468
542
  // Sanity check: every tool in the names set has a capability mapping.