@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
@@ -9,6 +9,7 @@ import { getLogger } from "../util/logger.js";
9
9
  import { BYOOAuthConnection } from "./byo-connection.js";
10
10
  import type { OAuthConnection } from "./connection.js";
11
11
  import { getConnectionAccessTokenResult } from "./credential-token-resolver.js";
12
+ import { syncManualTokenConnection } from "./manual-token-connection.js";
12
13
  import { getActiveConnection, getProvider } from "./oauth-store.js";
13
14
  import { PlatformOAuthConnection } from "./platform-connection.js";
14
15
 
@@ -82,6 +83,10 @@ export async function resolveOAuthConnection(
82
83
  }
83
84
 
84
85
  // BYO path — requires a local connection row, access token, and base URL.
86
+ if (providerRow?.authorizeUrl === "urn:manual-token") {
87
+ await syncManualTokenConnection(provider);
88
+ }
89
+
85
90
  const conn = getActiveConnection(provider, { clientId, account });
86
91
  if (!conn) {
87
92
  const filters = [
@@ -185,20 +190,26 @@ interface ResolvePlatformConnectionIdOptions {
185
190
  account?: string;
186
191
  }
187
192
 
193
+ interface PlatformConnectionEntry {
194
+ id: string;
195
+ account_label?: string | null;
196
+ }
197
+
188
198
  /**
189
- * Fetch the platform-side connection ID for a managed provider by calling
190
- * the List Connections endpoint.
199
+ * Fetch active platform connections for a managed provider by calling the
200
+ * List Connections endpoint.
191
201
  */
192
- async function resolvePlatformConnectionId(
193
- options: ResolvePlatformConnectionIdOptions,
194
- ): Promise<string> {
195
- const { client, provider, account } = options;
196
-
202
+ async function fetchPlatformConnections(options: {
203
+ client: VellumPlatformClient;
204
+ provider: string;
205
+ accountIdentifier?: string;
206
+ }): Promise<PlatformConnectionEntry[]> {
207
+ const { client, provider, accountIdentifier } = options;
197
208
  const params = new URLSearchParams();
198
209
  params.set("provider", provider);
199
210
  params.set("status", "ACTIVE");
200
- if (account) {
201
- params.set("account_identifier", account);
211
+ if (accountIdentifier) {
212
+ params.set("account_identifier", accountIdentifier);
202
213
  }
203
214
 
204
215
  const path = `/v1/assistants/${client.platformAssistantId}/oauth/connections/?${params.toString()}`;
@@ -219,7 +230,35 @@ async function resolvePlatformConnectionId(
219
230
  Array.isArray(body)
220
231
  ? body
221
232
  : ((body as Record<string, unknown>).results ?? [])
222
- ) as Array<{ id: string; account_label?: string }>;
233
+ ) as PlatformConnectionEntry[];
234
+ return connections;
235
+ }
236
+
237
+ /**
238
+ * Fetch the platform-side connection ID for a managed provider by calling
239
+ * the List Connections endpoint.
240
+ */
241
+ async function resolvePlatformConnectionId(
242
+ options: ResolvePlatformConnectionIdOptions,
243
+ ): Promise<string> {
244
+ const { client, provider, account } = options;
245
+
246
+ let connections = await fetchPlatformConnections({
247
+ client,
248
+ provider,
249
+ accountIdentifier: account,
250
+ });
251
+
252
+ if (account && connections.length === 0) {
253
+ const unfilteredConnections = await fetchPlatformConnections({
254
+ client,
255
+ provider,
256
+ });
257
+ connections = unfilteredConnections.filter(
258
+ (connection) =>
259
+ connection.account_label === account || connection.id === account,
260
+ );
261
+ }
223
262
 
224
263
  if (connections.length === 0) {
225
264
  throw new Error(
@@ -10,6 +10,7 @@
10
10
 
11
11
  import { credentialKey } from "../security/credential-key.js";
12
12
  import { getSecureKeyResultAsync } from "../security/secure-keys.js";
13
+ import { getTelegramBotUsername } from "../telegram/bot-username.js";
13
14
  import { getLogger } from "../util/logger.js";
14
15
  import {
15
16
  createConnection,
@@ -24,6 +25,44 @@ const log = getLogger("manual-token-connection");
24
25
  /** Sentinel client_id used for non-OAuth providers that don't have a real app. */
25
26
  const MANUAL_TOKEN_CLIENT_ID = "manual-config";
26
27
 
28
+ type ResolvedAccountInfoSource = "provided" | "derived" | "none";
29
+
30
+ interface ResolvedAccountInfo {
31
+ value?: string;
32
+ source: ResolvedAccountInfoSource;
33
+ }
34
+
35
+ function resolveManualTokenAccountInfo(
36
+ provider: string,
37
+ accountInfo?: string,
38
+ ): ResolvedAccountInfo {
39
+ if (accountInfo !== undefined) {
40
+ return { value: accountInfo, source: "provided" };
41
+ }
42
+
43
+ if (provider === "telegram") {
44
+ const botUsername = getTelegramBotUsername();
45
+ if (!botUsername) return { source: "none" };
46
+ return {
47
+ value: botUsername.startsWith("@") ? botUsername : `@${botUsername}`,
48
+ source: "derived",
49
+ };
50
+ }
51
+
52
+ return { source: "none" };
53
+ }
54
+
55
+ function accountInfoForManualTokenSync(
56
+ provider: string,
57
+ resolved: ResolvedAccountInfo,
58
+ ): string | undefined {
59
+ if (resolved.source !== "derived") return resolved.value;
60
+
61
+ const existing = getConnectionByProvider(provider);
62
+ if (existing?.accountInfo) return undefined;
63
+ return resolved.value;
64
+ }
65
+
27
66
  /**
28
67
  * Ensure an active oauth_connection row exists for the given manual-token
29
68
  * provider. Creates the synthetic oauth_app row on first use.
@@ -79,6 +118,15 @@ export async function syncManualTokenConnection(
79
118
  provider: string,
80
119
  accountInfo?: string,
81
120
  ): Promise<void> {
121
+ const resolvedAccountInfo = resolveManualTokenAccountInfo(
122
+ provider,
123
+ accountInfo,
124
+ );
125
+ const accountInfoToStore = accountInfoForManualTokenSync(
126
+ provider,
127
+ resolvedAccountInfo,
128
+ );
129
+
82
130
  switch (provider) {
83
131
  case "telegram": {
84
132
  const botTokenResult = await getSecureKeyResultAsync(
@@ -94,7 +142,7 @@ export async function syncManualTokenConnection(
94
142
  return;
95
143
  }
96
144
  if (botTokenResult.value && webhookSecretResult.value) {
97
- await ensureManualTokenConnection(provider, accountInfo);
145
+ await ensureManualTokenConnection(provider, accountInfoToStore);
98
146
  } else {
99
147
  removeManualTokenConnection(provider);
100
148
  }
@@ -115,7 +163,7 @@ export async function syncManualTokenConnection(
115
163
  return;
116
164
  }
117
165
  if (botTokenResult.value && appTokenResult.value) {
118
- await ensureManualTokenConnection(provider, accountInfo);
166
+ await ensureManualTokenConnection(provider, accountInfoToStore);
119
167
  } else {
120
168
  removeManualTokenConnection(provider);
121
169
  }
@@ -133,7 +181,7 @@ export async function syncManualTokenConnection(
133
181
  return;
134
182
  }
135
183
  if (tokenResult.value) {
136
- await ensureManualTokenConnection(provider, accountInfo);
184
+ await ensureManualTokenConnection(provider, accountInfoToStore);
137
185
  } else {
138
186
  removeManualTokenConnection(provider);
139
187
  }
@@ -199,6 +199,9 @@ export const PROVIDER_SEED_DATA: Record<
199
199
  },
200
200
  ],
201
201
  appType: "Public integration",
202
+ setupNotes: [
203
+ "Enable Token Rotation on your Notion integration (developer dashboard → your integration → Configuration → Token rotation). Without it, Notion does not issue a refresh token and the connection cannot auto-recover if Notion revokes the access token server-side — you will silently lose access and need to reconnect manually.",
204
+ ],
202
205
  identityUrl: "https://api.notion.com/v1/users/me",
203
206
  identityHeaders: { "Notion-Version": "2022-06-28" },
204
207
  identityResponsePaths: ["name", "person.email"],
@@ -334,6 +334,19 @@ describe("no rule — third-party skill tool", () => {
334
334
  expect(result.reason).toContain("Skill tool");
335
335
  });
336
336
 
337
+ test("plugin origin → treated as extension-owned (prompt at strict threshold)", () => {
338
+ // Plugins join skills in the "extension-owned" bucket — both prompt by
339
+ // default. `isSkillBundled` is irrelevant for plugins (always false).
340
+ const result = evaluate({
341
+ riskLevel: RiskLevel.Low,
342
+ toolName: "custom_plugin_tool",
343
+ toolOrigin: "plugin",
344
+ autoApproveUpTo: "none",
345
+ });
346
+ expect(result.decision).toBe("prompt");
347
+ expect(result.reason).toContain("Skill tool");
348
+ });
349
+
337
350
  test("no tool origin but hasManifestOverride, strict threshold → prompt (unregistered skill tool)", () => {
338
351
  const result = evaluate({
339
352
  riskLevel: RiskLevel.Low,
@@ -699,16 +712,17 @@ describe("edge cases", () => {
699
712
  expect(result.reason).toContain("Skill tool");
700
713
  });
701
714
 
702
- test("hasManifestOverride with toolOrigin set to builtin — falls through (not a skill)", () => {
715
+ test("hasManifestOverride with toolOrigin=mcp — falls through (not extension-owned)", () => {
703
716
  const result = evaluate({
704
717
  riskLevel: RiskLevel.Low,
705
718
  toolName: "manifest_tool",
706
- toolOrigin: "builtin",
719
+ toolOrigin: "mcp",
707
720
  hasManifestOverride: true,
708
721
  });
709
- // toolOrigin is "builtin", so the third-party skill check doesn't trigger.
710
- // The hasManifestOverride check requires !toolOrigin, but toolOrigin is set.
711
- // Falls through to risk-based: Low allow (within default "low" threshold).
722
+ // toolOrigin is "mcp", which is not extension-class (skill/plugin), so
723
+ // the third-party skill check doesn't trigger. The hasManifestOverride
724
+ // sub-check requires !toolOrigin, but toolOrigin is set. Falls through
725
+ // to risk-based: Low → allow (within default "low" threshold).
712
726
  expect(result.decision).toBe("allow");
713
727
  expect(result.reason).toContain("low risk");
714
728
  });
@@ -1,3 +1,4 @@
1
+ import type { OwnerKind } from "../tools/types.js";
1
2
  import type { TrustRule } from "./types.js";
2
3
  import { RiskLevel } from "./types.js";
3
4
 
@@ -13,8 +14,13 @@ export interface ApprovalContext {
13
14
  matchedRule?: TrustRule;
14
15
  isContainerized: boolean;
15
16
  isWorkspaceScoped: boolean;
16
- /** Where the tool originates from — "skill" for skill-provided tools, "builtin" for core tools. */
17
- toolOrigin?: "skill" | "builtin";
17
+ /**
18
+ * Owner kind of the tool, as recorded by the tool registry — "skill" /
19
+ * "plugin" / "mcp" for extension-owned tools, `undefined` for core tools
20
+ * (and for tools that aren't registered, e.g. unregistered skill tools
21
+ * matched only via `hasManifestOverride`).
22
+ */
23
+ toolOrigin?: OwnerKind;
18
24
  /** Whether the tool's owning skill is a first-party bundled skill. */
19
25
  isSkillBundled?: boolean;
20
26
  /** Whether the tool has a manifest override (unregistered skill tool). */
@@ -173,8 +179,13 @@ export class DefaultApprovalPolicy implements ApprovalPolicy {
173
179
 
174
180
  // ── 6. No rule + third-party skill tool → prompt (unless threshold covers it)
175
181
  if (!matchedRule) {
182
+ // Plugin- and skill-owned tools are both treated as extension-class
183
+ // for approval purposes: external by default, prompt unless bundled.
184
+ // MCP-owned tools fall through to the core risk-based path.
185
+ const isExtensionOwned =
186
+ toolOrigin === "skill" || toolOrigin === "plugin";
176
187
  const isThirdPartySkill =
177
- (toolOrigin === "skill" && !isSkillBundled) ||
188
+ (isExtensionOwned && !isSkillBundled) ||
178
189
  (hasManifestOverride && !toolOrigin);
179
190
  if (isThirdPartySkill) {
180
191
  if (isRiskWithinThreshold(riskLevel, context.autoApproveUpTo)) {
@@ -15,7 +15,8 @@ import {
15
15
  looksLikeHostPortShorthand,
16
16
  looksLikePathOnlyInput,
17
17
  } from "../tools/network/url-safety.js";
18
- import { getTool } from "../tools/registry.js";
18
+ import { getTool, getToolOwner } from "../tools/registry.js";
19
+ import type { Tool } from "../tools/types.js";
19
20
  import {
20
21
  getDeprecatedDir,
21
22
  getProtectedDir,
@@ -150,6 +151,23 @@ function resolveSkillIdAndHash(
150
151
  }
151
152
  }
152
153
 
154
+ /**
155
+ * Resolve whether the skill that owns this tool is bundled (first-party).
156
+ * Returns false when the tool has no owning skill or the skill is not in
157
+ * the catalog. Derived from `loadSkillCatalog()` at check time so the
158
+ * answer reflects current catalog truth (managed overrides flip the bit
159
+ * without needing to re-register tools). Owner is looked up from the tool
160
+ * registry (`getToolOwner(name)`) rather than read from the `Tool` object,
161
+ * since ownership lives on the registry, not on the tool itself.
162
+ */
163
+ function isToolOwnerSkillBundled(tool: Tool | undefined): boolean {
164
+ if (!tool) return false;
165
+ const owner = getToolOwner(tool.name);
166
+ if (owner?.kind !== "skill") return false;
167
+ const skill = loadSkillCatalog().find((s) => s.id === owner.id);
168
+ return skill?.bundled ?? false;
169
+ }
170
+
153
171
  /**
154
172
  * Check whether a skill (by id) has parsed inline command expansions.
155
173
  * Returns false when the skill is not found in the catalog.
@@ -529,13 +547,8 @@ export async function check(
529
547
  risk === RiskLevel.Low
530
548
  ? isWorkspaceScopedInvocation(toolName, input, workingDir)
531
549
  : false,
532
- toolOrigin:
533
- tool?.origin === "skill" || tool?.origin === "plugin"
534
- ? "skill"
535
- : tool
536
- ? "builtin"
537
- : undefined,
538
- isSkillBundled: tool?.ownerSkillBundled ?? false,
550
+ toolOrigin: getToolOwner(toolName)?.kind,
551
+ isSkillBundled: isToolOwnerSkillBundled(tool),
539
552
  hasManifestOverride: !!manifestOverride,
540
553
  autoApproveUpTo: threshold,
541
554
  hasSandboxAutoApprove,
@@ -10,6 +10,7 @@ let mockManagedProxyCtx = {
10
10
  assistantApiKey: "",
11
11
  };
12
12
  let mockAssistantId = "";
13
+ let mockSecureKeys: Record<string, string | null | undefined> = {};
13
14
 
14
15
  // ---------------------------------------------------------------------------
15
16
  // Module mocks
@@ -26,7 +27,7 @@ mock.module("../config/env.js", () => ({
26
27
  // Stub the credential-store fallback so tests stay hermetic and do not
27
28
  // read real values from the host credential backend.
28
29
  mock.module("../security/secure-keys.js", () => ({
29
- getSecureKeyAsync: async () => null,
30
+ getSecureKeyAsync: async (key: string) => mockSecureKeys[key] ?? null,
30
31
  }));
31
32
 
32
33
  mock.module("../security/credential-key.js", () => ({
@@ -54,6 +55,7 @@ describe("VellumPlatformClient", () => {
54
55
  assistantApiKey: "sk-test-key",
55
56
  };
56
57
  mockAssistantId = "asst-123";
58
+ mockSecureKeys = {};
57
59
  });
58
60
 
59
61
  afterEach(() => {
@@ -94,6 +96,27 @@ describe("VellumPlatformClient", () => {
94
96
  const client = await VellumPlatformClient.create();
95
97
  expect(client!.baseUrl).toBe("https://platform.example.com");
96
98
  });
99
+
100
+ test("falls back to credential store values when managed context is not rehydrated", async () => {
101
+ mockManagedProxyCtx = {
102
+ enabled: false,
103
+ platformBaseUrl: "",
104
+ assistantApiKey: "",
105
+ };
106
+ mockAssistantId = "";
107
+ mockSecureKeys = {
108
+ "vellum:platform_base_url": "https://stored-platform.example.com/",
109
+ "vellum:assistant_api_key": "stored-api-key",
110
+ "vellum:platform_assistant_id": " stored-assistant-id ",
111
+ };
112
+
113
+ const client = await VellumPlatformClient.create();
114
+
115
+ expect(client).not.toBeNull();
116
+ expect(client!.baseUrl).toBe("https://stored-platform.example.com");
117
+ expect(client!.assistantApiKey).toBe("stored-api-key");
118
+ expect(client!.platformAssistantId).toBe("stored-assistant-id");
119
+ });
97
120
  });
98
121
 
99
122
  describe("fetch()", () => {
@@ -10,6 +10,7 @@ import { resolveManagedProxyContext } from "../providers/platform-proxy/context.
10
10
  import { credentialKey } from "../security/credential-key.js";
11
11
  import { getSecureKeyAsync } from "../security/secure-keys.js";
12
12
  import { getLogger } from "../util/logger.js";
13
+ import { arePlatformFeaturesEnabled } from "./feature-gate.js";
13
14
 
14
15
  const log = getLogger("platform-client");
15
16
 
@@ -43,6 +44,13 @@ export class VellumPlatformClient {
43
44
  * should check `platformAssistantId` themselves.
44
45
  */
45
46
  static async create(): Promise<VellumPlatformClient | null> {
47
+ if (!arePlatformFeaturesEnabled()) {
48
+ log.debug(
49
+ "platform-features-in-local-mode is disabled — returning null",
50
+ );
51
+ return null;
52
+ }
53
+
46
54
  const ctx = await resolveManagedProxyContext();
47
55
 
48
56
  let baseUrl = ctx.enabled ? ctx.platformBaseUrl : "";
@@ -0,0 +1,15 @@
1
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
2
+ import { getIsPlatform } from "../config/env-registry.js";
3
+ import type { AssistantConfig } from "../config/schema.js";
4
+
5
+ const FLAG_KEY = "platform-features-in-local-mode" as const;
6
+
7
+ export function arePlatformFeaturesEnabled(
8
+ config?: AssistantConfig,
9
+ ): boolean {
10
+ if (getIsPlatform()) return true;
11
+ return isAssistantFeatureFlagEnabled(
12
+ FLAG_KEY,
13
+ (config ?? {}) as AssistantConfig,
14
+ );
15
+ }
@@ -48,7 +48,6 @@
48
48
 
49
49
  import { resolve } from "node:path";
50
50
 
51
- import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
52
51
  import { getConfig } from "../../config/loader.js";
53
52
  import { getInContextPkbPaths } from "../../daemon/pkb-context-tracker.js";
54
53
  import { buildPkbReminder } from "../../daemon/pkb-reminder-builder.js";
@@ -107,24 +106,19 @@ function readInjectionInputs(ctx: TurnContext): TurnInjectionInputs {
107
106
  }
108
107
 
109
108
  export const DISK_PRESSURE_WARNING_PROMPT = `<disk_pressure_warning>
110
- Disk usage is critically low: this assistant is in storage cleanup mode because the workspace volume is at least 95% full.
109
+ Disk usage is critically low: this assistant is in storage cleanup mode because the workspace volume is critically full.
111
110
 
112
111
  In your first paragraph, warn the user that storage is critically low and that normal work is suspended until space is freed.
113
112
 
114
113
  Then help the user clean up storage. Prefer safe inspection steps first, such as checking available space and finding large directories. Ask before deleting files or caches unless the user has already clearly approved the specific cleanup action.
115
114
 
116
- Do not work on unrelated tasks until disk usage drops below the critical threshold or the user explicitly overrides the lock. Background processes and messages from trusted contacts are blocked while this cleanup mode is active.
115
+ Do not work on unrelated tasks until enough space is freed to clear the lock or the user explicitly overrides it. Background processes and messages from trusted contacts are blocked while this cleanup mode is active.
117
116
  </disk_pressure_warning>`;
118
117
 
119
- function isSafeStorageLimitsEnabled(): boolean {
120
- return isAssistantFeatureFlagEnabled("safe-storage-limits", getConfig());
121
- }
122
-
123
118
  const diskPressureWarningInjector: Injector = {
124
119
  name: "disk-pressure-warning",
125
120
  order: DEFAULT_INJECTOR_ORDER.diskPressureWarning,
126
121
  async produce(ctx: TurnContext): Promise<InjectionBlock | null> {
127
- if (!isSafeStorageLimitsEnabled()) return null;
128
122
  const inputs = readInjectionInputs(ctx);
129
123
  if (!inputs.diskPressureContext?.cleanupModeActive) return null;
130
124
  return {
@@ -15,12 +15,17 @@
15
15
  *
16
16
  * The terminal dispatches on the discriminated {@link PersistArgs.op} field:
17
17
  *
18
- * - `add` → {@link addMessage}, optionally followed by
19
- * {@link syncMessageToDisk} when `args.syncToDisk` is true.
20
- * - `update` → {@link updateMessageMetadata} (returns `void`, wrapped as
21
- * `{ op: "update" }`).
22
- * - `delete` → {@link deleteMessageById} (returns the segment/summary IDs
23
- * the caller must clean up out-of-band).
18
+ * - `add` → {@link addMessage}, optionally followed by
19
+ * {@link syncMessageToDisk} when `args.syncToDisk` is true.
20
+ * - `reserve` → {@link reserveMessage} pre-allocates an empty row
21
+ * for assistant anchor stamping.
22
+ * - `updateContent` → {@link updateMessageContent} overwrites an existing
23
+ * row's content (returns `void`, wrapped as
24
+ * `{ op: "updateContent" }`).
25
+ * - `update` → {@link updateMessageMetadata} (returns `void`, wrapped
26
+ * as `{ op: "update" }`).
27
+ * - `delete` → {@link deleteMessageById} (returns the segment/summary
28
+ * IDs the caller must clean up out-of-band).
24
29
  *
25
30
  * Manifest declares `provides.persistence: "v1"` so other plugins can
26
31
  * negotiate against the pipeline surface and `requires.pluginRuntime: "v1"`
@@ -36,6 +41,8 @@
36
41
  import {
37
42
  addMessage,
38
43
  deleteMessageById,
44
+ reserveMessage,
45
+ updateMessageContent,
39
46
  updateMessageMetadata,
40
47
  } from "../../memory/conversation-crud.js";
41
48
  import { syncMessageToDisk } from "../../memory/conversation-disk-view.js";
@@ -74,6 +81,18 @@ export async function defaultPersistenceTerminal(
74
81
  }
75
82
  return { op: "add", message };
76
83
  }
84
+ case "reserve": {
85
+ const message = await reserveMessage(
86
+ args.conversationId,
87
+ args.role,
88
+ args.metadata,
89
+ );
90
+ return { op: "reserve", message };
91
+ }
92
+ case "updateContent": {
93
+ updateMessageContent(args.messageId, args.content);
94
+ return { op: "updateContent" };
95
+ }
77
96
  case "update": {
78
97
  updateMessageMetadata(args.messageId, args.updates);
79
98
  return { op: "update" };
@@ -32,6 +32,7 @@ import type { RepairResult } from "../daemon/history-repair.js";
32
32
  import type { ServerMessage } from "../daemon/message-protocol.js";
33
33
  import type { PkbContextConversation } from "../daemon/pkb-context-tracker.js";
34
34
  import type { TrustContext } from "../daemon/trust-context.js";
35
+ import type { MessageRole } from "../memory/conversation-crud.js";
35
36
  import type { QdrantSparseVector } from "../memory/qdrant-client.js";
36
37
  import type {
37
38
  ContentBlock,
@@ -439,18 +440,25 @@ export interface OverflowReduceResult {
439
440
  * Pipeline arguments for `persistence` — a discriminated union over the
440
441
  * message-CRUD operations plugins may observe, redirect, or short-circuit:
441
442
  *
442
- * - `add` — append a new message (`addMessage`). Mirrors
443
- * `addMessage(conversationId, role, content, metadata?, opts?)`.
444
- * When `syncToDisk` is set, the default plugin also runs
445
- * {@link syncMessageToDisk} against the just-persisted row so
446
- * the JSONL disk view stays consistent. The `createdAtMs` field
447
- * carries the conversation's creation timestamp — needed to
448
- * resolve the disk-view directory path.
449
- * - `update` shallow-merge metadata into an existing message
450
- * (`updateMessageMetadata`). Returns `void`.
451
- * - `delete` remove a single message (`deleteMessageById`). Returns the
452
- * {@link DeletedMemoryIds}-shaped segment/summary IDs the caller
453
- * must clean up out-of-band.
443
+ * - `add` — append a new message (`addMessage`). Mirrors
444
+ * `addMessage(conversationId, role, content, metadata?, opts?)`.
445
+ * When `syncToDisk` is set, the default plugin also runs
446
+ * {@link syncMessageToDisk} against the just-persisted row
447
+ * so the JSONL disk view stays consistent. The
448
+ * `createdAtMs` field carries the conversation's creation
449
+ * timestamp — needed to resolve the disk-view directory.
450
+ * - `reserve` pre-allocate an empty assistant anchor row
451
+ * (`reserveMessage`) so the agent loop can stamp streaming
452
+ * events with stable identity before any content is
453
+ * produced. Returns the same row shape as `add`.
454
+ * - `updateContent` — overwrite the content of an existing message
455
+ * (`updateMessageContent`). Used to finalize a previously
456
+ * reserved row, and by consolidation paths.
457
+ * - `update` — shallow-merge metadata into an existing message
458
+ * (`updateMessageMetadata`). Returns `void`.
459
+ * - `delete` — remove a single message (`deleteMessageById`). Returns
460
+ * the {@link DeletedMemoryIds}-shaped segment/summary IDs
461
+ * the caller must clean up out-of-band.
454
462
  *
455
463
  * The discriminated `op` field lets plugin middleware narrow the union and
456
464
  * tailor behavior per-operation (e.g. "only observe deletes", "redirect
@@ -459,7 +467,7 @@ export interface OverflowReduceResult {
459
467
  export type PersistAddArgs = {
460
468
  readonly op: "add";
461
469
  readonly conversationId: string;
462
- readonly role: string;
470
+ readonly role: MessageRole;
463
471
  readonly content: string;
464
472
  readonly metadata?: Record<string, unknown>;
465
473
  readonly addOptions?: { readonly skipIndexing?: boolean };
@@ -473,6 +481,19 @@ export type PersistAddArgs = {
473
481
  readonly createdAtMs?: number;
474
482
  };
475
483
 
484
+ export type PersistReserveArgs = {
485
+ readonly op: "reserve";
486
+ readonly conversationId: string;
487
+ readonly role: MessageRole;
488
+ readonly metadata?: Record<string, unknown>;
489
+ };
490
+
491
+ export type PersistUpdateContentArgs = {
492
+ readonly op: "updateContent";
493
+ readonly messageId: string;
494
+ readonly content: string;
495
+ };
496
+
476
497
  export type PersistUpdateArgs = {
477
498
  readonly op: "update";
478
499
  readonly messageId: string;
@@ -486,6 +507,8 @@ export type PersistDeleteArgs = {
486
507
 
487
508
  export type PersistArgs =
488
509
  | PersistAddArgs
510
+ | PersistReserveArgs
511
+ | PersistUpdateContentArgs
489
512
  | PersistUpdateArgs
490
513
  | PersistDeleteArgs;
491
514
 
@@ -506,6 +529,25 @@ export type PersistAddResult = {
506
529
  };
507
530
  };
508
531
 
532
+ /**
533
+ * Result row returned by a `reserve` op — same row shape as `add` but with
534
+ * empty `content` (`"[]"`) and tagged distinctly so middleware can branch
535
+ * on intent.
536
+ */
537
+ export type PersistReserveResult = {
538
+ readonly op: "reserve";
539
+ readonly message: {
540
+ readonly id: string;
541
+ readonly conversationId: string;
542
+ readonly role: string;
543
+ readonly content: string;
544
+ readonly createdAt: number;
545
+ readonly metadata?: string;
546
+ };
547
+ };
548
+
549
+ export type PersistUpdateContentResult = { readonly op: "updateContent" };
550
+
509
551
  export type PersistUpdateResult = { readonly op: "update" };
510
552
 
511
553
  /** IDs of segments/summaries the caller must remove from Qdrant. */
@@ -517,6 +559,8 @@ export type PersistDeleteResult = {
517
559
 
518
560
  export type PersistResult =
519
561
  | PersistAddResult
562
+ | PersistReserveResult
563
+ | PersistUpdateContentResult
520
564
  | PersistUpdateResult
521
565
  | PersistDeleteResult;
522
566
 
@@ -163,6 +163,7 @@ mock.module("../memory/conversation-crud.js", () => ({
163
163
  addMessageCalls.push({ conversationId, role, content, metadata, opts });
164
164
  return { id: `msg-${addMessageCalls.length}` };
165
165
  },
166
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
166
167
  }));
167
168
 
168
169
  // emitNotificationSignal mock
@@ -58,7 +58,7 @@ mock.module("../../config/loader.js", () => ({
58
58
  setNestedValue: () => {},
59
59
  }));
60
60
 
61
- const { buildSystemPrompt, maybeReseedBootstrapForCohort } =
61
+ const { buildSystemPrompt, maybeReseedBootstrap } =
62
62
  await import("../system-prompt.js");
63
63
 
64
64
  describe("buildSystemPrompt — tool routing guidance", () => {
@@ -73,12 +73,12 @@ describe("buildSystemPrompt — tool routing guidance", () => {
73
73
  });
74
74
  });
75
75
 
76
- describe("maybeReseedBootstrapForCohort — content-automation template", () => {
76
+ describe("maybeReseedBootstrap — content-automation template", () => {
77
77
  const templatesDir = join(import.meta.dirname!, "..", "templates");
78
78
 
79
79
  beforeEach(() => {
80
80
  mkdirSync(TEST_DIR, { recursive: true });
81
- // Seed the workspace with the generic BOOTSTRAP.md so the cohort
81
+ // Seed the workspace with the generic BOOTSTRAP.md so the bootstrap
82
82
  // reseed detects it as an unmodified template and overwrites it.
83
83
  copyFileSync(
84
84
  join(templatesDir, "BOOTSTRAP.md"),
@@ -87,7 +87,7 @@ describe("maybeReseedBootstrapForCohort — content-automation template", () =>
87
87
  });
88
88
 
89
89
  function reseedAndRead(): string {
90
- maybeReseedBootstrapForCohort("content-automation");
90
+ maybeReseedBootstrap("BOOTSTRAP-CONTENT-AUTOMATION.md");
91
91
  return readFileSync(join(TEST_DIR, "BOOTSTRAP.md"), "utf-8");
92
92
  }
93
93