@vellumai/assistant 0.8.5 → 0.8.6

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 (544) hide show
  1. package/AGENTS.md +33 -1
  2. package/ARCHITECTURE.md +1 -1
  3. package/bunfig.toml +6 -1
  4. package/docs/credential-execution-service.md +6 -6
  5. package/docs/plugins.md +4 -3
  6. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
  7. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
  8. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
  9. package/openapi.yaml +1900 -166
  10. package/package.json +1 -1
  11. package/src/__tests__/actor-token-service.test.ts +3 -2
  12. package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
  13. package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
  14. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  15. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  16. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  17. package/src/__tests__/annotate-risk-options.test.ts +1 -0
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/approval-routes-http.test.ts +9 -13
  20. package/src/__tests__/assert-not-live-db.ts +79 -0
  21. package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
  22. package/src/__tests__/audit-log-rotation.test.ts +2 -2
  23. package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
  24. package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
  25. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  26. package/src/__tests__/btw-routes.test.ts +3 -2
  27. package/src/__tests__/call-controller.test.ts +3 -2
  28. package/src/__tests__/channel-approval-routes.test.ts +3 -2
  29. package/src/__tests__/channel-guardian.test.ts +3 -2
  30. package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
  31. package/src/__tests__/channel-reply-delivery.test.ts +35 -0
  32. package/src/__tests__/channel-retry-sweep.test.ts +320 -3
  33. package/src/__tests__/checker.test.ts +12 -12
  34. package/src/__tests__/compaction-events.test.ts +1 -0
  35. package/src/__tests__/compaction-trail-store.test.ts +264 -0
  36. package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
  37. package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
  38. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
  39. package/src/__tests__/computer-use-tools.test.ts +12 -14
  40. package/src/__tests__/config-loader-backfill.test.ts +13 -28
  41. package/src/__tests__/config-loader-corrupt.test.ts +5 -5
  42. package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
  43. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
  44. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
  45. package/src/__tests__/config-schema.test.ts +10 -10
  46. package/src/__tests__/connection-model-compat.test.ts +83 -0
  47. package/src/__tests__/contacts-tools.test.ts +3 -2
  48. package/src/__tests__/context-token-estimator.test.ts +22 -0
  49. package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
  50. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
  52. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  53. package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
  54. package/src/__tests__/conversation-agent-loop.test.ts +488 -2
  55. package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
  56. package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
  57. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
  58. package/src/__tests__/conversation-attention-store.test.ts +101 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  61. package/src/__tests__/conversation-error.test.ts +30 -0
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -8
  63. package/src/__tests__/conversation-fork-route.test.ts +3 -2
  64. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  65. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
  66. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
  67. package/src/__tests__/conversation-lifecycle.test.ts +1 -0
  68. package/src/__tests__/conversation-list-source.test.ts +3 -2
  69. package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
  70. package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
  71. package/src/__tests__/conversation-pairing.test.ts +53 -0
  72. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
  73. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  74. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
  75. package/src/__tests__/conversation-queue.test.ts +333 -291
  76. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
  77. package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
  78. package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
  79. package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
  80. package/src/__tests__/conversation-skill-tools.test.ts +38 -142
  81. package/src/__tests__/conversation-slash-queue.test.ts +84 -32
  82. package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
  83. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
  85. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
  90. package/src/__tests__/conversation-sync-tags.test.ts +128 -12
  91. package/src/__tests__/conversation-title-service.test.ts +1 -0
  92. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
  93. package/src/__tests__/conversation-usage.test.ts +1 -0
  94. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  95. package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
  96. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
  97. package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
  98. package/src/__tests__/credential-broker-server-use.test.ts +5 -5
  99. package/src/__tests__/credential-execution-client.test.ts +72 -1
  100. package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
  101. package/src/__tests__/credential-health-service.test.ts +252 -3
  102. package/src/__tests__/credential-security-invariants.test.ts +5 -5
  103. package/src/__tests__/credential-vault-unit.test.ts +19 -19
  104. package/src/__tests__/credential-vault.test.ts +5 -5
  105. package/src/__tests__/cross-provider-web-search.test.ts +56 -2
  106. package/src/__tests__/db-connection-isolation.test.ts +7 -6
  107. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
  108. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
  109. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
  110. package/src/__tests__/db-test-helpers.ts +58 -0
  111. package/src/__tests__/disk-pressure-guard.test.ts +58 -41
  112. package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
  113. package/src/__tests__/disk-pressure-routes.test.ts +0 -33
  114. package/src/__tests__/disk-pressure-tools.test.ts +0 -4
  115. package/src/__tests__/dm-persistence.test.ts +26 -40
  116. package/src/__tests__/document-create-dedupe.test.ts +189 -0
  117. package/src/__tests__/document-find-replace.test.ts +3 -2
  118. package/src/__tests__/document-tool-security.test.ts +81 -2
  119. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
  120. package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
  121. package/src/__tests__/encrypted-store.test.ts +11 -9
  122. package/src/__tests__/feature-flag-test-helpers.ts +53 -0
  123. package/src/__tests__/filing-service.test.ts +1 -0
  124. package/src/__tests__/first-greeting.test.ts +62 -12
  125. package/src/__tests__/gateway-flag-listener.test.ts +0 -1
  126. package/src/__tests__/gemini-provider.test.ts +26 -0
  127. package/src/__tests__/guardian-action-sweep.test.ts +3 -2
  128. package/src/__tests__/guardian-outbound-http.test.ts +3 -2
  129. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
  130. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
  131. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  132. package/src/__tests__/heartbeat-service.test.ts +1 -0
  133. package/src/__tests__/helpers/mock-logger.ts +26 -0
  134. package/src/__tests__/host-bash-routes.test.ts +1 -0
  135. package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
  136. package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
  137. package/src/__tests__/host-shell-tool.test.ts +5 -4
  138. package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
  139. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  140. package/src/__tests__/http-user-message-parity.test.ts +29 -7
  141. package/src/__tests__/identity-intro-cache.test.ts +133 -22
  142. package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
  143. package/src/__tests__/inference-profile-reaper.test.ts +3 -2
  144. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
  145. package/src/__tests__/injector-disk-pressure.test.ts +3 -17
  146. package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
  147. package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
  148. package/src/__tests__/llm-context-normalization.test.ts +42 -0
  149. package/src/__tests__/llm-resolver.test.ts +331 -0
  150. package/src/__tests__/llm-schema.test.ts +1 -1
  151. package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
  152. package/src/__tests__/mcp-abort-signal.test.ts +14 -0
  153. package/src/__tests__/mcp-client-auth.test.ts +14 -0
  154. package/src/__tests__/messaging-send-tool.test.ts +1 -0
  155. package/src/__tests__/migration-import-from-url.test.ts +3 -3
  156. package/src/__tests__/mock-gateway-ipc.ts +18 -2
  157. package/src/__tests__/model-intents.test.ts +3 -3
  158. package/src/__tests__/native-web-search.test.ts +30 -2
  159. package/src/__tests__/notification-deep-link.test.ts +62 -0
  160. package/src/__tests__/oauth-commands-routes.test.ts +37 -0
  161. package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
  162. package/src/__tests__/oauth-store.test.ts +3 -2
  163. package/src/__tests__/onboarding-template-contract.test.ts +3 -2
  164. package/src/__tests__/openai-provider.test.ts +8 -9
  165. package/src/__tests__/openai-responses-provider.test.ts +70 -10
  166. package/src/__tests__/openrouter-provider-only.test.ts +27 -5
  167. package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
  168. package/src/__tests__/persistence-pipeline.test.ts +139 -1
  169. package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
  170. package/src/__tests__/plugin-bootstrap.test.ts +9 -11
  171. package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
  172. package/src/__tests__/process-message-background-slack.test.ts +21 -16
  173. package/src/__tests__/process-message-display-content.test.ts +19 -22
  174. package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
  175. package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
  176. package/src/__tests__/provider-registry-ollama.test.ts +45 -22
  177. package/src/__tests__/recording-handler.test.ts +1 -0
  178. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  179. package/src/__tests__/registry.test.ts +82 -76
  180. package/src/__tests__/relay-server.test.ts +10 -10
  181. package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
  182. package/src/__tests__/schedule-store.test.ts +16 -1
  183. package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
  184. package/src/__tests__/secret-ingress-http.test.ts +5 -1
  185. package/src/__tests__/secure-keys.test.ts +3 -3
  186. package/src/__tests__/send-endpoint-busy.test.ts +81 -42
  187. package/src/__tests__/server-history-render.test.ts +4 -1
  188. package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
  189. package/src/__tests__/skill-feature-flags.test.ts +14 -16
  190. package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
  191. package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
  192. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  193. package/src/__tests__/skill-tool-factory.test.ts +96 -95
  194. package/src/__tests__/slack-channel-config.test.ts +3 -3
  195. package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
  196. package/src/__tests__/subagent-disposal.test.ts +27 -8
  197. package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
  198. package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
  199. package/src/__tests__/subagent-manager-notify.test.ts +20 -8
  200. package/src/__tests__/subagent-notify-parent.test.ts +5 -4
  201. package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
  202. package/src/__tests__/subagent-tools.test.ts +2 -1
  203. package/src/__tests__/suggestion-routes.test.ts +1 -0
  204. package/src/__tests__/system-prompt.test.ts +38 -0
  205. package/src/__tests__/test-preload-verifier.ts +68 -0
  206. package/src/__tests__/test-preload.ts +32 -39
  207. package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
  208. package/src/__tests__/tool-executor.test.ts +55 -10
  209. package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
  210. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  211. package/src/__tests__/twilio-routes.test.ts +3 -2
  212. package/src/__tests__/validate-input.test.ts +381 -0
  213. package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
  214. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
  215. package/src/__tests__/voice-session-bridge.test.ts +37 -28
  216. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
  217. package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
  218. package/src/acp/session-manager.ts +5 -6
  219. package/src/agent/loop.ts +80 -0
  220. package/src/api/README.md +124 -2
  221. package/src/api/constants/call-sites.ts +27 -0
  222. package/src/api/events/assistant-outbound-attachment.ts +51 -0
  223. package/src/api/events/assistant-text-delta.ts +32 -0
  224. package/src/api/events/assistant-turn-start.ts +33 -0
  225. package/src/api/events/document-comment-created.ts +48 -0
  226. package/src/api/events/document-comment-deleted.ts +24 -0
  227. package/src/api/events/document-comment-reopened.ts +25 -0
  228. package/src/api/events/document-comment-resolved.ts +27 -0
  229. package/src/api/events/generation-cancelled.ts +24 -0
  230. package/src/api/events/generation-handoff.ts +41 -0
  231. package/src/api/events/message-complete.ts +42 -0
  232. package/src/api/events/open-url.ts +30 -0
  233. package/src/{events → api/events}/relationship-state-updated.ts +3 -3
  234. package/src/api/events/tool-use-start.ts +32 -0
  235. package/src/api/index.ts +128 -3
  236. package/src/api/responses/llm-context-response.ts +39 -0
  237. package/src/api/responses/llm-request-log-entry.ts +93 -0
  238. package/src/api/responses/memory-recall-log.ts +65 -0
  239. package/src/api/responses/memory-v2-activation-log.ts +78 -0
  240. package/src/background-wake/background-wake-routes.test.ts +687 -52
  241. package/src/background-wake/platform-client.test.ts +308 -0
  242. package/src/background-wake/platform-client.ts +167 -0
  243. package/src/background-wake/publisher.ts +91 -0
  244. package/src/background-wake/runtime-registry.ts +2 -2
  245. package/src/background-wake/wake-intent-hooks.test.ts +282 -0
  246. package/src/calls/guardian-dispatch.ts +1 -0
  247. package/src/calls/voice-session-bridge.ts +4 -4
  248. package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
  249. package/src/cli/commands/__tests__/notifications.test.ts +184 -40
  250. package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
  251. package/src/cli/commands/channels/index.ts +229 -0
  252. package/src/cli/commands/memory-v3-render.ts +147 -0
  253. package/src/cli/commands/memory-v3.ts +255 -4
  254. package/src/cli/commands/notifications.ts +365 -55
  255. package/src/cli/lib/open-browser.ts +7 -2
  256. package/src/cli/program.ts +2 -0
  257. package/src/config/assistant-feature-flags.ts +23 -42
  258. package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
  259. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  260. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  261. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
  262. package/src/config/call-site-defaults.ts +1 -1
  263. package/src/config/feature-flag-cache.ts +86 -0
  264. package/src/config/feature-flag-registry.json +17 -17
  265. package/src/config/llm-context-resolution.ts +10 -1
  266. package/src/config/llm-resolver.ts +121 -15
  267. package/src/config/loader.ts +4 -5
  268. package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
  269. package/src/config/schemas/heartbeat.ts +1 -1
  270. package/src/config/schemas/llm.ts +90 -1
  271. package/src/config/schemas/memory-v2.ts +26 -0
  272. package/src/config/schemas/services.ts +6 -2
  273. package/src/config/seed-inference-profiles.ts +36 -16
  274. package/src/context/token-estimator.ts +10 -5
  275. package/src/credential-execution/executable-discovery.ts +40 -0
  276. package/src/credential-execution/process-manager.ts +6 -2
  277. package/src/credential-health/credential-health-service.ts +125 -40
  278. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
  279. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
  280. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
  281. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
  282. package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
  283. package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
  284. package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
  285. package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
  286. package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
  287. package/src/daemon/conversation-agent-loop.ts +132 -28
  288. package/src/daemon/conversation-error.ts +33 -5
  289. package/src/daemon/conversation-messaging.ts +84 -43
  290. package/src/daemon/conversation-process.ts +74 -37
  291. package/src/daemon/conversation-runtime-assembly.ts +29 -9
  292. package/src/daemon/conversation-skill-tools.ts +14 -30
  293. package/src/daemon/conversation-surfaces.ts +69 -34
  294. package/src/daemon/conversation-tool-setup.ts +33 -48
  295. package/src/daemon/conversation.ts +26 -46
  296. package/src/daemon/daemon-control.ts +1 -1
  297. package/src/daemon/daemon-skill-host.ts +9 -2
  298. package/src/daemon/disk-pressure-guard.ts +27 -29
  299. package/src/daemon/first-greeting.ts +31 -13
  300. package/src/daemon/handlers/shared.ts +6 -1
  301. package/src/daemon/lifecycle.ts +12 -12
  302. package/src/daemon/mcp-reload-service.ts +1 -1
  303. package/src/daemon/meet-manifest-loader.ts +10 -17
  304. package/src/daemon/message-types/conversations.ts +20 -22
  305. package/src/daemon/message-types/document-comments.ts +8 -44
  306. package/src/daemon/message-types/home.ts +2 -2
  307. package/src/daemon/message-types/integrations.ts +2 -7
  308. package/src/daemon/message-types/messages.ts +23 -38
  309. package/src/daemon/message-types/subagents.ts +6 -0
  310. package/src/daemon/process-message.ts +9 -9
  311. package/src/daemon/providers-setup.ts +1 -1
  312. package/src/daemon/server.ts +16 -0
  313. package/src/daemon/switch-inference-profile-tool.ts +13 -3
  314. package/src/daemon/tool-setup-types.ts +0 -6
  315. package/src/daemon/wake-target-adapter.ts +10 -0
  316. package/src/documents/document-store.ts +38 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +1 -0
  318. package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
  319. package/src/heartbeat/heartbeat-service.ts +63 -0
  320. package/src/home/__tests__/feed-writer.test.ts +161 -0
  321. package/src/home/__tests__/post-connect-feed.test.ts +1 -0
  322. package/src/home/__tests__/suggested-prompts.test.ts +55 -59
  323. package/src/home/feed-writer.ts +146 -7
  324. package/src/home/suggested-prompts.ts +27 -145
  325. package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
  326. package/src/ipc/gateway-client.test.ts +4 -1
  327. package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
  328. package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
  329. package/src/ipc/skill-routes/memory.ts +4 -3
  330. package/src/ipc/skill-routes/registries.ts +28 -29
  331. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
  332. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
  333. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
  334. package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
  335. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
  336. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
  337. package/src/memory/conversation-attention-store.ts +17 -3
  338. package/src/memory/conversation-crud.ts +352 -112
  339. package/src/memory/db-connection.ts +29 -19
  340. package/src/memory/db-init.ts +4 -0
  341. package/src/memory/db-singleton.ts +77 -0
  342. package/src/memory/delivery-channels.ts +82 -0
  343. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
  344. package/src/memory/graph/retriever.test.ts +3 -3
  345. package/src/memory/job-handlers/embedding.test.ts +3 -2
  346. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
  347. package/src/memory/jobs-worker.ts +12 -1
  348. package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
  349. package/src/memory/llm-request-log-source-local.ts +24 -0
  350. package/src/memory/llm-request-log-source.ts +31 -0
  351. package/src/memory/llm-request-log-store.ts +188 -3
  352. package/src/memory/memory-v2-activation-log-store.ts +95 -1
  353. package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
  354. package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
  355. package/src/memory/migrations/index.ts +2 -0
  356. package/src/memory/schema/conversations.ts +9 -1
  357. package/src/memory/schema/inference.ts +0 -1
  358. package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
  359. package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
  360. package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
  361. package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
  362. package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
  363. package/src/memory/v2/harness/metrics.ts +5 -1
  364. package/src/memory/v2/harness/replay-input.ts +19 -3
  365. package/src/memory/v2/harness/runner.ts +6 -0
  366. package/src/memory/v2/harness/trace.ts +6 -0
  367. package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
  368. package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
  369. package/src/memory/v3/__tests__/edges.test.ts +144 -1
  370. package/src/memory/v3/__tests__/filter.test.ts +48 -0
  371. package/src/memory/v3/__tests__/gate.test.ts +96 -33
  372. package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
  373. package/src/memory/v3/__tests__/loop.test.ts +250 -5
  374. package/src/memory/v3/__tests__/scouts.test.ts +49 -0
  375. package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
  376. package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
  377. package/src/memory/v3/__tests__/traversal.test.ts +39 -0
  378. package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
  379. package/src/memory/v3/__tests__/validate.test.ts +32 -0
  380. package/src/memory/v3/coretrieval-seed.ts +240 -0
  381. package/src/memory/v3/edges.ts +58 -21
  382. package/src/memory/v3/filter.ts +27 -22
  383. package/src/memory/v3/gate.ts +51 -36
  384. package/src/memory/v3/index-composition.ts +18 -5
  385. package/src/memory/v3/loop.ts +65 -17
  386. package/src/memory/v3/scouts.ts +15 -4
  387. package/src/memory/v3/shadow-diff.ts +287 -0
  388. package/src/memory/v3/shadow-middleware.ts +44 -2
  389. package/src/memory/v3/traversal.ts +6 -1
  390. package/src/memory/v3/tree-walk.ts +6 -1
  391. package/src/memory/v3/validate.ts +56 -33
  392. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  393. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
  394. package/src/notifications/adapters/slack.ts +45 -11
  395. package/src/notifications/broadcaster.ts +114 -63
  396. package/src/notifications/conversation-pairing.ts +23 -3
  397. package/src/notifications/decisions-store.ts +32 -1
  398. package/src/notifications/deliveries-store.ts +45 -0
  399. package/src/notifications/edit-notification.ts +201 -0
  400. package/src/notifications/emit-signal.ts +11 -1
  401. package/src/notifications/signal.ts +10 -0
  402. package/src/notifications/types.ts +37 -0
  403. package/src/oauth/byo-connection.test.ts +67 -3
  404. package/src/oauth/byo-connection.ts +32 -5
  405. package/src/oauth/connect-orchestrator.ts +9 -0
  406. package/src/oauth/connection-resolver.test.ts +76 -0
  407. package/src/oauth/connection-resolver.ts +49 -10
  408. package/src/oauth/manual-token-connection.ts +51 -3
  409. package/src/oauth/seed-providers.ts +3 -0
  410. package/src/permissions/approval-policy.test.ts +19 -5
  411. package/src/permissions/approval-policy.ts +14 -3
  412. package/src/permissions/checker.ts +21 -8
  413. package/src/platform/client.test.ts +24 -1
  414. package/src/platform/client.ts +8 -0
  415. package/src/platform/feature-gate.ts +15 -0
  416. package/src/plugins/defaults/injectors.ts +2 -8
  417. package/src/plugins/defaults/persistence.ts +25 -6
  418. package/src/plugins/types.ts +57 -13
  419. package/src/proactive-artifact/job.test.ts +1 -0
  420. package/src/prompts/__tests__/system-prompt.test.ts +4 -4
  421. package/src/prompts/system-prompt.ts +38 -40
  422. package/src/prompts/template-detection.ts +10 -4
  423. package/src/prompts/templates/BOOTSTRAP.md +7 -11
  424. package/src/prompts/templates/IDENTITY.md +0 -2
  425. package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
  426. package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
  427. package/src/providers/call-site-routing.ts +33 -9
  428. package/src/providers/connection-model-compat.ts +23 -0
  429. package/src/providers/connection-resolution.ts +39 -20
  430. package/src/providers/fireworks/client.ts +1 -0
  431. package/src/providers/gemini/client.ts +24 -3
  432. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
  433. package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
  434. package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
  435. package/src/providers/inference/auth.ts +0 -8
  436. package/src/providers/inference/connections.ts +3 -66
  437. package/src/providers/inference/resolve-auth.ts +2 -3
  438. package/src/providers/model-catalog.ts +35 -1
  439. package/src/providers/model-intents.ts +3 -3
  440. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  441. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
  442. package/src/providers/openai/chat-completions-provider.ts +110 -12
  443. package/src/providers/openai/codex-models.ts +2 -0
  444. package/src/providers/openai/responses-provider.ts +53 -53
  445. package/src/providers/openrouter/client.ts +13 -8
  446. package/src/providers/provider-send-message.ts +18 -9
  447. package/src/providers/registry.ts +48 -8
  448. package/src/providers/retry.ts +16 -4
  449. package/src/providers/search-provider-catalog.ts +17 -9
  450. package/src/providers/types.ts +9 -0
  451. package/src/runtime/__tests__/agent-wake.test.ts +1 -0
  452. package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
  453. package/src/runtime/access-request-helper.ts +1 -0
  454. package/src/runtime/auth/route-policy.ts +10 -0
  455. package/src/runtime/channel-readiness-service.ts +68 -0
  456. package/src/runtime/channel-reply-delivery.ts +23 -0
  457. package/src/runtime/channel-retry-sweep.ts +47 -14
  458. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  459. package/src/runtime/migrations/vbundle-builder.ts +3 -2
  460. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
  461. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
  462. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  463. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  464. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
  465. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
  466. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
  467. package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
  468. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
  469. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
  470. package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
  471. package/src/runtime/routes/acp-routes-list.test.ts +3 -0
  472. package/src/runtime/routes/app-management-routes.ts +111 -4
  473. package/src/runtime/routes/background-wake-routes.ts +188 -20
  474. package/src/runtime/routes/btw-routes.ts +4 -4
  475. package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
  476. package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
  477. package/src/runtime/routes/conversation-list-routes.ts +147 -0
  478. package/src/runtime/routes/conversation-management-routes.ts +39 -14
  479. package/src/runtime/routes/conversation-query-routes.ts +60 -10
  480. package/src/runtime/routes/conversation-routes.ts +186 -140
  481. package/src/runtime/routes/conversations-import-routes.ts +19 -6
  482. package/src/runtime/routes/documents-routes.ts +10 -1
  483. package/src/runtime/routes/group-routes.ts +11 -0
  484. package/src/runtime/routes/home-feed-routes.ts +129 -0
  485. package/src/runtime/routes/identity-intro-cache.ts +61 -16
  486. package/src/runtime/routes/identity-routes.ts +30 -9
  487. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
  488. package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
  489. package/src/runtime/routes/index.ts +2 -0
  490. package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
  491. package/src/runtime/routes/integrations/vercel.ts +15 -0
  492. package/src/runtime/routes/llm-context-normalization.ts +7 -2
  493. package/src/runtime/routes/memory-v3-routes.ts +160 -2
  494. package/src/runtime/routes/migration-routes.ts +20 -13
  495. package/src/runtime/routes/notification-routes.ts +63 -1
  496. package/src/runtime/routes/oauth-commands-routes.ts +6 -1
  497. package/src/runtime/routes/surface-action-routes.ts +1 -38
  498. package/src/runtime/routes/surface-content-routes.ts +12 -5
  499. package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
  500. package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
  501. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
  502. package/src/runtime/slack-dm-text-delivery.ts +177 -0
  503. package/src/runtime/sync/resource-sync-events.ts +1 -1
  504. package/src/runtime/tool-grant-request-helper.ts +1 -0
  505. package/src/schedule/schedule-store.ts +8 -1
  506. package/src/schedule/scheduler.ts +111 -15
  507. package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
  508. package/src/security/encrypted-store.ts +7 -16
  509. package/src/security/store-path-override.ts +61 -0
  510. package/src/signals/user-message.ts +5 -8
  511. package/src/skills/validate-input.ts +177 -0
  512. package/src/subagent/manager.ts +13 -13
  513. package/src/subagent/types.ts +6 -0
  514. package/src/tasks/tool-sanitizer.ts +2 -2
  515. package/src/tools/apps/definitions.ts +35 -21
  516. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
  517. package/src/tools/computer-use/definitions.ts +268 -266
  518. package/src/tools/document/document-tool.ts +131 -8
  519. package/src/tools/execution-target.ts +2 -5
  520. package/src/tools/executor.ts +18 -55
  521. package/src/tools/host-filesystem/edit.test.ts +1 -0
  522. package/src/tools/host-filesystem/read.test.ts +1 -0
  523. package/src/tools/host-filesystem/transfer.test.ts +31 -6
  524. package/src/tools/host-filesystem/write.test.ts +1 -0
  525. package/src/tools/mcp/mcp-tool-factory.ts +0 -2
  526. package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
  527. package/src/tools/network/__tests__/web-search.test.ts +211 -3
  528. package/src/tools/network/managed-search-proxy.ts +183 -0
  529. package/src/tools/network/web-search.ts +199 -44
  530. package/src/tools/policy-context.ts +3 -1
  531. package/src/tools/registry.ts +146 -103
  532. package/src/tools/schedule/create.ts +1 -1
  533. package/src/tools/skills/skill-tool-factory.ts +17 -36
  534. package/src/tools/subagent/spawn.ts +3 -0
  535. package/src/tools/tool-approval-handler.ts +10 -4
  536. package/src/tools/tool-name-aliases.ts +72 -14
  537. package/src/tools/types.ts +17 -15
  538. package/src/tools/ui-surface/definitions.ts +98 -86
  539. package/src/types/onboarding-context.ts +6 -0
  540. package/src/usage/attribution.ts +32 -1
  541. package/src/util/browser.ts +7 -2
  542. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
  543. package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
  544. package/src/workspace/migrations/registry.ts +4 -0
@@ -142,6 +142,8 @@ mock.module("../memory/conversation-crud.js", () => ({
142
142
  setLastNotifiedInferenceProfile: () => {},
143
143
  getConversationOverrideProfileFromRow: () => undefined,
144
144
  updateMessageMetadata: () => {},
145
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
146
+ updateMessageContent: mock(() => {}),
145
147
  }));
146
148
 
147
149
  mock.module("../memory/conversation-queries.js", () => ({
@@ -228,6 +230,9 @@ mock.module("../agent/loop.js", () => ({
228
230
  messages: Message[],
229
231
  onEvent: (event: AgentEvent) => void,
230
232
  ): Promise<Message[]> {
233
+ // Prime the assistant row anchor — production code emits this from
234
+ // `AgentLoop.run` just before `provider.sendMessage`.
235
+ await onEvent({ type: "llm_call_started" });
231
236
  agentLoopScript(onEvent);
232
237
  onEvent({
233
238
  type: "usage",
@@ -28,8 +28,8 @@ mock.module("../util/logger.js", () => ({
28
28
  // Use encrypted backend with a temp store path
29
29
  // ---------------------------------------------------------------------------
30
30
 
31
- import { _setStorePath } from "../security/encrypted-store.js";
32
31
  import { _resetBackend } from "../security/secure-keys.js";
32
+ import { setStorePathForTesting } from "./encrypted-store-test-helpers.js";
33
33
 
34
34
  const TEST_DIR = join(
35
35
  tmpdir(),
@@ -74,7 +74,7 @@ describe("CredentialBroker.browserFill", () => {
74
74
  for (const entry of readdirSync(TEST_DIR)) {
75
75
  rmSync(join(TEST_DIR, entry), { recursive: true, force: true });
76
76
  }
77
- _setStorePath(STORE_PATH);
77
+ setStorePathForTesting(STORE_PATH);
78
78
  _resetBackend();
79
79
  _setMetadataPath(join(TEST_DIR, "metadata.json"));
80
80
  broker = new CredentialBroker();
@@ -82,7 +82,7 @@ describe("CredentialBroker.browserFill", () => {
82
82
 
83
83
  afterEach(() => {
84
84
  _setMetadataPath(null);
85
- _setStorePath(null);
85
+ setStorePathForTesting(null);
86
86
  _resetBackend();
87
87
  });
88
88
 
@@ -27,8 +27,8 @@ mock.module("../util/logger.js", () => ({
27
27
  // Use encrypted backend with a temp store path
28
28
  // ---------------------------------------------------------------------------
29
29
 
30
- import { _setStorePath } from "../security/encrypted-store.js";
31
30
  import { _resetBackend } from "../security/secure-keys.js";
31
+ import { setStorePathForTesting } from "./encrypted-store-test-helpers.js";
32
32
 
33
33
  const TEST_DIR = join(
34
34
  tmpdir(),
@@ -69,7 +69,7 @@ describe("CredentialBroker.serverUse", () => {
69
69
 
70
70
  beforeEach(() => {
71
71
  mkdirSync(TEST_DIR, { recursive: true });
72
- _setStorePath(STORE_PATH);
72
+ setStorePathForTesting(STORE_PATH);
73
73
  _resetBackend();
74
74
  _setMetadataPath(join(TEST_DIR, "metadata.json"));
75
75
  broker = new CredentialBroker();
@@ -77,7 +77,7 @@ describe("CredentialBroker.serverUse", () => {
77
77
 
78
78
  afterEach(() => {
79
79
  _setMetadataPath(null);
80
- _setStorePath(null);
80
+ setStorePathForTesting(null);
81
81
  _resetBackend();
82
82
  rmSync(TEST_DIR, { recursive: true, force: true });
83
83
  });
@@ -464,7 +464,7 @@ describe("CredentialBroker.serverUseById", () => {
464
464
 
465
465
  beforeEach(() => {
466
466
  mkdirSync(TEST_DIR, { recursive: true });
467
- _setStorePath(STORE_PATH);
467
+ setStorePathForTesting(STORE_PATH);
468
468
  _resetBackend();
469
469
  _setMetadataPath(join(TEST_DIR, "metadata.json"));
470
470
  broker = new CredentialBroker();
@@ -472,7 +472,7 @@ describe("CredentialBroker.serverUseById", () => {
472
472
 
473
473
  afterEach(() => {
474
474
  _setMetadataPath(null);
475
- _setStorePath(null);
475
+ setStorePathForTesting(null);
476
476
  _resetBackend();
477
477
  rmSync(TEST_DIR, { recursive: true, force: true });
478
478
  });
@@ -8,7 +8,15 @@
8
8
  * 4. The CES RPC client correctly frames requests and validates responses.
9
9
  */
10
10
 
11
- import { readdirSync, readFileSync, statSync } from "node:fs";
11
+ import {
12
+ mkdtempSync,
13
+ readdirSync,
14
+ readFileSync,
15
+ rmSync,
16
+ statSync,
17
+ writeFileSync,
18
+ } from "node:fs";
19
+ import { tmpdir } from "node:os";
12
20
  import { join, resolve } from "node:path";
13
21
  import { describe, expect, test } from "bun:test";
14
22
 
@@ -22,6 +30,7 @@ import {
22
30
  createCesClient,
23
31
  } from "../credential-execution/client.js";
24
32
  import {
33
+ discoverCesWithRetry,
25
34
  discoverLocalCes,
26
35
  discoverManagedCes,
27
36
  } from "../credential-execution/executable-discovery.js";
@@ -147,6 +156,68 @@ describe("managed CES discovery", () => {
147
156
  });
148
157
  });
149
158
 
159
+ // ---------------------------------------------------------------------------
160
+ // Managed discovery retry — absorbs the sidecar's socket re-bind window
161
+ // ---------------------------------------------------------------------------
162
+
163
+ describe("discoverCesWithRetry", () => {
164
+ function withManagedEnv(socketPath: string): () => void {
165
+ const savedSocket = process.env["CES_BOOTSTRAP_SOCKET"];
166
+ const savedSocketDir = process.env["CES_BOOTSTRAP_SOCKET_DIR"];
167
+ const savedContainerized = process.env["IS_CONTAINERIZED"];
168
+ delete process.env["CES_BOOTSTRAP_SOCKET_DIR"];
169
+ process.env["CES_BOOTSTRAP_SOCKET"] = socketPath;
170
+ process.env["IS_CONTAINERIZED"] = "true";
171
+ return () => {
172
+ const restore = (key: string, value: string | undefined) => {
173
+ if (value !== undefined) process.env[key] = value;
174
+ else delete process.env[key];
175
+ };
176
+ restore("CES_BOOTSTRAP_SOCKET", savedSocket);
177
+ restore("CES_BOOTSTRAP_SOCKET_DIR", savedSocketDir);
178
+ restore("IS_CONTAINERIZED", savedContainerized);
179
+ };
180
+ }
181
+
182
+ test("returns unavailable after polling when the socket never appears", async () => {
183
+ const socketPath = join(tmpdir(), `ces-retry-missing-${Date.now()}.sock`);
184
+ const restore = withManagedEnv(socketPath);
185
+ try {
186
+ const start = Date.now();
187
+ const result = await discoverCesWithRetry({
188
+ timeoutMs: 200,
189
+ intervalMs: 20,
190
+ });
191
+ expect(result.mode).toBe("unavailable");
192
+ // It must actually have waited rather than failing on the first probe.
193
+ expect(Date.now() - start).toBeGreaterThanOrEqual(150);
194
+ } finally {
195
+ restore();
196
+ }
197
+ });
198
+
199
+ test("resolves to managed once the socket is re-bound mid-poll", async () => {
200
+ const dir = mkdtempSync(join(tmpdir(), "ces-retry-"));
201
+ const socketPath = join(dir, "ces.sock");
202
+ const restore = withManagedEnv(socketPath);
203
+ try {
204
+ // Simulate the sidecar re-binding its bootstrap socket shortly after
205
+ // the assistant begins probing.
206
+ setTimeout(() => writeFileSync(socketPath, ""), 80);
207
+
208
+ const result = await discoverCesWithRetry({
209
+ timeoutMs: 2_000,
210
+ intervalMs: 20,
211
+ });
212
+ expect(result.mode).toBe("managed");
213
+ expect((result as { socketPath: string }).socketPath).toBe(socketPath);
214
+ } finally {
215
+ restore();
216
+ rmSync(dir, { recursive: true, force: true });
217
+ }
218
+ });
219
+ });
220
+
150
221
  // ---------------------------------------------------------------------------
151
222
  // CES client — transport and framing
152
223
  // ---------------------------------------------------------------------------
@@ -10,10 +10,7 @@
10
10
 
11
11
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
12
12
 
13
- import {
14
- _setOverridesForTesting,
15
- isAssistantFeatureFlagEnabled,
16
- } from "../config/assistant-feature-flags.js";
13
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
17
14
  import type { AssistantConfig } from "../config/schema.js";
18
15
  import {
19
16
  CES_GRANT_AUDIT_FLAG_KEY,
@@ -25,20 +22,21 @@ import {
25
22
  isCesShellLockdownEnabled,
26
23
  isCesToolsEnabled,
27
24
  } from "../credential-execution/feature-gates.js";
25
+ import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
28
26
 
29
27
  beforeEach(() => {
30
- _setOverridesForTesting({});
28
+ setOverridesForTesting({});
31
29
  });
32
30
 
33
31
  afterEach(() => {
34
- _setOverridesForTesting({});
32
+ setOverridesForTesting({});
35
33
  });
36
34
 
37
35
  // ---------------------------------------------------------------------------
38
36
  // Helpers
39
37
  // ---------------------------------------------------------------------------
40
38
 
41
- /** Create a minimal AssistantConfig (flag overrides are now set via _setOverridesForTesting). */
39
+ /** Create a minimal AssistantConfig (flag overrides are now set via setOverridesForTesting). */
42
40
  function makeConfig(): AssistantConfig {
43
41
  return {} as AssistantConfig;
44
42
  }
@@ -120,13 +118,13 @@ describe("CES flags match registry defaults", () => {
120
118
  describe("CES flags can be toggled independently", () => {
121
119
  for (const { name, fn, key } of ALL_CES_PREDICATES) {
122
120
  test(`enabling ${key} makes ${name} return true`, () => {
123
- _setOverridesForTesting({ [key]: true });
121
+ setOverridesForTesting({ [key]: true });
124
122
  const config = makeConfig();
125
123
  expect(fn(config)).toBe(true);
126
124
  });
127
125
 
128
126
  test(`enabling ${key} does not change other CES flags from their defaults`, () => {
129
- _setOverridesForTesting({ [key]: true });
127
+ setOverridesForTesting({ [key]: true });
130
128
  const config = makeConfig();
131
129
  for (const {
132
130
  fn: otherFn,
@@ -147,7 +145,7 @@ describe("CES flags can be toggled independently", () => {
147
145
  describe("CES flags respect explicit false overrides", () => {
148
146
  for (const { name, fn, key } of ALL_CES_PREDICATES) {
149
147
  test(`${name} returns false when explicitly set to false`, () => {
150
- _setOverridesForTesting({ [key]: false });
148
+ setOverridesForTesting({ [key]: false });
151
149
  const config = makeConfig();
152
150
  expect(fn(config)).toBe(false);
153
151
  });
@@ -164,7 +162,7 @@ describe("CES flags do not affect unrelated flags", () => {
164
162
  for (const key of ALL_CES_FLAG_KEYS) {
165
163
  overrides[key] = true;
166
164
  }
167
- _setOverridesForTesting(overrides);
165
+ setOverridesForTesting(overrides);
168
166
  const config = makeConfig();
169
167
 
170
168
  // account-deletion defaults to true in the registry and should stay true.
@@ -178,7 +176,7 @@ describe("CES flags do not affect unrelated flags", () => {
178
176
  for (const key of ALL_CES_FLAG_KEYS) {
179
177
  overrides[key] = true;
180
178
  }
181
- _setOverridesForTesting(overrides);
179
+ setOverridesForTesting(overrides);
182
180
  const config = makeConfig();
183
181
 
184
182
  // Unknown flags fail closed unless explicitly overridden.
@@ -12,6 +12,7 @@ let mockProviders: Array<{
12
12
  pingMethod: string | null;
13
13
  pingHeaders: string | null;
14
14
  pingBody: string | null;
15
+ managedServiceConfigKey?: string;
15
16
  }> = [];
16
17
 
17
18
  let mockConnections: Map<
@@ -33,6 +34,24 @@ let mockFetchResponse: { ok: boolean; status: number } = {
33
34
  };
34
35
  let mockFetchThrows = false;
35
36
 
37
+ // Per-test refresh outcome — drives the withValidToken mock.
38
+ // "ok" → refresh succeeds (or wasn't needed); callback runs with token
39
+ // "refresh_failed" → refresh throws TokenExpiredError (revoked refresh token)
40
+ type RefreshOutcome = "ok" | "refresh_failed";
41
+ let mockRefreshOutcome: RefreshOutcome = "ok";
42
+
43
+ // Managed-path mock state.
44
+ const managedProviders = new Set<string>();
45
+ let managedListResponse: {
46
+ ok: boolean;
47
+ status: number;
48
+ body?: unknown;
49
+ } = { ok: true, status: 200, body: { results: [] } };
50
+ let managedPingOutcome:
51
+ | { kind: "status"; status: number }
52
+ | { kind: "credential_required" }
53
+ | { kind: "throw"; message: string } = { kind: "status", status: 200 };
54
+
36
55
  // ── Module mocks ─────────────────────────────────────────────────────
37
56
 
38
57
  mock.module("../security/secure-keys.js", () => ({
@@ -81,6 +100,88 @@ mock.module("../util/logger.js", () => ({
81
100
  }),
82
101
  }));
83
102
 
103
+ class MockTokenExpiredError extends Error {
104
+ constructor(service: string, message?: string) {
105
+ super(message ?? `Token expired for "${service}"`);
106
+ this.name = "TokenExpiredError";
107
+ }
108
+ }
109
+
110
+ mock.module("../security/token-manager.js", () => ({
111
+ TokenExpiredError: MockTokenExpiredError,
112
+ withValidToken: async (
113
+ service: string,
114
+ callback: (token: string) => Promise<unknown>,
115
+ opts: { connectionId: string },
116
+ ) => {
117
+ if (mockRefreshOutcome === "refresh_failed") {
118
+ throw new MockTokenExpiredError(service);
119
+ }
120
+ const token =
121
+ secureKeyValues.get(
122
+ `oauth_connection/${opts.connectionId}/access_token`,
123
+ ) ?? "refreshed-token";
124
+ return callback(token);
125
+ },
126
+ }));
127
+
128
+ // Managed-path mocks: services schema, config loader, platform client,
129
+ // PlatformOAuthConnection. The managed code path uses dynamic imports for
130
+ // these so they only matter when a managed test scenario is exercised.
131
+ mock.module("../config/schemas/services.js", () => ({
132
+ ServicesSchema: {
133
+ shape: {
134
+ "google-oauth": {},
135
+ "notion-oauth": {},
136
+ },
137
+ },
138
+ getServiceMode: (_services: unknown, key: string) =>
139
+ managedProviders.has(key) ? "managed" : "your-own",
140
+ }));
141
+
142
+ mock.module("../config/loader.js", () => ({
143
+ getConfig: () => ({ services: {} }),
144
+ }));
145
+
146
+ mock.module("../platform/client.js", () => ({
147
+ VellumPlatformClient: {
148
+ create: async () => ({
149
+ platformAssistantId: "test-assistant",
150
+ fetch: async (_path: string) => ({
151
+ ok: managedListResponse.ok,
152
+ status: managedListResponse.status,
153
+ json: async () => managedListResponse.body ?? { results: [] },
154
+ }),
155
+ }),
156
+ },
157
+ }));
158
+
159
+ class MockCredentialRequiredError extends Error {
160
+ constructor() {
161
+ super("credential required");
162
+ this.name = "CredentialRequiredError";
163
+ }
164
+ }
165
+
166
+ mock.module("../oauth/platform-connection.js", () => ({
167
+ PlatformOAuthConnection: class {
168
+ constructor(_opts: unknown) {}
169
+ async request(_req: unknown) {
170
+ if (managedPingOutcome.kind === "credential_required") {
171
+ throw new MockCredentialRequiredError();
172
+ }
173
+ if (managedPingOutcome.kind === "throw") {
174
+ throw new Error(managedPingOutcome.message);
175
+ }
176
+ return {
177
+ status: managedPingOutcome.status,
178
+ headers: {},
179
+ body: {},
180
+ };
181
+ }
182
+ },
183
+ }));
184
+
84
185
  // ── Import under test ────────────────────────────────────────────────
85
186
 
86
187
  const { checkAllCredentials, checkCredentialForProvider, _setFetchFn } =
@@ -106,6 +207,7 @@ function addProvider(
106
207
  defaultScopes?: string[];
107
208
  pingUrl?: string | null;
108
209
  pingMethod?: string | null;
210
+ managedServiceConfigKey?: string;
109
211
  },
110
212
  ) {
111
213
  mockProviders.push({
@@ -115,6 +217,10 @@ function addProvider(
115
217
  pingMethod: opts?.pingMethod ?? null,
116
218
  pingHeaders: null,
117
219
  pingBody: null,
220
+ // checkManagedProvider reads this field via OAuthProviderRow; the
221
+ // health-check code branches to the managed path only when set AND
222
+ // when managedProviders includes the corresponding config key.
223
+ managedServiceConfigKey: opts?.managedServiceConfigKey,
118
224
  });
119
225
  }
120
226
 
@@ -159,6 +265,10 @@ describe("credential-health-service", () => {
159
265
  mockConnections = new Map();
160
266
  mockFetchResponse = { ok: true, status: 200 };
161
267
  mockFetchThrows = false;
268
+ mockRefreshOutcome = "ok";
269
+ managedProviders.clear();
270
+ managedListResponse = { ok: true, status: 200, body: { results: [] } };
271
+ managedPingOutcome = { kind: "status", status: 200 };
162
272
  });
163
273
 
164
274
  test("returns empty report when no providers exist", async () => {
@@ -238,8 +348,12 @@ describe("credential-health-service", () => {
238
348
  expect(report.results[0]!.canAutoRecover).toBe(false);
239
349
  });
240
350
 
241
- test("returns expiring when token is past expiresAt with refresh token", async () => {
242
- addProvider("google");
351
+ test("returns healthy when expired token has a refresh token, no ping URL, and refresh succeeds via the ping path", async () => {
352
+ // Without a pingUrl there's no opportunity to exercise the refresh —
353
+ // the connection is reported as healthy on the trust that the next
354
+ // real API call will refresh transparently. This matches the contract
355
+ // for connections that have a refresh token but no liveness endpoint.
356
+ addProvider("google"); // no pingUrl
243
357
  addConnection("google", "conn-1", {
244
358
  expiresAt: Date.now() - 60_000, // 1 minute ago
245
359
  hasRefreshToken: true,
@@ -247,10 +361,61 @@ describe("credential-health-service", () => {
247
361
  setToken("conn-1");
248
362
 
249
363
  const report = await checkAllCredentials();
250
- expect(report.results[0]!.status).toBe("expiring");
364
+ expect(report.results[0]!.status).toBe("healthy");
251
365
  expect(report.results[0]!.canAutoRecover).toBe(true);
252
366
  });
253
367
 
368
+ test("expired BYO token + successful refresh + 200 ping → healthy", async () => {
369
+ // The withValidToken mock returns "refreshed" by default; pingProvider
370
+ // sees a fresh token and the upstream returns 200.
371
+ mockFetchResponse = { ok: true, status: 200 };
372
+ addProvider("google", {
373
+ pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
374
+ });
375
+ addConnection("google", "conn-1", {
376
+ expiresAt: Date.now() - 60_000,
377
+ hasRefreshToken: true,
378
+ });
379
+ setToken("conn-1");
380
+
381
+ const report = await checkAllCredentials();
382
+ expect(report.results[0]!.status).toBe("healthy");
383
+ });
384
+
385
+ test("expired BYO token + refresh fails (revoked refresh token) → revoked", async () => {
386
+ mockRefreshOutcome = "refresh_failed";
387
+ addProvider("google", {
388
+ pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
389
+ });
390
+ addConnection("google", "conn-1", {
391
+ expiresAt: Date.now() - 60_000,
392
+ hasRefreshToken: true,
393
+ });
394
+ setToken("conn-1");
395
+
396
+ const report = await checkAllCredentials();
397
+ expect(report.results[0]!.status).toBe("revoked");
398
+ expect(report.results[0]!.canAutoRecover).toBe(false);
399
+ });
400
+
401
+ test("BYO ping that still returns 401 after a refresh attempt → revoked", async () => {
402
+ // Refresh succeeds (mockRefreshOutcome stays "ok") but the upstream
403
+ // still returns 401 — the token was genuinely revoked by the provider.
404
+ mockFetchResponse = { ok: false, status: 401 };
405
+ addProvider("google", {
406
+ pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
407
+ });
408
+ addConnection("google", "conn-1", {
409
+ expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000,
410
+ hasRefreshToken: true,
411
+ });
412
+ setToken("conn-1");
413
+
414
+ const report = await checkAllCredentials();
415
+ expect(report.results[0]!.status).toBe("revoked");
416
+ expect(report.results[0]!.details).toContain("after a refresh attempt");
417
+ });
418
+
254
419
  test("returns expiring when token expires within 7 days without refresh token", async () => {
255
420
  addProvider("google");
256
421
  addConnection("google", "conn-1", {
@@ -459,6 +624,90 @@ describe("credential-health-service", () => {
459
624
  });
460
625
  });
461
626
 
627
+ describe("managed-provider path", () => {
628
+ // Distinguishing managed-path failure modes: a 424 from the platform
629
+ // proxy (CredentialRequiredError) means the platform tried and gave up
630
+ // on refresh — actionable, fire reconnect alert. A bare 401/403 from
631
+ // the upstream provider could be a transient platform-side miss (proxy
632
+ // didn't refresh-before-forward) and should NOT fire reconnect alerts
633
+ // without further evidence — demote to ping_failed.
634
+
635
+ function setupManagedGoogle(opts?: { hasActiveConnection?: boolean }) {
636
+ addProvider("google", {
637
+ pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
638
+ managedServiceConfigKey: "google-oauth",
639
+ });
640
+ managedProviders.add("google-oauth");
641
+ if (opts?.hasActiveConnection !== false) {
642
+ managedListResponse = {
643
+ ok: true,
644
+ status: 200,
645
+ body: {
646
+ results: [
647
+ {
648
+ id: "platform-conn-1",
649
+ account_label: "user@example.com",
650
+ status: "ACTIVE",
651
+ },
652
+ ],
653
+ },
654
+ };
655
+ }
656
+ }
657
+
658
+ test("managed 2xx ping → healthy", async () => {
659
+ setupManagedGoogle();
660
+ managedPingOutcome = { kind: "status", status: 200 };
661
+
662
+ const report = await checkAllCredentials();
663
+ const google = report.results.find((r) => r.provider === "google");
664
+ expect(google!.status).toBe("healthy");
665
+ });
666
+
667
+ test("managed 424 (CredentialRequiredError) → revoked", async () => {
668
+ setupManagedGoogle();
669
+ managedPingOutcome = { kind: "credential_required" };
670
+
671
+ const report = await checkAllCredentials();
672
+ const google = report.results.find((r) => r.provider === "google");
673
+ expect(google!.status).toBe("revoked");
674
+ expect(google!.canAutoRecover).toBe(false);
675
+ expect(google!.details).toContain("cannot be refreshed");
676
+ });
677
+
678
+ test("managed 401 from upstream → ping_failed, not revoked", async () => {
679
+ // This is the key change: previously this would fire a user-facing
680
+ // reconnect alert (because "revoked" is in hardFailureStatuses). Now
681
+ // we treat upstream 401 as potentially transient — only a 424 from
682
+ // the platform proxy is trusted enough to trigger the alert.
683
+ setupManagedGoogle();
684
+ managedPingOutcome = { kind: "status", status: 401 };
685
+
686
+ const report = await checkAllCredentials();
687
+ const google = report.results.find((r) => r.provider === "google");
688
+ expect(google!.status).toBe("ping_failed");
689
+ expect(google!.status).not.toBe("revoked");
690
+ });
691
+
692
+ test("managed 403 from upstream → ping_failed, not revoked", async () => {
693
+ setupManagedGoogle();
694
+ managedPingOutcome = { kind: "status", status: 403 };
695
+
696
+ const report = await checkAllCredentials();
697
+ const google = report.results.find((r) => r.provider === "google");
698
+ expect(google!.status).toBe("ping_failed");
699
+ });
700
+
701
+ test("managed 500 from upstream → ping_failed", async () => {
702
+ setupManagedGoogle();
703
+ managedPingOutcome = { kind: "status", status: 500 };
704
+
705
+ const report = await checkAllCredentials();
706
+ const google = report.results.find((r) => r.provider === "google");
707
+ expect(google!.status).toBe("ping_failed");
708
+ });
709
+ });
710
+
462
711
  describe("checkCredentialForProvider", () => {
463
712
  test("returns null when no connections exist", async () => {
464
713
  const result = await checkCredentialForProvider("google");
@@ -41,8 +41,8 @@ afterAll(() => {
41
41
  mock.restore();
42
42
  });
43
43
 
44
- import { _setStorePath } from "../security/encrypted-store.js";
45
44
  import { _resetBackend } from "../security/secure-keys.js";
45
+ import { setStorePathForTesting } from "./encrypted-store-test-helpers.js";
46
46
 
47
47
  const TEST_DIR = join(
48
48
  tmpdir(),
@@ -409,7 +409,7 @@ describe("Invariant 4: credentials only used for allowed purpose", () => {
409
409
 
410
410
  beforeEach(() => {
411
411
  mkdirSync(TEST_DIR, { recursive: true });
412
- _setStorePath(STORE_PATH);
412
+ setStorePathForTesting(STORE_PATH);
413
413
  _resetBackend();
414
414
  _setMetadataPath(join(TEST_DIR, "metadata.json"));
415
415
  broker = new CredentialBroker();
@@ -417,7 +417,7 @@ describe("Invariant 4: credentials only used for allowed purpose", () => {
417
417
 
418
418
  afterEach(() => {
419
419
  _setMetadataPath(null);
420
- _setStorePath(null);
420
+ setStorePathForTesting(null);
421
421
  _resetBackend();
422
422
  rmSync(TEST_DIR, { recursive: true, force: true });
423
423
  });
@@ -497,14 +497,14 @@ describe("Invariant 4: credentials only used for allowed purpose", () => {
497
497
  describe("Invariant 6: oauth2ClientSecret not in metadata, only in secure store", () => {
498
498
  beforeEach(() => {
499
499
  mkdirSync(TEST_DIR, { recursive: true });
500
- _setStorePath(STORE_PATH);
500
+ setStorePathForTesting(STORE_PATH);
501
501
  _resetBackend();
502
502
  _setMetadataPath(join(TEST_DIR, "metadata.json"));
503
503
  });
504
504
 
505
505
  afterEach(() => {
506
506
  _setMetadataPath(null);
507
- _setStorePath(null);
507
+ setStorePathForTesting(null);
508
508
  _resetBackend();
509
509
  rmSync(TEST_DIR, { recursive: true, force: true });
510
510
  });