@vellumai/assistant 0.10.9 → 0.10.10-dev.202607162206.d08e98e

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 (456) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +8 -0
  3. package/docs/activation-funnel-telemetry.md +13 -7
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  9. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  12. package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  13. package/openapi.yaml +530 -107
  14. package/package.json +1 -1
  15. package/scripts/generate-openapi.ts +8 -0
  16. package/src/__tests__/activation-early-marking.test.ts +6 -5
  17. package/src/__tests__/agent-loop-override-profile.test.ts +22 -25
  18. package/src/__tests__/agent-wake-override-profile.test.ts +21 -48
  19. package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
  20. package/src/__tests__/app-bundler.test.ts +4 -12
  21. package/src/__tests__/app-executors.test.ts +2 -49
  22. package/src/__tests__/app-routes-csp.test.ts +114 -146
  23. package/src/__tests__/auth-fallback-events-store.test.ts +8 -1
  24. package/src/__tests__/build-persisted-content.test.ts +96 -0
  25. package/src/__tests__/bundle-scanner.test.ts +27 -1
  26. package/src/__tests__/call-controller.test.ts +291 -0
  27. package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +165 -0
  28. package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
  29. package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
  30. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  31. package/src/__tests__/compactor-image-manifest-trust.test.ts +50 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +9 -4
  33. package/src/__tests__/config-schema-cmd.test.ts +10 -11
  34. package/src/__tests__/config-schema.test.ts +190 -257
  35. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +208 -0
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +5 -19
  37. package/src/__tests__/conversation-agent-loop.test.ts +22 -9
  38. package/src/__tests__/conversation-error.test.ts +31 -0
  39. package/src/__tests__/conversation-load-history-repair.test.ts +110 -0
  40. package/src/__tests__/conversation-process-callsite.test.ts +12 -19
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +7 -21
  42. package/src/__tests__/conversation-summarize-route.test.ts +36 -44
  43. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  44. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +7 -7
  45. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
  46. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -1
  47. package/src/__tests__/conversation-usage.test.ts +4 -12
  48. package/src/__tests__/credential-routes.test.ts +250 -0
  49. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  50. package/src/__tests__/db-migration-rollback.test.ts +22 -0
  51. package/src/__tests__/empty-response-hook.test.ts +186 -1
  52. package/src/__tests__/external-plugin-loader.test.ts +11 -5
  53. package/src/__tests__/heartbeat-service.test.ts +0 -28
  54. package/src/__tests__/host-shell-tool.test.ts +2 -0
  55. package/src/__tests__/inactive-tool-error-messages.test.ts +2 -2
  56. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
  57. package/src/__tests__/internal-telemetry-routes.test.ts +23 -4
  58. package/src/__tests__/invite-routes-http.test.ts +12 -16
  59. package/src/__tests__/list-all-apps.test.ts +0 -4
  60. package/src/__tests__/llm-context-resolution.test.ts +32 -56
  61. package/src/__tests__/llm-request-log-turn-query.test.ts +109 -0
  62. package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
  63. package/src/__tests__/llm-resolver.test.ts +342 -602
  64. package/src/__tests__/llm-schema.test.ts +79 -37
  65. package/src/__tests__/max-tokens-continue-hook.test.ts +19 -0
  66. package/src/__tests__/media-stream-output.test.ts +259 -3
  67. package/src/__tests__/media-stream-server-integration.test.ts +22 -1
  68. package/src/__tests__/media-stream-stt-session.test.ts +47 -0
  69. package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -0
  70. package/src/__tests__/memory-recall-log-store.test.ts +47 -13
  71. package/src/__tests__/mock-gateway-ipc.ts +46 -1
  72. package/src/__tests__/mtime-cache.test.ts +61 -0
  73. package/src/__tests__/navigate-settings-tab.test.ts +2 -0
  74. package/src/__tests__/normalize-onboarding.test.ts +33 -0
  75. package/src/__tests__/onboarding-persona-write.test.ts +26 -0
  76. package/src/__tests__/plugin-api-resolve-credential.test.ts +140 -0
  77. package/src/__tests__/plugin-app-serve-routes.test.ts +166 -14
  78. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -0
  79. package/src/__tests__/post-turn-tool-result-truncation.test.ts +38 -0
  80. package/src/__tests__/provider-commit-message-generator.test.ts +27 -20
  81. package/src/__tests__/provider-connections-backfill.test.ts +138 -0
  82. package/src/__tests__/provider-platform-proxy-integration.test.ts +10 -31
  83. package/src/__tests__/provider-registry-ollama.test.ts +8 -18
  84. package/src/__tests__/provider-send-message-override-profile.test.ts +23 -23
  85. package/src/__tests__/provider-usage-tracking.test.ts +9 -19
  86. package/src/__tests__/prune-old-conversations-job.test.ts +12 -0
  87. package/src/__tests__/published-app-updater.test.ts +22 -16
  88. package/src/__tests__/registry.test.ts +5 -16
  89. package/src/__tests__/retry-openrouter-only-normalization.test.ts +22 -23
  90. package/src/__tests__/retry-thinking-adaptive-only.test.ts +43 -48
  91. package/src/__tests__/retry-thinking-tool-choice.test.ts +57 -66
  92. package/src/__tests__/retry-verbosity-normalization.test.ts +24 -23
  93. package/src/__tests__/reveal-success-registry.test.ts +123 -0
  94. package/src/__tests__/run-conversation-turn-persistence.test.ts +130 -0
  95. package/src/__tests__/secret-fixtures.ts +9 -0
  96. package/src/__tests__/server-history-render.test.ts +28 -0
  97. package/src/__tests__/skills.test.ts +9 -4
  98. package/src/__tests__/slack-share-routes.test.ts +0 -1
  99. package/src/__tests__/stt-stream-session.test.ts +6 -5
  100. package/src/__tests__/subagent-call-site-routing.test.ts +69 -95
  101. package/src/__tests__/subagent-disposal.test.ts +2 -0
  102. package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
  103. package/src/__tests__/subagent-fork-spawn.test.ts +2 -0
  104. package/src/__tests__/subagent-manager-notify.test.ts +2 -0
  105. package/src/__tests__/subagent-role-registry.test.ts +37 -0
  106. package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
  107. package/src/__tests__/subagent-terminal-message.test.ts +50 -0
  108. package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -5
  109. package/src/__tests__/surface-completion-nudge-hook.test.ts +19 -0
  110. package/src/__tests__/telemetry-routes.test.ts +99 -19
  111. package/src/__tests__/tool-audit.test.ts +34 -4
  112. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  113. package/src/__tests__/tool-profiler.test.ts +72 -1
  114. package/src/__tests__/tool-result-spool.test.ts +49 -4
  115. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
  116. package/src/__tests__/ui-channel-variants.test.ts +108 -0
  117. package/src/__tests__/ui-shape-teaching.test.ts +255 -0
  118. package/src/__tests__/usage-attribution.test.ts +18 -41
  119. package/src/__tests__/user-plugin-loader.test.ts +4 -4
  120. package/src/__tests__/voice-config-update.test.ts +46 -0
  121. package/src/__tests__/voice-session-bridge.test.ts +216 -84
  122. package/src/__tests__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
  123. package/src/agent/loop.ts +4 -3
  124. package/src/api/events/open-conversation.test.ts +64 -0
  125. package/src/api/events/open-conversation.ts +33 -0
  126. package/src/api/index.ts +6 -0
  127. package/src/api/responses/conversation-message.ts +5 -0
  128. package/src/apps/app-store.ts +25 -35
  129. package/src/bundler/app-bundler.ts +34 -48
  130. package/src/bundler/app-compiler.ts +39 -4
  131. package/src/bundler/bundle-scanner.ts +13 -0
  132. package/src/bundler/manifest.ts +1 -1
  133. package/src/calls/__tests__/voice-session-bridge.test.ts +47 -0
  134. package/src/calls/call-constants.ts +5 -0
  135. package/src/calls/call-controller.ts +100 -32
  136. package/src/calls/call-transport.ts +9 -0
  137. package/src/calls/media-stream-output.ts +107 -4
  138. package/src/calls/media-stream-server.ts +29 -9
  139. package/src/calls/media-stream-stt-session.ts +7 -1
  140. package/src/calls/media-turn-detector.ts +11 -1
  141. package/src/calls/voice-session-bridge.ts +84 -63
  142. package/src/cli/commands/__tests__/inference-providers.test.ts +270 -33
  143. package/src/cli/commands/__tests__/notifications.test.ts +24 -3
  144. package/src/cli/commands/config.help.ts +6 -6
  145. package/src/cli/commands/credentials.help.ts +13 -0
  146. package/src/cli/commands/credentials.ts +6 -1
  147. package/src/cli/commands/email.help.ts +7 -0
  148. package/src/cli/commands/email.ts +35 -1
  149. package/src/cli/commands/inference-providers.ts +168 -107
  150. package/src/cli/commands/inference.help.ts +118 -46
  151. package/src/cli/commands/memory/index.help.ts +23 -0
  152. package/src/cli/commands/memory/nodes.ts +146 -0
  153. package/src/cli/commands/notifications.help.ts +10 -10
  154. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +40 -0
  155. package/src/cli/commands/oauth/connect-surface-guidance.ts +54 -0
  156. package/src/cli/commands/oauth/connect.test.ts +126 -0
  157. package/src/cli/commands/oauth/connect.ts +29 -6
  158. package/src/cli/commands/oauth/index.help.ts +7 -1
  159. package/src/cli/commands/oauth/status.test.ts +69 -3
  160. package/src/cli/commands/oauth/status.ts +50 -12
  161. package/src/cli/commands/plugins.help.ts +13 -2
  162. package/src/cli/commands/plugins.ts +61 -7
  163. package/src/cli/commands/telemetry.help.ts +13 -0
  164. package/src/cli/commands/telemetry.ts +45 -5
  165. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +8 -2
  166. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +213 -9
  167. package/src/cli/lib/bundled-marketplace.json +93 -36
  168. package/src/cli/lib/inspect-plugin.ts +5 -1
  169. package/src/cli/lib/install-from-github.ts +68 -4
  170. package/src/cli/lib/plugin-catalog-local.ts +6 -2
  171. package/src/cli/lib/plugin-fingerprint.ts +3 -3
  172. package/src/cli/lib/upgrade-plugin.ts +236 -35
  173. package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
  174. package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +137 -0
  175. package/src/config/__tests__/profile-materialization.test.ts +1 -88
  176. package/src/config/bundled-skills/AGENTS.md +3 -30
  177. package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
  178. package/src/config/bundled-skills/app-builder/TOOLS.json +23 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-open.ts +32 -0
  180. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  181. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
  182. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  183. package/src/config/bundled-skills/schedule/TOOLS.json +1 -1
  184. package/src/config/bundled-skills/settings/TOOLS.json +3 -1
  185. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +2 -0
  186. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +18 -10
  187. package/src/config/bundled-skills/subagent/SKILL.md +2 -0
  188. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  189. package/src/config/call-site-defaults.ts +3 -3
  190. package/src/config/feature-flag-registry.json +24 -39
  191. package/src/config/llm-resolver.ts +88 -475
  192. package/src/config/profile-materialization.ts +11 -11
  193. package/src/config/schema.ts +93 -127
  194. package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
  195. package/src/config/schemas/__tests__/stt.test.ts +31 -3
  196. package/src/config/schemas/live-voice.ts +10 -4
  197. package/src/config/schemas/llm.ts +52 -15
  198. package/src/config/schemas/memory-lifecycle.ts +1 -1
  199. package/src/config/schemas/memory-retrospective.ts +8 -0
  200. package/src/config/schemas/memory-v2.ts +2 -2
  201. package/src/config/schemas/memory-v3.ts +1 -1
  202. package/src/config/schemas/services.ts +6 -3
  203. package/src/config/schemas/stt.ts +38 -21
  204. package/src/config/schemas/tts.ts +13 -18
  205. package/src/config/skills.ts +31 -21
  206. package/src/context/compactor.ts +44 -7
  207. package/src/context/post-turn-tool-result-truncation.ts +4 -2
  208. package/src/context/tool-result-spool.ts +26 -5
  209. package/src/conversations/__tests__/message-consolidation.test.ts +48 -0
  210. package/src/conversations/message-consolidation.ts +22 -2
  211. package/src/daemon/__tests__/conversation-tool-setup.test.ts +5 -12
  212. package/src/daemon/app-source-watcher.ts +17 -23
  213. package/src/daemon/chat-credential-redaction.ts +1365 -0
  214. package/src/daemon/conversation-agent-loop-handlers.ts +627 -34
  215. package/src/daemon/conversation-agent-loop.ts +71 -31
  216. package/src/daemon/conversation-error.ts +36 -14
  217. package/src/daemon/conversation-process.ts +22 -0
  218. package/src/daemon/conversation-store.ts +35 -0
  219. package/src/daemon/conversation-surfaces.ts +25 -25
  220. package/src/daemon/conversation-tool-setup.ts +33 -7
  221. package/src/daemon/conversation.ts +55 -5
  222. package/src/daemon/handlers/shared.ts +33 -2
  223. package/src/daemon/lifecycle.ts +4 -4
  224. package/src/daemon/message-types/conversations.ts +6 -17
  225. package/src/daemon/providers-setup.ts +8 -0
  226. package/src/daemon/tool-setup-types.ts +7 -1
  227. package/src/daemon/wake-conversation-ops.ts +10 -1
  228. package/src/hooks/types.ts +9 -0
  229. package/src/ipc/__tests__/email-ipc.test.ts +90 -0
  230. package/src/ipc/gateway-client.test.ts +59 -0
  231. package/src/ipc/gateway-client.ts +70 -27
  232. package/src/live-voice/__tests__/live-voice-events.test.ts +14 -2
  233. package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
  234. package/src/live-voice/__tests__/live-voice-vad.test.ts +804 -13
  235. package/src/live-voice/__tests__/protocol.test.ts +122 -0
  236. package/src/live-voice/live-voice-session.ts +443 -40
  237. package/src/live-voice/protocol.ts +143 -1
  238. package/src/monitoring/__tests__/plugin-source-watch.test.ts +3 -1
  239. package/src/monitoring/plugin-source-watch.ts +3 -62
  240. package/src/notifications/README.md +1 -1
  241. package/src/permissions/checker.ts +8 -4
  242. package/src/persistence/__tests__/db-init-migrations-ok.test.ts +26 -0
  243. package/src/persistence/conversation-crud.ts +104 -2
  244. package/src/persistence/db-init.ts +14 -3
  245. package/src/persistence/job-handlers/cleanup.ts +15 -7
  246. package/src/persistence/llm-request-log-store.ts +88 -52
  247. package/src/persistence/migrations/298-move-memory-jobs-to-memory-db.ts +7 -31
  248. package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
  249. package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +8 -34
  250. package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +90 -0
  251. package/src/persistence/migrations/337-move-memory-recall-logs-to-memory-db.ts +114 -0
  252. package/src/persistence/migrations/338-move-memory-v3-selections-to-memory-db.ts +84 -0
  253. package/src/persistence/migrations/339-move-activation-sessions-to-memory-db.ts +52 -0
  254. package/src/persistence/migrations/__tests__/run-migrations.test.ts +155 -0
  255. package/src/persistence/migrations/helpers/relocation.ts +44 -1
  256. package/src/persistence/migrations/run-migrations.ts +25 -1
  257. package/src/persistence/schema/infrastructure.ts +9 -0
  258. package/src/persistence/schema/memory-core.ts +3 -0
  259. package/src/persistence/schema/memory-injection.ts +2 -0
  260. package/src/persistence/steps.ts +36 -0
  261. package/src/platform/client.test.ts +1 -44
  262. package/src/platform/client.ts +10 -20
  263. package/src/platform/consent-cache.test.ts +89 -23
  264. package/src/platform/consent-cache.ts +71 -33
  265. package/src/plugin-api/constants.ts +12 -0
  266. package/src/plugin-api/conversation-turn.ts +37 -14
  267. package/src/plugin-api/index.ts +11 -1
  268. package/src/plugin-api/resolve-credential.ts +75 -0
  269. package/src/plugin-api/vision-support.test.ts +8 -19
  270. package/src/plugin-api/vision-support.ts +26 -17
  271. package/src/plugins/collect-source-versions.ts +77 -0
  272. package/src/plugins/defaults/compaction/compact.ts +6 -0
  273. package/src/plugins/defaults/compaction/window-manager.ts +9 -0
  274. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +18 -24
  275. package/src/plugins/defaults/empty-response/hooks/user-prompt-submit.ts +38 -0
  276. package/src/plugins/defaults/empty-response/refusal-quarantine.ts +99 -0
  277. package/src/plugins/defaults/image-fallback/__tests__/caption-cache-persistence.test.ts +7 -3
  278. package/src/plugins/defaults/index.ts +8 -1
  279. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +4 -1
  280. package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +48 -6
  281. package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +28 -22
  282. package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +148 -0
  283. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +80 -8
  284. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +202 -0
  285. package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +64 -25
  286. package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +24 -11
  287. package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +70 -6
  288. package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +278 -0
  289. package/src/plugins/defaults/memory/activation-session-store.ts +27 -20
  290. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -9
  291. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -8
  292. package/src/plugins/defaults/memory/graph/retriever.test.ts +6 -6
  293. package/src/plugins/defaults/memory/graph/store.ts +114 -0
  294. package/src/plugins/defaults/memory/graph/tool-handlers.ts +36 -1
  295. package/src/plugins/defaults/memory/graph/tools.ts +47 -13
  296. package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +16 -35
  297. package/src/plugins/defaults/memory/jobs-worker.ts +10 -1
  298. package/src/plugins/defaults/memory/memory-db.ts +3 -2
  299. package/src/plugins/defaults/memory/memory-recall-log-store.ts +129 -66
  300. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +8 -0
  301. package/src/plugins/defaults/memory/memory-retrospective-job.ts +11 -122
  302. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
  303. package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +84 -46
  304. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +42 -32
  305. package/src/plugins/defaults/memory/path-containment.ts +21 -0
  306. package/src/plugins/defaults/memory/prompt-override.ts +51 -13
  307. package/src/plugins/defaults/memory/tools.test.ts +34 -0
  308. package/src/plugins/defaults/memory/tools.ts +12 -2
  309. package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +19 -15
  310. package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +24 -19
  311. package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +19 -15
  312. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +22 -2
  313. package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +5 -3
  314. package/src/plugins/defaults/memory/v2/harness/oracle.ts +59 -41
  315. package/src/plugins/defaults/memory/v2/harness/replay-input.ts +29 -25
  316. package/src/plugins/defaults/memory/v2/migration.ts +46 -16
  317. package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
  318. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +17 -15
  319. package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +24 -22
  320. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +9 -5
  321. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +277 -10
  322. package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +57 -5
  323. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +9 -7
  324. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +38 -7
  325. package/src/plugins/defaults/memory/v3/hot-set.test.ts +36 -18
  326. package/src/plugins/defaults/memory/v3/hot-set.ts +12 -14
  327. package/src/plugins/defaults/memory/v3/learned-edges.test.ts +47 -27
  328. package/src/plugins/defaults/memory/v3/learned-edges.ts +12 -14
  329. package/src/plugins/defaults/memory/v3/orchestrate.ts +139 -22
  330. package/src/plugins/defaults/memory/v3/prune.test.ts +10 -3
  331. package/src/plugins/defaults/memory/v3/prune.ts +17 -11
  332. package/src/plugins/defaults/memory/v3/selection-log-store.ts +23 -11
  333. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +70 -53
  334. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +9 -6
  335. package/src/plugins/external-plugin-loader.ts +15 -15
  336. package/src/plugins/mtime-cache.ts +47 -0
  337. package/src/plugins/pipeline.ts +9 -1
  338. package/src/plugins/plugin-execution-context.ts +44 -0
  339. package/src/plugins/plugin-tree-walk.ts +31 -24
  340. package/src/plugins/source-fingerprint.ts +3 -4
  341. package/src/prompts/normalize-onboarding.ts +12 -0
  342. package/src/prompts/persona-resolver.ts +8 -0
  343. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +1 -1
  344. package/src/providers/__tests__/dispatch-connection-routing.test.ts +6 -9
  345. package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
  346. package/src/providers/__tests__/retry-callsite.test.ts +235 -215
  347. package/src/providers/__tests__/satellite-connection-routing.test.ts +8 -16
  348. package/src/providers/atlascloud/client.ts +10 -49
  349. package/src/providers/baseten/client.ts +43 -0
  350. package/src/providers/call-site-routing.ts +26 -13
  351. package/src/providers/connection-resolution.ts +9 -11
  352. package/src/providers/fetch-provider-catalog.ts +4 -2
  353. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +29 -4
  354. package/src/providers/inference/__tests__/connection-availability-keyless.test.ts +78 -0
  355. package/src/providers/inference/adapter-factory.ts +27 -6
  356. package/src/providers/inference/auth.ts +29 -1
  357. package/src/providers/inference/backfill.ts +61 -10
  358. package/src/providers/inference/connection-availability.ts +3 -2
  359. package/src/providers/inference/resolve-auth.ts +9 -1
  360. package/src/providers/model-catalog.ts +37 -0
  361. package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
  362. package/src/providers/openai/api-key-validation.ts +70 -0
  363. package/src/providers/retry.ts +2 -0
  364. package/src/providers/types.ts +11 -4
  365. package/src/providers/vellum-model-routing.test.ts +26 -0
  366. package/src/providers/vellum-model-routing.ts +30 -0
  367. package/src/providers/voice-error-copy.ts +47 -0
  368. package/src/runtime/agent-wake.ts +40 -24
  369. package/src/runtime/for-chat-mint-registry.ts +118 -0
  370. package/src/runtime/reveal-nonce.ts +49 -0
  371. package/src/runtime/reveal-success-registry.ts +306 -0
  372. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +88 -3
  373. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -30
  374. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +7 -0
  375. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -0
  376. package/src/runtime/routes/app-management-routes.ts +152 -45
  377. package/src/runtime/routes/app-routes.ts +16 -77
  378. package/src/runtime/routes/canned-message-complete.ts +16 -16
  379. package/src/runtime/routes/conversation-management-routes.ts +17 -14
  380. package/src/runtime/routes/conversation-query-routes.ts +54 -6
  381. package/src/runtime/routes/conversation-routes.ts +48 -2
  382. package/src/runtime/routes/credential-routes.ts +116 -3
  383. package/src/runtime/routes/email-routes.ts +17 -1
  384. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +33 -5
  385. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +6 -5
  386. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -28
  387. package/src/runtime/routes/inference-send-routes.ts +1 -1
  388. package/src/runtime/routes/internal-telemetry-routes.ts +7 -6
  389. package/src/runtime/routes/plugins-routes.ts +37 -6
  390. package/src/runtime/routes/publish-routes.ts +15 -18
  391. package/src/runtime/routes/schedule-worker-routes.ts +9 -0
  392. package/src/runtime/routes/secret-routes.ts +10 -0
  393. package/src/runtime/routes/telemetry-routes.ts +149 -45
  394. package/src/schedule/__tests__/schedule-timezone.test.ts +101 -0
  395. package/src/schedule/__tests__/worker-watchdog.test.ts +209 -0
  396. package/src/schedule/schedule-store.ts +12 -1
  397. package/src/schedule/schedule-timezone.ts +63 -0
  398. package/src/schedule/scheduler.ts +100 -6
  399. package/src/schedule/worker-control.ts +19 -0
  400. package/src/security/auth-fallback-events-store.ts +7 -6
  401. package/src/security/secret-scanner.ts +26 -1
  402. package/src/services/published-app-updater.ts +6 -11
  403. package/src/stt/stt-stream-session.ts +36 -16
  404. package/src/subagent/manager.ts +34 -8
  405. package/src/telemetry/AGENTS.md +30 -1
  406. package/src/telemetry/__tests__/config-setting-snapshot.test.ts +18 -0
  407. package/src/telemetry/__tests__/outbox-test-harness.ts +5 -3
  408. package/src/telemetry/config-setting-snapshot.ts +44 -10
  409. package/src/telemetry/telemetry-event-sources.test.ts +124 -30
  410. package/src/telemetry/telemetry-event-sources.ts +137 -83
  411. package/src/telemetry/telemetry-events-outbox.test.ts +46 -1
  412. package/src/telemetry/telemetry-events-outbox.ts +60 -9
  413. package/src/telemetry/telemetry-wire-source.json +1 -1
  414. package/src/telemetry/telemetry-wire-validation.ts +39 -2
  415. package/src/telemetry/telemetry-wire.generated.ts +8 -0
  416. package/src/telemetry/tool-audit.ts +15 -9
  417. package/src/telemetry/tool-executed-events-store.test.ts +1 -1
  418. package/src/telemetry/turn-events-store.ts +20 -0
  419. package/src/telemetry/types.ts +45 -12
  420. package/src/telemetry/usage-telemetry-reporter.test.ts +294 -25
  421. package/src/telemetry/usage-telemetry-reporter.ts +117 -18
  422. package/src/telemetry/watchdog-direct-emit.test.ts +11 -3
  423. package/src/telemetry/watchdog-direct-emit.ts +11 -6
  424. package/src/tools/apps/executors.ts +11 -36
  425. package/src/tools/executor.ts +14 -1
  426. package/src/tools/host-terminal/host-shell.ts +6 -2
  427. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +1 -1
  428. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +25 -0
  429. package/src/tools/network/web-fetch.ts +6 -2
  430. package/src/tools/skills/sandbox-runner.ts +5 -2
  431. package/src/tools/subagent/spawn.ts +7 -11
  432. package/src/tools/terminal/shell.ts +5 -2
  433. package/src/tools/tool-manifest.ts +0 -2
  434. package/src/tools/tool-profiler.ts +37 -6
  435. package/src/tools/ui-surface/channel-variants.ts +101 -0
  436. package/src/tools/ui-surface/definitions.ts +23 -67
  437. package/src/tools/ui-surface/surface-shape-docs.ts +229 -0
  438. package/src/tts/__tests__/provider-adapters.test.ts +18 -0
  439. package/src/tts/provider-catalog.ts +2 -4
  440. package/src/tts/providers/deepgram-provider.ts +10 -1
  441. package/src/types/onboarding-context.ts +8 -0
  442. package/src/usage/attribution.ts +18 -112
  443. package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
  444. package/src/util/provider-error-patterns.ts +7 -1
  445. package/src/util/worker-process.ts +105 -0
  446. package/src/watcher/__tests__/telemetry.test.ts +17 -4
  447. package/src/watcher/telemetry.ts +5 -5
  448. package/src/workspace/migrations/131-drop-web-fetch-mode.ts +61 -0
  449. package/src/workspace/migrations/registry.ts +2 -0
  450. package/src/workspace/provider-commit-message-generator.ts +6 -4
  451. package/src/__tests__/app-open-proxy.test.ts +0 -67
  452. package/src/onboarding/onboarding-research-events-store.test.ts +0 -230
  453. package/src/onboarding/onboarding-research-events-store.ts +0 -104
  454. package/src/runtime/routes/assets/vellum-design-system.css +0 -2236
  455. package/src/tools/apps/definitions.ts +0 -73
  456. package/src/tools/apps/open-proxy.ts +0 -43
@@ -1,12 +1,20 @@
1
1
  /**
2
- * Upgrade a single installed plugin to the marketplace's current pin.
2
+ * Upgrade a single installed plugin to its source's current revision.
3
3
  *
4
- * The marketplace pins every plugin to a full, immutable commit SHA (see
4
+ * A marketplace plugin pins to a full, immutable commit SHA (see
5
5
  * {@link ./plugin-marketplace}); an upgrade re-materializes the install at
6
6
  * whatever SHA the catalog currently advertises. Drift is detected with the
7
7
  * same exact commit-SHA comparison {@link ./inspect-plugin} uses, so an
8
8
  * upgrade is a no-op when the installed copy already matches the pin.
9
9
  *
10
+ * A plugin installed directly from a GitHub URL (untrusted, not in the
11
+ * marketplace) is upgraded against its *recorded* source instead: its
12
+ * `install-meta.json` names the owner/repo/path/ref it was cloned from, and the
13
+ * upgrade target is whatever that ref resolves to now — a pinned SHA is
14
+ * immutable (a no-op), a branch / tag / `HEAD` advances as upstream does. Such
15
+ * an upgrade re-materializes verbatim, with no curated adapter overlay, exactly
16
+ * as the original untrusted install was (see {@link directUpgrade}).
17
+ *
10
18
  * This is deliberately a distinct operation from install: `install` is
11
19
  * first-time materialization (and errors on an existing install unless
12
20
  * `--force` is passed), whereas `upgrade` moves an existing install forward.
@@ -36,11 +44,11 @@ import { PRESERVED_ENTRIES } from "../../plugins/plugin-tree-walk.js";
36
44
  import { getWorkspacePluginsDir } from "../../util/platform.js";
37
45
  import type { FetchLike } from "./fetch-like.js";
38
46
  import {
47
+ fetchCommitDate,
39
48
  inspectPlugin,
40
49
  type PluginInspection,
41
50
  PluginInspectNotFoundError,
42
51
  type PluginLocalInfo,
43
- type PluginRemoteInfo,
44
52
  } from "./inspect-plugin.js";
45
53
  import {
46
54
  finalizeStagedInstall,
@@ -52,6 +60,7 @@ import {
52
60
  PluginSourceUnavailableError,
53
61
  type PostinstallRunner,
54
62
  readInstallMeta,
63
+ resolveRefCommit,
55
64
  sanitizePluginName,
56
65
  } from "./install-from-github.js";
57
66
  import { type ConflictLabels, mergePluginTree } from "./merge-plugin-tree.js";
@@ -191,23 +200,27 @@ function pluginTarget(name: string, deps: UpgradePluginDeps): string {
191
200
  }
192
201
 
193
202
  /**
194
- * Move an installed plugin to the marketplace's current pin.
203
+ * Move an installed plugin to its source's current revision — the marketplace
204
+ * pin for a catalog plugin, or the recorded GitHub ref's current commit for a
205
+ * directly-installed one (delegated to {@link directUpgrade}).
195
206
  *
196
- * The `strategy` controls how local edits are reconciled with the pin:
197
- * `overwrite` (default) re-installs the pin wholesale; `ours`/`theirs`/
207
+ * The `strategy` controls how local edits are reconciled with the target:
208
+ * `overwrite` (default) re-installs it wholesale; `ours`/`theirs`/
198
209
  * `assistant` do a three-way merge that carries non-conflicting local edits
199
210
  * forward, resolving conflicting hunks toward the local edit (`ours`) or the
200
- * pin (`theirs`), or leaving git conflict markers in the file for the assistant
201
- * to resolve (`assistant`).
211
+ * target (`theirs`), or leaving git conflict markers in the file for the
212
+ * assistant to resolve (`assistant`).
202
213
  *
203
214
  * Throws {@link PluginNotInstalledError} when no copy is installed,
204
- * {@link PluginNotUpgradableError} when the install has no marketplace entry to
205
- * advance to, {@link PluginMergeBaselineError} when a merge strategy is
206
- * requested but the install-time baseline cannot be reconstructed,
207
- * {@link PluginSourceUnavailableError} when the marketplace catalog is
208
- * temporarily unreachable (a retryable outage, distinct from the permanent
209
- * no-entry case), and propagates {@link installPlugin}'s errors (e.g. source
210
- * unavailable, postinstall failure) when the re-install itself fails.
215
+ * {@link PluginNotUpgradableError} when the install is neither in the
216
+ * marketplace nor carries a recorded GitHub source to advance,
217
+ * {@link PluginMergeBaselineError} when a merge strategy is requested but the
218
+ * install-time baseline cannot be reconstructed,
219
+ * {@link PluginSourceUnavailableError} when the marketplace catalog or the
220
+ * plugin source is temporarily unreachable (a retryable outage, distinct from
221
+ * the permanent no-source case), and propagates {@link installPlugin}'s errors
222
+ * (e.g. source unavailable, postinstall failure) when the re-install itself
223
+ * fails.
211
224
  */
212
225
  export async function upgradePlugin(
213
226
  opts: UpgradePluginOptions,
@@ -233,11 +246,19 @@ export async function upgradePlugin(
233
246
  switch (inspection.status) {
234
247
  case "not-installed":
235
248
  throw new PluginNotInstalledError(name, pluginTarget(name, deps));
236
- case "not-in-marketplace":
237
- throw new PluginNotUpgradableError(
238
- name,
239
- "it has no marketplace entry to upgrade from",
240
- );
249
+ case "not-in-marketplace": {
250
+ // The marketplace doesn't claim this name, but a plugin installed directly
251
+ // from a GitHub URL (untrusted) still has a recorded source to advance:
252
+ // upgrade it by re-fetching whatever its recorded ref now resolves to.
253
+ const local = inspection.local;
254
+ if (!local) {
255
+ throw new PluginNotUpgradableError(
256
+ name,
257
+ "it has no marketplace entry and no installed copy to upgrade",
258
+ );
259
+ }
260
+ return directUpgrade({ name, local, dryRun, strategy }, deps);
261
+ }
241
262
  case "remote-unavailable":
242
263
  // A transient catalog outage is not a permanent "cannot upgrade" state:
243
264
  // the same request can succeed once the marketplace source recovers, so
@@ -309,17 +330,28 @@ export async function upgradePlugin(
309
330
  strategy === "theirs" ||
310
331
  strategy === "assistant"
311
332
  ) {
333
+ const [remoteOwner, remoteRepo] = remote.repo.split("/");
312
334
  return mergeUpgrade(
313
335
  {
314
336
  name,
315
337
  strategy,
316
338
  local,
317
- remote,
318
339
  fromCommit,
319
340
  fromTimestamp,
320
341
  toCommit,
321
342
  toTimestamp,
322
343
  provenanceWasUnknown,
344
+ theirsSource: {
345
+ owner: remoteOwner ?? "",
346
+ repo: remoteRepo ?? "",
347
+ rootPath: remote.path,
348
+ ref: remote.commit,
349
+ },
350
+ // The install baseline re-materializes with the curated adapter overlay
351
+ // (canonical ref); the target reads its overlay at the marketplace ref
352
+ // the pin was advertised from.
353
+ baseStubRef: DEFAULT_PLUGIN_REF,
354
+ theirsStubRef: remote.marketplaceRef,
323
355
  },
324
356
  deps,
325
357
  );
@@ -352,9 +384,176 @@ export async function upgradePlugin(
352
384
  };
353
385
  }
354
386
 
387
+ /**
388
+ * Upgrade a plugin the marketplace does not claim by re-fetching its recorded
389
+ * GitHub source — the untrusted-direct-install analogue of the marketplace
390
+ * upgrade above.
391
+ *
392
+ * A direct install (`plugins install <github-url>`) records the exact
393
+ * owner/repo/path/ref it was cloned from in its `install-meta.json`. Its
394
+ * "latest" is whatever that ref currently resolves to, so the upgrade target is
395
+ * {@link resolveRefCommit} of the recorded ref — a pinned full SHA is immutable
396
+ * (nothing to advance to), while a branch / tag / `HEAD` moves as upstream does.
397
+ * The move is then materialized verbatim, with no curated adapter overlay,
398
+ * exactly as the original untrusted install was.
399
+ *
400
+ * Throws {@link PluginNotUpgradableError} when no resolvable GitHub source was
401
+ * recorded (a manually-copied install), {@link PluginNotFoundError} when the
402
+ * recorded ref has vanished from the remote, {@link PluginMergeBaselineError}
403
+ * when a merge strategy's install-time baseline cannot be reconstructed, and
404
+ * {@link PluginSourceUnavailableError} on a transient source outage.
405
+ */
406
+ async function directUpgrade(
407
+ ctx: {
408
+ readonly name: string;
409
+ readonly local: PluginLocalInfo;
410
+ readonly dryRun: boolean;
411
+ readonly strategy: PluginUpgradeStrategy;
412
+ },
413
+ deps: UpgradePluginDeps,
414
+ ): Promise<PluginUpgradeResult> {
415
+ const { name, local, dryRun, strategy } = ctx;
416
+ const source = local.source;
417
+ // Without resolvable GitHub coordinates in the provenance sidecar (a
418
+ // manually-copied install, or a sidecar naming a non-github source) there is
419
+ // nothing to re-fetch.
420
+ if (!source || source.kind !== "github" || !source.owner || !source.repo) {
421
+ throw new PluginNotUpgradableError(
422
+ name,
423
+ "it has no marketplace entry and no recorded GitHub source to re-fetch from",
424
+ );
425
+ }
426
+ const fetchSource: PluginFetchSource = {
427
+ owner: source.owner,
428
+ repo: source.repo,
429
+ rootPath: source.path ?? "",
430
+ ref: source.ref,
431
+ };
432
+
433
+ const fromCommit = local.commit;
434
+ const fromTimestamp = local.committedAt;
435
+ const provenanceWasUnknown = fromCommit === null;
436
+
437
+ // Resolve what the recorded ref points at now, without cloning. A pinned
438
+ // full-SHA ref is immutable, so it resolves to itself — nothing to advance to.
439
+ const toCommit = await resolveRefCommit(fetchSource, deps.runGit);
440
+ if (toCommit === null) {
441
+ // The recorded ref is gone from the remote (deleted branch / tag) or the
442
+ // repo is unreachable as a hard failure — there is no revision to move to.
443
+ throw new PluginNotFoundError(
444
+ name,
445
+ fetchSource.ref,
446
+ `${fetchSource.owner}/${fetchSource.repo}`,
447
+ );
448
+ }
449
+
450
+ if (
451
+ fromCommit !== null &&
452
+ toCommit.toLowerCase() === fromCommit.toLowerCase()
453
+ ) {
454
+ return {
455
+ name,
456
+ outcome: "already-up-to-date",
457
+ fromCommit,
458
+ fromTimestamp,
459
+ toCommit,
460
+ // The ref still points at the installed commit, so the "to" version is the
461
+ // "from" version — reuse its recorded timestamp rather than a fresh fetch.
462
+ toTimestamp: fromTimestamp,
463
+ target: local.target,
464
+ fileCount: null,
465
+ dryRun,
466
+ strategy,
467
+ conflicts: [],
468
+ binaryConflicts: [],
469
+ provenanceWasUnknown: false,
470
+ };
471
+ }
472
+
473
+ if (dryRun) {
474
+ // Preview: resolve the target commit's date the same way `plugins inspect`
475
+ // does, so the dry run shows the human-readable version it would move to.
476
+ const toTimestamp = await fetchCommitDate(
477
+ `${fetchSource.owner}/${fetchSource.repo}`,
478
+ toCommit,
479
+ deps.fetch,
480
+ );
481
+ return {
482
+ name,
483
+ outcome: "would-upgrade",
484
+ fromCommit,
485
+ fromTimestamp,
486
+ toCommit,
487
+ toTimestamp,
488
+ target: local.target,
489
+ fileCount: null,
490
+ dryRun: true,
491
+ strategy,
492
+ conflicts: [],
493
+ binaryConflicts: [],
494
+ provenanceWasUnknown,
495
+ };
496
+ }
497
+
498
+ if (
499
+ strategy === "ours" ||
500
+ strategy === "theirs" ||
501
+ strategy === "assistant"
502
+ ) {
503
+ // A direct install carries no curated adapter overlay, so both the install
504
+ // baseline and the incoming revision re-materialize verbatim (`stubRef`
505
+ // null), pinned to the recorded install commit and the resolved target.
506
+ return mergeUpgrade(
507
+ {
508
+ name,
509
+ strategy,
510
+ local,
511
+ fromCommit,
512
+ fromTimestamp,
513
+ toCommit,
514
+ toTimestamp: null,
515
+ provenanceWasUnknown,
516
+ theirsSource: { ...fetchSource, ref: toCommit },
517
+ baseStubRef: null,
518
+ theirsStubRef: null,
519
+ },
520
+ deps,
521
+ );
522
+ }
523
+
524
+ // overwrite (default): re-install the recorded direct source verbatim, moving
525
+ // to whatever its ref now resolves to. Untrusted — no curated adapter overlay,
526
+ // just as the original direct install was materialized.
527
+ const result = await installPlugin(
528
+ { name, force: true, directSource: fetchSource },
529
+ {
530
+ fetch: deps.fetch,
531
+ workspacePluginsDir: deps.workspacePluginsDir,
532
+ runGit: deps.runGit,
533
+ runPostinstall: deps.runPostinstall,
534
+ },
535
+ );
536
+
537
+ return {
538
+ name,
539
+ outcome: "upgraded",
540
+ fromCommit,
541
+ fromTimestamp,
542
+ toCommit: result.commit ?? toCommit,
543
+ toTimestamp: result.committedAt,
544
+ target: result.target,
545
+ fileCount: result.fileCount,
546
+ dryRun: false,
547
+ strategy,
548
+ conflicts: [],
549
+ binaryConflicts: [],
550
+ provenanceWasUnknown,
551
+ };
552
+ }
553
+
355
554
  /**
356
555
  * Carry local edits forward by three-way merging the on-disk install (`ours`)
357
- * and the marketplace pin (`theirs`) against the re-materialized install commit
556
+ * and the incoming revision (`theirs`) against the re-materialized install commit
358
557
  * (`base`), then atomically swapping the merged tree into place pinned at the
359
558
  * new commit. Conflicting hunks resolve toward `ours` or `theirs` per the
360
559
  * strategy, or are left as git conflict markers under `assistant`;
@@ -365,16 +564,25 @@ async function mergeUpgrade(
365
564
  readonly name: string;
366
565
  readonly strategy: "ours" | "theirs" | "assistant";
367
566
  readonly local: PluginLocalInfo;
368
- readonly remote: PluginRemoteInfo;
369
567
  readonly fromCommit: string | null;
370
568
  readonly fromTimestamp: string | null;
371
569
  readonly toCommit: string;
372
570
  readonly toTimestamp: string | null;
373
571
  readonly provenanceWasUnknown: boolean;
572
+ /** Coordinates of the revision being merged in (`theirs`), pinned to {@link ctx.toCommit}. */
573
+ readonly theirsSource: PluginFetchSource;
574
+ /**
575
+ * Curated-adapter-stub ref overlaid when re-materializing the install
576
+ * baseline, or `null` for a direct (untrusted) install that carries no
577
+ * curated overlay.
578
+ */
579
+ readonly baseStubRef: string | null;
580
+ /** Curated-adapter-stub ref overlaid on the incoming revision, or `null` for a direct install. */
581
+ readonly theirsStubRef: string | null;
374
582
  },
375
583
  deps: UpgradePluginDeps,
376
584
  ): Promise<PluginUpgradeResult> {
377
- const { name, strategy, local, remote } = ctx;
585
+ const { name, strategy, local, theirsSource } = ctx;
378
586
 
379
587
  // Marker labels name each side by its commit so the assistant can tell the
380
588
  // local edit from the incoming pin when resolving.
@@ -401,13 +609,6 @@ async function mergeUpgrade(
401
609
  rootPath: meta.source.path ?? "",
402
610
  ref: meta.commit,
403
611
  };
404
- const [remoteOwner, remoteRepo] = remote.repo.split("/");
405
- const theirsSource: PluginFetchSource = {
406
- owner: remoteOwner ?? "",
407
- repo: remoteRepo ?? "",
408
- rootPath: remote.path,
409
- ref: remote.commit,
410
- };
411
612
 
412
613
  const pluginsDir = deps.workspacePluginsDir ?? getWorkspacePluginsDir();
413
614
  const baseDir = mkdtempSync(join(tmpdir(), `plugin-upgrade-base-${name}-`));
@@ -429,7 +630,7 @@ async function mergeUpgrade(
429
630
  {
430
631
  source: baseSource,
431
632
  name,
432
- stubRef: DEFAULT_PLUGIN_REF,
633
+ stubRef: ctx.baseStubRef,
433
634
  destDir: baseDir,
434
635
  },
435
636
  deps,
@@ -457,7 +658,7 @@ async function mergeUpgrade(
457
658
  {
458
659
  source: theirsSource,
459
660
  name,
460
- stubRef: remote.marketplaceRef,
661
+ stubRef: ctx.theirsStubRef,
461
662
  destDir: theirsDir,
462
663
  },
463
664
  deps,
@@ -479,8 +680,8 @@ async function mergeUpgrade(
479
680
  conflictLabels,
480
681
  });
481
682
 
482
- const toCommit = theirs.commit ?? remote.commit;
483
- const toTimestamp = theirs.committedAt ?? remote.committedAt;
683
+ const toCommit = theirs.commit ?? ctx.toCommit;
684
+ const toTimestamp = theirs.committedAt ?? ctx.toTimestamp;
484
685
  finalizeStagedInstall(stagingDir, {
485
686
  name,
486
687
  source: theirsSource,
@@ -1,6 +1,5 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
- import { setOverridesForTesting } from "../../__tests__/feature-flag-test-helpers.js";
4
3
  import { resolveModelIntent } from "../../providers/model-intents.js";
5
4
  import { CALL_SITE_DEFAULTS } from "../call-site-defaults.js";
6
5
  import {
@@ -177,7 +176,7 @@ describe("resolver integration", () => {
177
176
  }
178
177
  });
179
178
 
180
- test("a disabled managed default falls through to the custom-* profile", () => {
179
+ test("a disabled managed default does not divert resolution to the custom-* clone", () => {
181
180
  const llm = LLMSchema.parse({
182
181
  profiles: {
183
182
  balanced: { source: "managed", status: "disabled" },
@@ -189,18 +188,15 @@ describe("resolver integration", () => {
189
188
  },
190
189
  },
191
190
  });
192
- // Legacy cascade: the disabled stub falls through to the custom-* hop.
193
- setOverridesForTesting({ "override-or-default-resolution": false });
194
- expect(resolveDefaultProfileKey("mainAgent", llm)).toBe("custom-balanced");
195
- const resolved = resolveCallSiteConfig("mainAgent", llm);
196
- expect(resolved.model).toBe("claude-sonnet-4-6");
197
- // Override-or-default: the hop is gone — the fallback anchor is the
198
- // code-owned intent, and a legacy disabled stub does not suppress it.
199
- setOverridesForTesting({});
191
+ // The fallback anchor is the code-owned intent: a legacy disabled stub
192
+ // does not suppress it, and the user-mutable custom-* clone never
193
+ // captures the call site.
200
194
  expect(resolveDefaultProfileKey("mainAgent", llm)).toBe("balanced");
201
- expect(resolveCallSiteConfig("mainAgent", llm).model).not.toBe(
202
- "claude-sonnet-4-6",
195
+ const resolved = resolveCallSiteConfig("mainAgent", llm);
196
+ expect(resolved.model).toBe(
197
+ CODE_DEFAULT_PROFILE_ENTRIES.balanced.model as string,
203
198
  );
199
+ expect(resolved.model).not.toBe("claude-sonnet-4-6");
204
200
  });
205
201
  });
206
202
 
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Tests for plugin-resident skill discovery in `loadSkillCatalog`.
3
+ *
4
+ * The catalog surfaces skills a plugin ships at `plugins/<dir>/skills/<id>/`.
5
+ * The gate is a loadable manifest (parseable `package.json` with a non-empty
6
+ * `name`) — the manifest `name` need NOT equal the install directory name.
7
+ * That match must not be required: a plugin is installed under its marketplace
8
+ * slug or GitHub path leaf, which routinely differs from its `package.json`
9
+ * `name` (e.g. cognee installs to `cognee`/`vellum-assistant` while its package
10
+ * is named `cognee-memory`). Requiring the match silently drops the plugin's
11
+ * skills even though the runtime loads its hooks and tools fine.
12
+ */
13
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { beforeEach, describe, expect, test } from "bun:test";
16
+
17
+ import { getWorkspacePluginsDir } from "../../util/platform.js";
18
+ import { loadSkillCatalog } from "../skills.js";
19
+
20
+ function writePlugin(
21
+ dirName: string,
22
+ packageName: string,
23
+ skills: Array<{ id: string; description: string }>,
24
+ opts: { disabled?: boolean; packageJson?: string | null } = {},
25
+ ): void {
26
+ const pluginDir = join(getWorkspacePluginsDir(), dirName);
27
+ mkdirSync(pluginDir, { recursive: true });
28
+
29
+ if (opts.packageJson === null) {
30
+ // Intentionally omit package.json.
31
+ } else {
32
+ writeFileSync(
33
+ join(pluginDir, "package.json"),
34
+ opts.packageJson ??
35
+ JSON.stringify({
36
+ name: packageName,
37
+ version: "1.0.0",
38
+ peerDependencies: { "@vellumai/plugin-api": "*" },
39
+ }),
40
+ );
41
+ }
42
+
43
+ if (opts.disabled) writeFileSync(join(pluginDir, ".disabled"), "");
44
+
45
+ for (const skill of skills) {
46
+ const skillDir = join(pluginDir, "skills", skill.id);
47
+ mkdirSync(skillDir, { recursive: true });
48
+ writeFileSync(
49
+ join(skillDir, "SKILL.md"),
50
+ `---\nname: ${skill.id}\ndescription: ${skill.description}\n---\n\nBody for ${skill.id}.\n`,
51
+ );
52
+ }
53
+ }
54
+
55
+ function skillById(id: string) {
56
+ return loadSkillCatalog().find((s) => s.id === id);
57
+ }
58
+
59
+ describe("discoverPluginResidentSkills (via loadSkillCatalog)", () => {
60
+ beforeEach(() => {
61
+ const pluginsDir = getWorkspacePluginsDir();
62
+ if (existsSync(pluginsDir))
63
+ rmSync(pluginsDir, { recursive: true, force: true });
64
+ });
65
+
66
+ test("surfaces a plugin skill when the install dir name differs from package.json name", () => {
67
+ // dir `vellum-assistant` (GitHub path leaf) vs package `cognee-memory`:
68
+ // the exact cognee shape that was silently dropped before.
69
+ writePlugin("vellum-assistant", "cognee-memory", [
70
+ {
71
+ id: "qa-cognee-sync",
72
+ description: "Sync session memory into the graph.",
73
+ },
74
+ ]);
75
+
76
+ const skill = skillById("qa-cognee-sync");
77
+ expect(skill).toBeDefined();
78
+ expect(skill!.source).toBe("plugin");
79
+ // Attribution is the install slug (directory name), which is the identity
80
+ // used by `plugins list` and per-conversation plugin scoping.
81
+ expect(skill!.owner).toEqual({ kind: "plugin", id: "vellum-assistant" });
82
+ });
83
+
84
+ test("surfaces a plugin skill when dir name equals package.json name", () => {
85
+ writePlugin("demo-matched", "demo-matched", [
86
+ { id: "qa-matched-skill", description: "A matched-name plugin skill." },
87
+ ]);
88
+
89
+ const skill = skillById("qa-matched-skill");
90
+ expect(skill).toBeDefined();
91
+ expect(skill!.owner).toEqual({ kind: "plugin", id: "demo-matched" });
92
+ });
93
+
94
+ test("skips a plugin directory that has no package.json", () => {
95
+ writePlugin(
96
+ "no-manifest",
97
+ "unused",
98
+ [{ id: "qa-no-manifest-skill", description: "Should not surface." }],
99
+ { packageJson: null },
100
+ );
101
+
102
+ expect(skillById("qa-no-manifest-skill")).toBeUndefined();
103
+ });
104
+
105
+ test("skips a plugin directory whose package.json is unparseable", () => {
106
+ writePlugin(
107
+ "bad-manifest",
108
+ "unused",
109
+ [{ id: "qa-bad-manifest-skill", description: "Should not surface." }],
110
+ { packageJson: "{ not valid json" },
111
+ );
112
+
113
+ expect(skillById("qa-bad-manifest-skill")).toBeUndefined();
114
+ });
115
+
116
+ test("skips a plugin directory whose package.json has no name", () => {
117
+ writePlugin(
118
+ "nameless",
119
+ "unused",
120
+ [{ id: "qa-nameless-skill", description: "Should not surface." }],
121
+ { packageJson: JSON.stringify({ version: "1.0.0" }) },
122
+ );
123
+
124
+ expect(skillById("qa-nameless-skill")).toBeUndefined();
125
+ });
126
+
127
+ test("hides resident skills of a disabled plugin", () => {
128
+ writePlugin(
129
+ "disabled-plugin",
130
+ "disabled-pkg",
131
+ [{ id: "qa-disabled-skill", description: "Hidden while disabled." }],
132
+ { disabled: true },
133
+ );
134
+
135
+ expect(skillById("qa-disabled-skill")).toBeUndefined();
136
+ });
137
+ });
@@ -1,9 +1,8 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import { VELLUM_MANAGED_CONNECTION_NAME } from "../../providers/vellum-model-routing.js";
4
- import { resolveCallSiteConfig } from "../llm-resolver.js";
5
4
  import { completeCustomProfile } from "../profile-materialization.js";
6
- import { LLMConfigBase, LLMSchema, ProfileEntry } from "../schemas/llm.js";
5
+ import { LLMConfigBase, ProfileEntry } from "../schemas/llm.js";
7
6
 
8
7
  const fullDefault = LLMConfigBase.parse({
9
8
  provider: "anthropic",
@@ -209,89 +208,3 @@ describe("completeCustomProfile", () => {
209
208
  expect(completed.contextWindow).not.toBe(fullDefault.contextWindow);
210
209
  });
211
210
  });
212
-
213
- // Equivalence with the live deep-merge resolver: on a profile-less call site
214
- // with no activeProfile, the custom profile is the only profile layer above
215
- // `llm.default`, which is exactly the standalone meaning materialization
216
- // bakes in. Partial and completed forms must resolve identically — this is
217
- // the contract that lets migration 128 rewrite profiles without changing
218
- // behavior under the current resolver, and lets the M6 resolver treat the
219
- // completed form as the profile's full meaning.
220
- //
221
- // (Deliberately NOT covered: a partial profile layered above other profiles
222
- // — e.g. mainAgent activeProfile above the balanced call-site layer — where
223
- // omitted fields inherit from the mid layer today. That context-dependent
224
- // meaning is unrepresentable as a single complete profile; the standalone
225
- // reading is the one that ships. See the M6 plan, Behavior changes #4.)
226
- describe("completeCustomProfile — resolver equivalence", () => {
227
- const PARTIALS: Record<string, ProfileEntry> = {
228
- empty: {},
229
- "model only, served by default provider": { model: "claude-fable-5" },
230
- "model only, implies another provider": { model: "gpt-5.5" },
231
- "sampling only": { temperature: 0.3 },
232
- "explicit null sampling": { temperature: null },
233
- "nested thinking fragment": { thinking: { enabled: false } },
234
- "tokens and effort": { maxTokens: 1234, effort: "low" },
235
- "explicit cross-provider without connection": {
236
- provider: "openai",
237
- model: "gpt-5.4",
238
- },
239
- "complete profile": {
240
- provider: "openai",
241
- model: "gpt-5.4",
242
- provider_connection: "openai-personal",
243
- maxTokens: 9000,
244
- effort: "high",
245
- temperature: 0.1,
246
- topP: 0.5,
247
- },
248
- "metadata alongside config": {
249
- model: "gpt-5.5",
250
- label: "My GPT",
251
- description: "d",
252
- status: "active",
253
- },
254
- };
255
-
256
- // Run the whole matrix against two defaults: the schema-typical null
257
- // sampling, and a default with non-null temperature/topP — the latter is
258
- // what catches sampling-inheritance regressions (null defaults make those
259
- // cases pass vacuously).
260
- const DEFAULTS: Record<string, LLMConfigBase> = {
261
- "null sampling": fullDefault,
262
- "non-null sampling": LLMConfigBase.parse({
263
- ...fullDefault,
264
- temperature: 0.7,
265
- topP: 0.9,
266
- }),
267
- };
268
-
269
- const resolveWith = (dflt: LLMConfigBase, profile: ProfileEntry) => {
270
- const llm = LLMSchema.parse({
271
- default: dflt,
272
- profiles: { "custom-x": { source: "user", ...profile } },
273
- });
274
- return resolveCallSiteConfig("vision", llm, {
275
- overrideProfile: "custom-x",
276
- // The equivalence contract is a legacy-cascade property by definition:
277
- // materialization pins what the MERGE produced. Under
278
- // override-or-default semantics a partial profile falls back instead
279
- // of merging (pinned by llm-resolver-override-or-default.test.ts).
280
- resolutionSemantics: "legacy-merge",
281
- });
282
- };
283
-
284
- for (const [defaultName, dflt] of Object.entries(DEFAULTS)) {
285
- for (const [name, partial] of Object.entries(PARTIALS)) {
286
- test(`partial and completed resolve identically (${defaultName} default): ${name}`, () => {
287
- const completed = completeCustomProfile(dflt, {
288
- source: "user",
289
- ...partial,
290
- });
291
- expect(resolveWith(dflt, completed)).toEqual(
292
- resolveWith(dflt, partial),
293
- );
294
- });
295
- }
296
- }
297
- });