@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
@@ -6,7 +6,7 @@
6
6
  * GET /v1/inference/provider-connections/:name — single, 404
7
7
  * POST /v1/inference/provider-connections — create happy paths + 409 + 400 cases
8
8
  * PATCH /v1/inference/provider-connections/:name — update auth, 404
9
- * DELETE /v1/inference/provider-connections/:name — happy path, 409 with profile ref, 409 with call-site ref
9
+ * DELETE /v1/inference/provider-connections/:name — happy path, 409 with profile ref, llm.defaultProvider guard
10
10
  * Auth — 401 (missing key) and 403 (insufficient scope) via route-policy assertions
11
11
  */
12
12
 
@@ -32,13 +32,17 @@ await initializeDb();
32
32
 
33
33
  function findHandler(operationId: string): RouteDefinition["handler"] {
34
34
  const route = ROUTES.find((r) => r.operationId === operationId);
35
- if (!route) throw new Error(`Route ${operationId} not found`);
35
+ if (!route) {
36
+ throw new Error(`Route ${operationId} not found`);
37
+ }
36
38
  return route.handler;
37
39
  }
38
40
 
39
41
  function findRoute(operationId: string): RouteDefinition {
40
42
  const route = ROUTES.find((r) => r.operationId === operationId);
41
- if (!route) throw new Error(`Route ${operationId} not found`);
43
+ if (!route) {
44
+ throw new Error(`Route ${operationId} not found`);
45
+ }
42
46
  return route;
43
47
  }
44
48
 
@@ -225,6 +229,97 @@ describe("POST inference/provider-connections (create)", () => {
225
229
  expect(result.auth).toEqual({ type: "none" });
226
230
  });
227
231
 
232
+ test("derives api_key auth from provider + credential when auth is omitted", async () => {
233
+ const result = (await call(
234
+ findHandler("inference_provider_connections_create"),
235
+ {
236
+ body: {
237
+ name: "derived-anthropic",
238
+ provider: "anthropic",
239
+ credential: "vault/anthropic/key",
240
+ },
241
+ },
242
+ )) as { auth: object };
243
+ expect(result.auth).toEqual({
244
+ type: "api_key",
245
+ credential: "vault/anthropic/key",
246
+ });
247
+ });
248
+
249
+ test("derives none auth for keyless providers when auth is omitted", async () => {
250
+ const result = (await call(
251
+ findHandler("inference_provider_connections_create"),
252
+ {
253
+ body: { name: "derived-ollama", provider: "ollama" },
254
+ },
255
+ )) as { auth: object };
256
+ expect(result.auth).toEqual({ type: "none" });
257
+ });
258
+
259
+ test("derives platform auth for the vellum provider when auth is omitted", async () => {
260
+ const result = (await call(
261
+ findHandler("inference_provider_connections_create"),
262
+ {
263
+ body: { name: "derived-vellum", provider: "vellum" },
264
+ },
265
+ )) as { auth: object };
266
+ expect(result.auth).toEqual({ type: "platform" });
267
+ });
268
+
269
+ test("derives none auth for openai-compatible without a credential", async () => {
270
+ const result = (await call(
271
+ findHandler("inference_provider_connections_create"),
272
+ {
273
+ body: {
274
+ name: "derived-local-llm",
275
+ provider: "openai-compatible",
276
+ base_url: "http://localhost:1234/v1",
277
+ models: [{ id: "my-model" }],
278
+ },
279
+ },
280
+ )) as { auth: object };
281
+ expect(result.auth).toEqual({ type: "none" });
282
+ });
283
+
284
+ test("derives api_key auth for openai-compatible with a credential", async () => {
285
+ const result = (await call(
286
+ findHandler("inference_provider_connections_create"),
287
+ {
288
+ body: {
289
+ name: "derived-hosted-llm",
290
+ provider: "openai-compatible",
291
+ credential: "credential/hosted/key",
292
+ base_url: "https://api.example.com/v1",
293
+ models: [{ id: "my-model" }],
294
+ },
295
+ },
296
+ )) as { auth: object };
297
+ expect(result.auth).toEqual({
298
+ type: "api_key",
299
+ credential: "credential/hosted/key",
300
+ });
301
+ });
302
+
303
+ test("throws 400 when auth is omitted and a keyed provider has no credential", async () => {
304
+ await expect(
305
+ call(findHandler("inference_provider_connections_create"), {
306
+ body: { name: "derived-no-cred", provider: "anthropic" },
307
+ }),
308
+ ).rejects.toBeInstanceOf(BadRequestError);
309
+ });
310
+
311
+ test("throws 400 when the derived credential is not a non-empty string", async () => {
312
+ await expect(
313
+ call(findHandler("inference_provider_connections_create"), {
314
+ body: {
315
+ name: "derived-bad-cred",
316
+ provider: "anthropic",
317
+ credential: "",
318
+ },
319
+ }),
320
+ ).rejects.toBeInstanceOf(BadRequestError);
321
+ });
322
+
228
323
  test("throws 409 when connection name already exists", async () => {
229
324
  seedConnection({
230
325
  name: "dup-name",
@@ -332,6 +427,59 @@ describe("PATCH inference/provider-connections/:name (update)", () => {
332
427
  }),
333
428
  ).rejects.toBeInstanceOf(BadRequestError);
334
429
  });
430
+
431
+ test("keeps stored auth when both auth and credential are omitted", async () => {
432
+ seedConnection({
433
+ name: "label-only",
434
+ provider: "openai",
435
+ auth: { type: "oauth_subscription", credential: "vault/chatgpt/token" },
436
+ });
437
+
438
+ const result = (await call(
439
+ findHandler("inference_provider_connections_update"),
440
+ {
441
+ pathParams: { name: "label-only" },
442
+ body: { label: "Renamed" },
443
+ },
444
+ )) as { auth: object; label: string | null };
445
+ expect(result.auth).toEqual({
446
+ type: "oauth_subscription",
447
+ credential: "vault/chatgpt/token",
448
+ });
449
+ expect(result.label).toBe("Renamed");
450
+ });
451
+
452
+ test("throws 400 on credential-only PATCH of an oauth_subscription connection", async () => {
453
+ seedConnection({
454
+ name: "chatgpt-subscription",
455
+ provider: "openai",
456
+ auth: { type: "oauth_subscription", credential: "vault/chatgpt/token" },
457
+ });
458
+
459
+ await expect(
460
+ call(findHandler("inference_provider_connections_update"), {
461
+ pathParams: { name: "chatgpt-subscription" },
462
+ body: { credential: "vault/other" },
463
+ }),
464
+ ).rejects.toBeInstanceOf(BadRequestError);
465
+ });
466
+
467
+ test("rotates to derived api_key auth when only credential is passed", async () => {
468
+ seedConnection({
469
+ name: "rotate-cred",
470
+ provider: "anthropic",
471
+ auth: { type: "api_key", credential: "vault/old" },
472
+ });
473
+
474
+ const result = (await call(
475
+ findHandler("inference_provider_connections_update"),
476
+ {
477
+ pathParams: { name: "rotate-cred" },
478
+ body: { credential: "vault/new" },
479
+ },
480
+ )) as { auth: object };
481
+ expect(result.auth).toEqual({ type: "api_key", credential: "vault/new" });
482
+ });
335
483
  });
336
484
 
337
485
  // ── DELETE ────────────────────────────────────────────────────────────────────
@@ -390,31 +538,12 @@ describe("DELETE inference/provider-connections/:name (delete)", () => {
390
538
  expect((err as ConflictError).message).toContain("my-profile");
391
539
  });
392
540
 
393
- test("throws 409 when llm.default references the connection", async () => {
394
- seedConnection({
395
- name: "default-conn",
396
- provider: "anthropic",
397
- auth: { type: "platform" },
398
- });
399
- setConfig("llm", {
400
- default: { provider_connection: "default-conn" },
401
- });
402
-
403
- const err = await call(
404
- findHandler("inference_provider_connections_delete"),
405
- { pathParams: { name: "default-conn" } },
406
- ).catch((e: unknown) => e);
407
- expect(err).toBeInstanceOf(ConflictError);
408
- expect((err as ConflictError).message).toContain("default-conn");
409
- expect((err as ConflictError).message).toContain("llm.default");
410
- });
411
-
412
- test("throws 404 (not 409) when llm.default references a missing connection", async () => {
413
- // Stale ref in config: llm.default points at a connection that was
541
+ test("throws 404 (not 409) when a profile references a missing connection", async () => {
542
+ // Stale ref in config: a profile points at a connection that was
414
543
  // already deleted. Delete on the dangling name must return 404 so
415
544
  // callers can distinguish stale config from active conflicts.
416
545
  setConfig("llm", {
417
- default: { provider_connection: "ghost-conn" },
546
+ profiles: { "ghost-prof": { provider_connection: "ghost-conn" } },
418
547
  });
419
548
 
420
549
  await expect(
@@ -424,24 +553,26 @@ describe("DELETE inference/provider-connections/:name (delete)", () => {
424
553
  ).rejects.toBeInstanceOf(NotFoundError);
425
554
  });
426
555
 
427
- test("throws 409 when both llm.default and a profile reference the connection", async () => {
556
+ test("throws 409 naming every referencing profile", async () => {
428
557
  seedConnection({
429
558
  name: "shared-conn",
430
559
  provider: "anthropic",
431
560
  auth: { type: "none" },
432
561
  });
433
562
  setConfig("llm", {
434
- default: { provider_connection: "shared-conn" },
435
- profiles: { "prof-a": { provider_connection: "shared-conn" } },
563
+ profiles: {
564
+ "prof-a": { provider_connection: "shared-conn" },
565
+ "prof-b": { provider_connection: "shared-conn" },
566
+ },
436
567
  });
437
568
 
438
- // llm.default check fires first (before profiles check).
439
569
  const err = await call(
440
570
  findHandler("inference_provider_connections_delete"),
441
571
  { pathParams: { name: "shared-conn" } },
442
572
  ).catch((e: unknown) => e);
443
573
  expect(err).toBeInstanceOf(ConflictError);
444
- expect((err as ConflictError).message).toContain("llm.default");
574
+ expect((err as ConflictError).message).toContain("prof-a");
575
+ expect((err as ConflictError).message).toContain("prof-b");
445
576
  });
446
577
  });
447
578
 
@@ -28,6 +28,7 @@
28
28
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
29
29
 
30
30
  import { credentialKey } from "../../../security/credential-key.js";
31
+ import * as actualMetadataStore from "../../../tools/credentials/metadata-store.js";
31
32
 
32
33
  type MetadataRecord = {
33
34
  credentialId: string;
@@ -80,7 +81,13 @@ mock.module("../../../security/secure-keys.js", () => ({
80
81
  _resetBackend: () => {},
81
82
  }));
82
83
 
84
+ // Spread the real module so every export the reconcile handler's transitive
85
+ // graph links (e.g. getCredentialMetadataById, listCredentialMetadata pulled in
86
+ // via the plugin-api hub) stays present; only the two functions under test are
87
+ // overridden. A partial mock replaces the whole namespace and fails to link the
88
+ // unlisted exports.
83
89
  mock.module("../../../tools/credentials/metadata-store.js", () => ({
90
+ ...actualMetadataStore,
84
91
  getCredentialMetadata: (service: string, field: string) =>
85
92
  metadataStore.get(`${service}:${field}`),
86
93
  upsertCredentialMetadata: (
@@ -20,6 +20,7 @@ let stopImpl: () => { status: "running" | "not_running"; pid?: number };
20
20
  let workerProbe: { status: "running" | "not_running"; pid?: number } = {
21
21
  status: "not_running",
22
22
  };
23
+ const adminStoppedCalls: boolean[] = [];
23
24
 
24
25
  mock.module("../../../schedule/worker-control.js", () => ({
25
26
  ScheduleWorkerSpawnError: FakeSpawnError,
@@ -32,6 +33,9 @@ mock.module("../../../schedule/worker-control.js", () => ({
32
33
  },
33
34
  stopScheduleWorkerProcess: () => stopImpl(),
34
35
  probeScheduleWorker: () => workerProbe,
36
+ setScheduleWorkerAdministrativelyStopped: (value: boolean) => {
37
+ adminStoppedCalls.push(value);
38
+ },
35
39
  }));
36
40
 
37
41
  const { ROUTES } = await import("../schedule-worker-routes.js");
@@ -49,6 +53,7 @@ beforeEach(() => {
49
53
  spawnImpl = async () => ({ pid: 4242, alreadyRunning: false });
50
54
  stopImpl = () => ({ status: "not_running" });
51
55
  workerProbe = { status: "not_running" };
56
+ adminStoppedCalls.length = 0;
52
57
  });
53
58
 
54
59
  describe("schedules_worker_start", () => {
@@ -82,6 +87,11 @@ describe("schedules_worker_start", () => {
82
87
  "worker exited during startup",
83
88
  );
84
89
  });
90
+
91
+ test("clears the administratively-stopped flag so the watchdog resumes", async () => {
92
+ await handler("schedules_worker_start")();
93
+ expect(adminStoppedCalls).toContain(false);
94
+ });
85
95
  });
86
96
 
87
97
  describe("schedules_worker_stop", () => {
@@ -100,6 +110,11 @@ describe("schedules_worker_stop", () => {
100
110
 
101
111
  expect(res).toEqual({ workerWasRunning: false });
102
112
  });
113
+
114
+ test("sets the administratively-stopped flag so the watchdog does not respawn", async () => {
115
+ await handler("schedules_worker_stop")();
116
+ expect(adminStoppedCalls).toContain(true);
117
+ });
103
118
  });
104
119
 
105
120
  describe("schedules_worker_status", () => {
@@ -6,8 +6,10 @@ import { randomBytes } from "node:crypto";
6
6
  import {
7
7
  existsSync,
8
8
  mkdirSync,
9
+ mkdtempSync,
9
10
  readdirSync,
10
11
  readFileSync,
12
+ rmSync,
11
13
  statSync,
12
14
  writeFileSync,
13
15
  } from "node:fs";
@@ -19,6 +21,7 @@ import { z } from "zod";
19
21
 
20
22
  import {
21
23
  type AppDefinition,
24
+ type AppOrigin,
22
25
  createApp,
23
26
  createAppRecord,
24
27
  deleteApp,
@@ -38,7 +41,7 @@ import {
38
41
  } from "../../apps/app-store.js";
39
42
  import { createSharedAppLink } from "../../apps/shared-app-links-store.js";
40
43
  import { packageApp } from "../../bundler/app-bundler.js";
41
- import { compileApp } from "../../bundler/app-compiler.js";
44
+ import { compileApp, runCompile } from "../../bundler/app-compiler.js";
42
45
  import { scanBundle } from "../../bundler/bundle-scanner.js";
43
46
  import type { SignatureJson } from "../../bundler/bundle-signer.js";
44
47
  import { verifyBundleSignature } from "../../bundler/signature-verifier.js";
@@ -278,6 +281,29 @@ function forkSharedApp(
278
281
  return { success: false, error: "Shared app HTML not found" };
279
282
  }
280
283
 
284
+ // The unpacked bundle keeps its manifest.json; only multi-file (format 2)
285
+ // shared apps may be forked, else the fork would resurrect the retired
286
+ // single-file format through the dist/ materialization below.
287
+ let sharedFormatVersion: unknown;
288
+ const manifestPath = join(dir, appUuid, "manifest.json");
289
+ if (existsSync(manifestPath)) {
290
+ try {
291
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8")) as {
292
+ format_version?: unknown;
293
+ };
294
+ sharedFormatVersion = manifest.format_version;
295
+ } catch {
296
+ // Malformed manifest — treated as unsupported below.
297
+ }
298
+ }
299
+ if (Number(sharedFormatVersion) !== 2) {
300
+ return {
301
+ success: false,
302
+ error:
303
+ "This shared app uses the legacy single-file format, which is no longer supported",
304
+ };
305
+ }
306
+
281
307
  const htmlContent = readFileSync(htmlPath, "utf-8");
282
308
 
283
309
  const newApp = createApp({
@@ -287,6 +313,18 @@ function forkSharedApp(
287
313
  htmlDefinition: htmlContent,
288
314
  });
289
315
 
316
+ // Materialize the shared app's compiled output as the fork's dist/ so the
317
+ // fork opens without a source compile (mirrors bundle import).
318
+ const distDir = join(getAppDirPath(newApp.id), "dist");
319
+ mkdirSync(distDir, { recursive: true });
320
+ writeFileSync(join(distDir, "index.html"), htmlContent, "utf-8");
321
+ for (const asset of ["main.js", "main.css"]) {
322
+ const assetPath = join(dir, appUuid, asset);
323
+ if (existsSync(assetPath)) {
324
+ writeFileSync(join(distDir, asset), readFileSync(assetPath, "utf-8"));
325
+ }
326
+ }
327
+
290
328
  return { success: true, appId: newApp.id, name: newApp.name };
291
329
  }
292
330
 
@@ -450,10 +488,15 @@ async function importBundle(
450
488
  manifest = JSON.parse(manifestText);
451
489
  }
452
490
 
491
+ if (Number(manifest.format_version) !== 2) {
492
+ throw new BadRequestError(
493
+ "This app bundle uses the legacy single-file format, which is no longer supported. Re-export the app from a current version and try again.",
494
+ );
495
+ }
496
+
453
497
  const appName = manifest.name ?? "Imported App";
454
498
  const appDescription = manifest.description;
455
499
  const entry = manifest.entry ?? "index.html";
456
- const isMultiFile = manifest.format_version === 2;
457
500
 
458
501
  // Extract entry HTML
459
502
  const entryFile = zip.file(entry);
@@ -476,32 +519,29 @@ async function importBundle(
476
519
  schemaJson: JSON.stringify({ type: "object", properties: {} }),
477
520
  htmlDefinition,
478
521
  icon,
479
- formatVersion: isMultiFile ? 2 : undefined,
480
522
  });
481
523
 
482
- // For multi-file apps, extract compiled dist assets (main.js, main.css)
483
- // into the app's dist/ directory so the app can run correctly.
484
- if (isMultiFile) {
485
- const appDir = getAppDirPath(newApp.id);
486
- const distDir = join(appDir, "dist");
487
- mkdirSync(distDir, { recursive: true });
488
-
489
- // Write dist/index.html
490
- writeFileSync(join(distDir, "index.html"), htmlDefinition, "utf-8");
491
-
492
- // Write dist/main.js if present in the bundle
493
- const mainJsFile = zip.file("main.js");
494
- if (mainJsFile) {
495
- const mainJs = await mainJsFile.async("text");
496
- writeFileSync(join(distDir, "main.js"), mainJs, "utf-8");
497
- }
524
+ // Extract compiled dist assets (main.js, main.css) into the app's dist/
525
+ // directory so the app can run correctly.
526
+ const appDir = getAppDirPath(newApp.id);
527
+ const distDir = join(appDir, "dist");
528
+ mkdirSync(distDir, { recursive: true });
498
529
 
499
- // Write dist/main.css if present in the bundle
500
- const mainCssFile = zip.file("main.css");
501
- if (mainCssFile) {
502
- const mainCss = await mainCssFile.async("text");
503
- writeFileSync(join(distDir, "main.css"), mainCss, "utf-8");
504
- }
530
+ // Write dist/index.html
531
+ writeFileSync(join(distDir, "index.html"), htmlDefinition, "utf-8");
532
+
533
+ // Write dist/main.js if present in the bundle
534
+ const mainJsFile = zip.file("main.js");
535
+ if (mainJsFile) {
536
+ const mainJs = await mainJsFile.async("text");
537
+ writeFileSync(join(distDir, "main.js"), mainJs, "utf-8");
538
+ }
539
+
540
+ // Write dist/main.css if present in the bundle
541
+ const mainCssFile = zip.file("main.css");
542
+ if (mainCssFile) {
543
+ const mainCss = await mainCssFile.async("text");
544
+ writeFileSync(join(distDir, "main.css"), mainCss, "utf-8");
505
545
  }
506
546
 
507
547
  return {
@@ -651,6 +691,65 @@ function handleMutateAppData({ pathParams, body }: RouteHandlerArgs) {
651
691
  return { success: true, result };
652
692
  }
653
693
 
694
+ /**
695
+ * Wire-format provenance tag for an app, matching what `apps_list` reports:
696
+ * `"workspace"` for user-created apps, `"plugin:<name>"` for plugin-bundled
697
+ * ones. Clients read it to hide the mutation actions (delete, share, deploy)
698
+ * the daemon rejects for plugin apps.
699
+ */
700
+ function appOriginWire(origin: AppOrigin): string {
701
+ return origin.kind === "plugin" ? `plugin:${origin.pluginName}` : "workspace";
702
+ }
703
+
704
+ /**
705
+ * Compile a multi-file plugin app on open, without touching its `dist/`.
706
+ *
707
+ * A plugin app's real `dist/` is owned by the monitor process
708
+ * (`plugin-source-watch`), which builds it on install and on every source
709
+ * change. But a just-installed app can be opened before the monitor's first
710
+ * pass lands, which would otherwise render the "not compiled yet" fallback.
711
+ * Building in place here would both write into the read-only plugin tree and
712
+ * race the monitor's `rm -rf dist` + write. Instead we compile into a private
713
+ * temp dir and return the self-contained HTML from there, leaving `dist/`
714
+ * ownership solely with the monitor. Returns `null` if the build fails, so the
715
+ * caller falls back to the standard fallback HTML.
716
+ */
717
+ async function compilePluginAppHtmlEphemeral(
718
+ appId: string,
719
+ sourceDir: string,
720
+ ): Promise<string | null> {
721
+ // `resolveAppSource` classifies any app without a root index.html as
722
+ // multi-file, even a malformed one with no src/ (or one whose source was
723
+ // removed before the monitor built dist/). Building that would make the
724
+ // compiler throw, so skip it and let the caller serve the standard fallback
725
+ // rather than surfacing a 500 from a plain open.
726
+ if (!existsSync(join(sourceDir, "src"))) {
727
+ return null;
728
+ }
729
+ const tmpRoot = mkdtempSync(join(tmpdir(), "vellum-plugin-app-"));
730
+ try {
731
+ const result = await runCompile(sourceDir, join(tmpRoot, "dist"));
732
+ if (!result.ok) {
733
+ log.warn(
734
+ { appId, errors: result.errors },
735
+ "Ephemeral compile failed on plugin app open",
736
+ );
737
+ return null;
738
+ }
739
+ // resolveEffectiveAppHtmlFromDir reads `<tmpRoot>/dist/index.html` and
740
+ // inlines its JS/CSS, yielding a self-contained page.
741
+ return resolveEffectiveAppHtmlFromDir(tmpRoot);
742
+ } catch (err) {
743
+ // The compiler reports build failures via `ok: false`; a thrown error is
744
+ // unexpected (e.g. a filesystem fault mid-build) and must still degrade to
745
+ // the fallback instead of a 500.
746
+ log.warn({ appId, err }, "Ephemeral compile threw on plugin app open");
747
+ return null;
748
+ } finally {
749
+ rmSync(tmpRoot, { recursive: true, force: true });
750
+ }
751
+ }
752
+
654
753
  async function handleOpenApp({ pathParams }: RouteHandlerArgs) {
655
754
  const appId = pathParams?.id as string;
656
755
  const source = resolveAppSource(appId);
@@ -658,33 +757,40 @@ async function handleOpenApp({ pathParams }: RouteHandlerArgs) {
658
757
  throw new NotFoundError(`App not found: ${appId}`);
659
758
  }
660
759
 
661
- // Multifile workspace apps auto-compile on open when their dist is missing.
662
- // Plugin apps are not compiled here — a compile cache for plugin sources is a
663
- // follow-up — so a multifile plugin app without a prebuilt dist/ renders its
664
- // fallback message.
665
- if (source.formatVersion === 2 && source.origin.kind === "workspace") {
666
- const distIndex = join(source.sourceDir, "dist", "index.html");
667
- if (!existsSync(distIndex)) {
668
- const result = await compileApp(source.sourceDir);
669
- if (!result.ok) {
760
+ const result = {
761
+ appId: source.id,
762
+ dirName: source.dirName,
763
+ name: source.name,
764
+ origin: appOriginWire(source.origin),
765
+ };
766
+
767
+ // Apps auto-compile on open when their dist/ is missing so a freshly
768
+ // installed app renders immediately instead of the "not compiled yet"
769
+ // fallback.
770
+ if (!existsSync(join(source.sourceDir, "dist", "index.html"))) {
771
+ if (source.origin.kind === "workspace") {
772
+ // Workspace apps own their dist/ and are only compiled in this (daemon)
773
+ // process, so building in place is safe.
774
+ const compiled = await compileApp(source.sourceDir);
775
+ if (!compiled.ok) {
670
776
  log.warn(
671
- { appId, errors: result.errors },
777
+ { appId, errors: compiled.errors },
672
778
  "Auto-compile failed on app open",
673
779
  );
674
780
  }
781
+ } else {
782
+ // Plugin apps: compile off to the side (see helper) rather than write
783
+ // into the plugin tree or race the monitor.
784
+ const html = await compilePluginAppHtmlEphemeral(appId, source.sourceDir);
785
+ if (html !== null) {
786
+ return { ...result, html };
787
+ }
788
+ // Build failed — fall through to the standard fallback HTML.
675
789
  }
676
790
  }
677
791
 
678
- const html = resolveEffectiveAppHtmlFromDir(
679
- source.sourceDir,
680
- source.formatVersion,
681
- );
682
- return {
683
- appId: source.id,
684
- dirName: source.dirName,
685
- name: source.name,
686
- html,
687
- };
792
+ const html = resolveEffectiveAppHtmlFromDir(source.sourceDir);
793
+ return { ...result, html };
688
794
  }
689
795
 
690
796
  /**
@@ -1015,6 +1121,7 @@ export const ROUTES: RouteDefinition[] = [
1015
1121
  dirName: z.string(),
1016
1122
  name: z.string(),
1017
1123
  html: z.string(),
1124
+ origin: z.string(),
1018
1125
  }),
1019
1126
  },
1020
1127
  {