@vellumai/assistant 0.8.1 → 0.8.3

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 (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +642 -5
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -49,6 +49,73 @@ export class ElevenLabsTtsError extends Error {
49
49
  }
50
50
  }
51
51
 
52
+ // ---------------------------------------------------------------------------
53
+ // Error-body parser
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /** Maximum number of characters of a fallback raw body to surface in an error message. */
57
+ const MAX_RAW_ERROR_BODY_CHARS = 200;
58
+
59
+ /**
60
+ * Best-effort extraction of a user-facing error message from an ElevenLabs
61
+ * error response body.
62
+ *
63
+ * ElevenLabs returns structured errors in the shape:
64
+ * ```json
65
+ * { "detail": { "status": "...", "code": "...", "message": "..." } }
66
+ * ```
67
+ * but also occasionally returns `{ "message": "..." }`, `{ "detail": "..." }`,
68
+ * HTML pages (502/503 from their CDN), or free-form text. We try the
69
+ * structured shapes first, fall back to a trimmed/truncated raw body, and
70
+ * return `undefined` when nothing useful is present.
71
+ *
72
+ * Exported for unit testing.
73
+ */
74
+ export function extractElevenLabsErrorMessage(
75
+ body: string,
76
+ ): string | undefined {
77
+ if (!body) return undefined;
78
+ const trimmed = body.trim();
79
+ if (!trimmed) return undefined;
80
+
81
+ // Try JSON envelopes first.
82
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
83
+ try {
84
+ const parsed = JSON.parse(trimmed) as unknown;
85
+ if (parsed && typeof parsed === "object") {
86
+ const root = parsed as { detail?: unknown; message?: unknown };
87
+
88
+ // Standard ElevenLabs shape: { detail: { message } }
89
+ if (root.detail && typeof root.detail === "object") {
90
+ const detailMessage = (root.detail as { message?: unknown }).message;
91
+ if (typeof detailMessage === "string" && detailMessage.trim()) {
92
+ return detailMessage.trim();
93
+ }
94
+ }
95
+
96
+ // Fallback shape: { detail: "..." }
97
+ if (typeof root.detail === "string" && root.detail.trim()) {
98
+ return root.detail.trim();
99
+ }
100
+
101
+ // Fallback shape: { message: "..." }
102
+ if (typeof root.message === "string" && root.message.trim()) {
103
+ return root.message.trim();
104
+ }
105
+ }
106
+ } catch {
107
+ // Not valid JSON — fall through to the raw-body fallback.
108
+ }
109
+ }
110
+
111
+ // Raw body fallback (HTML pages, plain text). Truncate to keep error
112
+ // messages reasonable when surfaced to UI clients.
113
+ if (trimmed.length > MAX_RAW_ERROR_BODY_CHARS) {
114
+ return `${trimmed.slice(0, MAX_RAW_ERROR_BODY_CHARS)}…`;
115
+ }
116
+ return trimmed;
117
+ }
118
+
52
119
  // ---------------------------------------------------------------------------
53
120
  // Constants
54
121
  // ---------------------------------------------------------------------------
@@ -180,9 +247,16 @@ export function createElevenLabsProvider(): TtsProvider {
180
247
 
181
248
  if (!response.ok) {
182
249
  const errorText = await response.text().catch(() => "");
250
+ // Surface the upstream provider message verbatim when extractable —
251
+ // the daemon route wraps it with a single "TTS synthesis failed:"
252
+ // prefix on the way out. The HTTP status is preserved on `statusCode`
253
+ // and logged by the daemon, so we don't embed it in the message text.
254
+ const message =
255
+ extractElevenLabsErrorMessage(errorText) ??
256
+ `ElevenLabs returned HTTP ${response.status}`;
183
257
  throw new ElevenLabsTtsError(
184
258
  "ELEVENLABS_TTS_HTTP_ERROR",
185
- `ElevenLabs TTS returned ${response.status}: ${errorText}`,
259
+ message,
186
260
  response.status,
187
261
  );
188
262
  }
@@ -4,4 +4,6 @@ export interface OnboardingContext {
4
4
  tone: string;
5
5
  userName?: string;
6
6
  assistantName?: string;
7
+ googleConnected?: boolean;
8
+ googleScopes?: string[];
7
9
  }
@@ -151,14 +151,31 @@ export class ConfigError extends AssistantError {
151
151
  }
152
152
 
153
153
  export class ProviderNotConfiguredError extends ConfigError {
154
+ /**
155
+ * Optional name of the `provider_connections` row whose credential was
156
+ * missing. Surfaced through `ConversationErrorMessage.connectionName` so
157
+ * the macOS chat banner can render "API key required for connection
158
+ * <name>" instead of a generic message.
159
+ */
160
+ public readonly connectionName?: string;
161
+ /**
162
+ * Optional name of the resolved profile in play when this error was
163
+ * thrown. Forwarded to the wire `ConversationErrorMessage.profileName`
164
+ * for the same banner-attribution purpose as `connectionName`.
165
+ */
166
+ public readonly profileName?: string;
167
+
154
168
  constructor(
155
169
  public readonly requestedProvider: string,
156
170
  public readonly registeredProviders: string[],
171
+ attribution?: { connectionName?: string; profileName?: string },
157
172
  ) {
158
173
  super(
159
174
  `No providers available. Requested: "${requestedProvider}". Registered: ${registeredProviders.join(", ") || "none"}`,
160
175
  );
161
176
  this.name = "ProviderNotConfiguredError";
177
+ this.connectionName = attribution?.connectionName;
178
+ this.profileName = attribution?.profileName;
162
179
  }
163
180
  }
164
181
 
@@ -268,6 +268,16 @@ export function getWorkspaceHooksDir(): string {
268
268
  return join(getWorkspaceDir(), "hooks");
269
269
  }
270
270
 
271
+ /**
272
+ * Returns `<workspaceDir>/plugins` — the directory scanned by the user plugin
273
+ * loader at daemon startup. Writes here are security-sensitive: any
274
+ * `register.{ts,js}` will be dynamic-imported on next restart, so the file
275
+ * risk classifier escalates writes under this path to High.
276
+ */
277
+ export function getWorkspacePluginsDir(): string {
278
+ return join(getWorkspaceDir(), "plugins");
279
+ }
280
+
271
281
  /** Returns $VELLUM_WORKSPACE_DIR/routes — user-defined HTTP route handlers. */
272
282
  export function getWorkspaceRoutesDir(): string {
273
283
  return join(getWorkspaceDir(), "routes");
@@ -250,6 +250,28 @@ describe("runWatchersOnce — Phase 2 runBackgroundJob integration", () => {
250
250
  expect(dispositionCalls[0].reason).toBe("model exploded");
251
251
  });
252
252
 
253
+ test("on bootstrap failure (conversationId: ''): does not overwrite prior conversation id", async () => {
254
+ fakeWatchers = [makeWatcher()];
255
+ fakePending = [makeEvent()];
256
+ // bootstrap failure shape from runBackgroundJob — empty conversationId
257
+ // signals that conversation creation failed before assignment.
258
+ runJobImpl = async () => ({
259
+ conversationId: "",
260
+ ok: false,
261
+ error: new Error("bootstrap exploded"),
262
+ errorKind: "exception",
263
+ });
264
+
265
+ await runWatchersOnce(() => {});
266
+
267
+ // Critical: we must NOT have called setWatcherConversationId with "",
268
+ // which would clobber a valid prior conversation id in the DB.
269
+ expect(setConvCalls).toEqual([]);
270
+ // Failure path still updates event dispositions.
271
+ expect(dispositionCalls).toHaveLength(1);
272
+ expect(dispositionCalls[0].disposition).toBe("error");
273
+ });
274
+
253
275
  test("skips runBackgroundJob entirely when no pending events", async () => {
254
276
  fakeWatchers = [makeWatcher()];
255
277
  fakePending = [];
@@ -263,8 +263,12 @@ export async function runWatchersOnce(
263
263
  });
264
264
 
265
265
  // Persist the per-tick conversation id so downstream surfaces (UI,
266
- // store reads) can link back to the most recent watcher run.
267
- setWatcherConversationId(watcher.id, result.conversationId);
266
+ // store reads) can link back to the most recent watcher run. Skip
267
+ // persistence when the runner failed before bootstrap (conversationId
268
+ // is empty) — otherwise we'd overwrite a valid prior id with "".
269
+ if (result.conversationId !== "") {
270
+ setWatcherConversationId(watcher.id, result.conversationId);
271
+ }
268
272
 
269
273
  if (result.ok) {
270
274
  // Mark events as silent by default. The LLM is expected to use
@@ -35,8 +35,15 @@ export const repairStaleGeminiModelIdsMigration: WorkspaceMigration = {
35
35
 
36
36
  const defaultBlock = readObject(llm.default);
37
37
  const defaultProvider = readProvider(defaultBlock);
38
-
39
- if (defaultBlock !== null && isGeminiBlock(defaultBlock, defaultProvider)) {
38
+ const profiles = readObject(llm.profiles);
39
+ const activeProfileName =
40
+ typeof llm.activeProfile === "string" ? llm.activeProfile : undefined;
41
+ const activeProfileProvider =
42
+ profiles !== null && activeProfileName !== undefined
43
+ ? inferProvider(readObject(profiles[activeProfileName]))
44
+ : undefined;
45
+
46
+ if (defaultBlock !== null && isGeminiProvider(defaultProvider)) {
40
47
  changed = repairModel(defaultBlock, DEFAULT_REPLACEMENT_MODEL) || changed;
41
48
  }
42
49
 
@@ -45,7 +52,14 @@ export const repairStaleGeminiModelIdsMigration: WorkspaceMigration = {
45
52
  for (const [site, rawConfig] of Object.entries(callSites)) {
46
53
  const callSiteConfig = readObject(rawConfig);
47
54
  if (callSiteConfig === null) continue;
48
- if (!isGeminiBlock(callSiteConfig, defaultProvider)) continue;
55
+ const effective = resolveCallSiteEffectiveProvider({
56
+ callSite: site,
57
+ callSiteConfig,
58
+ profiles,
59
+ activeProfileProvider,
60
+ defaultProvider,
61
+ });
62
+ if (!isGeminiProvider(effective)) continue;
49
63
  const replacement = LATENCY_CALL_SITES.has(site)
50
64
  ? LATENCY_REPLACEMENT_MODEL
51
65
  : DEFAULT_REPLACEMENT_MODEL;
@@ -53,12 +67,12 @@ export const repairStaleGeminiModelIdsMigration: WorkspaceMigration = {
53
67
  }
54
68
  }
55
69
 
56
- const profiles = readObject(llm.profiles);
57
70
  if (profiles !== null) {
58
71
  for (const rawProfile of Object.values(profiles)) {
59
72
  const profile = readObject(rawProfile);
60
73
  if (profile === null) continue;
61
- if (!isGeminiBlock(profile, defaultProvider)) continue;
74
+ const effective = inferProvider(profile) ?? defaultProvider;
75
+ if (!isGeminiProvider(effective)) continue;
62
76
  changed = repairModel(profile, DEFAULT_REPLACEMENT_MODEL) || changed;
63
77
  }
64
78
  }
@@ -109,14 +123,65 @@ function readProvider(
109
123
  return typeof block.provider === "string" ? block.provider : undefined;
110
124
  }
111
125
 
112
- // A block targets Gemini if it explicitly sets provider="gemini", or if it has
113
- // no provider field and the default block resolves to Gemini. An explicit
114
- // non-Gemini provider blocks the rewrite.
115
- function isGeminiBlock(
116
- block: Record<string, unknown>,
117
- defaultProvider: string | undefined,
118
- ): boolean {
119
- const local = readProvider(block);
120
- const effective = local ?? defaultProvider;
121
- return effective === undefined || effective === "gemini";
126
+ // Mirrors `resolveCallSiteConfig`'s catalog-based inference: a fragment with
127
+ // no explicit `provider` but a Gemini-catalog `model` resolves to "gemini" at
128
+ // that layer. The stale model is the only catalog entry the migration cares
129
+ // about, so the check stays self-contained.
130
+ function inferProvider(
131
+ block: Record<string, unknown> | null,
132
+ ): string | undefined {
133
+ if (block === null) return undefined;
134
+ const explicit = readProvider(block);
135
+ if (explicit !== undefined) return explicit;
136
+ if (block.model === STALE_MODEL) return "gemini";
137
+ return undefined;
138
+ }
139
+
140
+ function isGeminiProvider(provider: string | undefined): boolean {
141
+ return provider === undefined || provider === "gemini";
142
+ }
143
+
144
+ // Walks the call-site provider resolution chain the same way
145
+ // `resolveCallSiteConfig` does: for `mainAgent`, `activeProfile` overrides the
146
+ // static `callSites.mainAgent` block, while for every other call site the
147
+ // call-site block (and its referenced profile) wins over `activeProfile`.
148
+ // Returns the highest-precedence inferred provider, falling back to `default`.
149
+ function resolveCallSiteEffectiveProvider(args: {
150
+ callSite: string;
151
+ callSiteConfig: Record<string, unknown>;
152
+ profiles: Record<string, unknown> | null;
153
+ activeProfileProvider: string | undefined;
154
+ defaultProvider: string | undefined;
155
+ }): string | undefined {
156
+ const {
157
+ callSite,
158
+ callSiteConfig,
159
+ profiles,
160
+ activeProfileProvider,
161
+ defaultProvider,
162
+ } = args;
163
+ const siteProvider = inferProvider(callSiteConfig);
164
+ const siteProfileName =
165
+ typeof callSiteConfig.profile === "string"
166
+ ? callSiteConfig.profile
167
+ : undefined;
168
+ const siteProfileProvider =
169
+ profiles !== null && siteProfileName !== undefined
170
+ ? inferProvider(readObject(profiles[siteProfileName]))
171
+ : undefined;
172
+
173
+ if (callSite === "mainAgent") {
174
+ return (
175
+ activeProfileProvider ??
176
+ siteProvider ??
177
+ siteProfileProvider ??
178
+ defaultProvider
179
+ );
180
+ }
181
+ return (
182
+ siteProvider ??
183
+ siteProfileProvider ??
184
+ activeProfileProvider ??
185
+ defaultProvider
186
+ );
122
187
  }
@@ -14,12 +14,20 @@ import type { WorkspaceMigration } from "./types.js";
14
14
  * high-effort / extended-thinking default, every turn would kick off an
15
15
  * expensive reasoning call and reject the assistant prefill.
16
16
  *
17
+ * Carry-forward: when `replySuggestion` is absent but the workspace has a
18
+ * customized `conversationStarters` entry (the call site this one was split
19
+ * out of), clone that entry into `replySuggestion` so users who previously
20
+ * tuned the combined call site keep their override. Only fall back to the
21
+ * fixed Haiku defaults when no `conversationStarters` override exists.
22
+ *
17
23
  * Mirrors `046-seed-conversation-starters-callsite`:
18
24
  * - Skip entirely when `VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH` is set
19
25
  * (platform overlay owns call-site seeds).
20
- * - Skip when the resolved provider is not Anthropic or OpenRouter (the
21
- * seeded model IDs are Anthropic-shaped, so mixing with another
22
- * provider would guarantee invalid-model errors).
26
+ * - In the fallback default path, skip when the resolved provider is not
27
+ * Anthropic or OpenRouter (the seeded model IDs are Anthropic-shaped, so
28
+ * mixing with another provider would guarantee invalid-model errors).
29
+ * The carry-forward path is provider-agnostic since the cloned config
30
+ * already reflects the user's explicit choice.
23
31
  * - No-op when `llm.callSites.replySuggestion` is already set.
24
32
  *
25
33
  * Idempotent, append-only — existing entries are untouched.
@@ -46,29 +54,34 @@ export const seedReplySuggestionCallsiteMigration: WorkspaceMigration = {
46
54
  }
47
55
 
48
56
  const llm = readObject(config.llm) ?? {};
49
- const defaultBlock = readObject(llm.default);
50
-
51
- const explicitProvider = readString(defaultBlock?.provider);
52
- if (
53
- explicitProvider !== undefined &&
54
- explicitProvider !== "anthropic" &&
55
- explicitProvider !== "openrouter"
56
- ) {
57
- return;
58
- }
59
- const provider = explicitProvider ?? "anthropic";
60
- const fastModel = resolveLatencyModel(provider);
61
- if (fastModel === undefined) return;
62
-
63
57
  const callSites = readObject(llm.callSites) ?? {};
64
58
  if (readObject(callSites.replySuggestion) !== null) return;
65
59
 
66
- callSites.replySuggestion = {
67
- model: fastModel,
68
- effort: "low",
69
- thinking: { enabled: false },
70
- };
60
+ const conversationStarters = readObject(callSites.conversationStarters);
61
+ let seed: Record<string, unknown>;
62
+ if (conversationStarters !== null) {
63
+ seed = { ...conversationStarters };
64
+ } else {
65
+ const defaultBlock = readObject(llm.default);
66
+ const explicitProvider = readString(defaultBlock?.provider);
67
+ if (
68
+ explicitProvider !== undefined &&
69
+ explicitProvider !== "anthropic" &&
70
+ explicitProvider !== "openrouter"
71
+ ) {
72
+ return;
73
+ }
74
+ const provider = explicitProvider ?? "anthropic";
75
+ const fastModel = resolveLatencyModel(provider);
76
+ if (fastModel === undefined) return;
77
+ seed = {
78
+ model: fastModel,
79
+ effort: "low",
80
+ thinking: { enabled: false },
81
+ };
82
+ }
71
83
 
84
+ callSites.replySuggestion = seed;
72
85
  llm.callSites = callSites;
73
86
  config.llm = llm;
74
87
  writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
@@ -57,7 +57,9 @@ export const repairRecallCallsiteEmptyProfileMigration: WorkspaceMigration = {
57
57
  if (cheapModel === undefined) return;
58
58
 
59
59
  delete recall.profile;
60
- recall.model = cheapModel;
60
+ if (readString(recall.model) === undefined) {
61
+ recall.model = cheapModel;
62
+ }
61
63
  callSites.recall = recall;
62
64
  llm.callSites = callSites;
63
65
  config.llm = llm;
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Workspace migration 083: Move config.systemPromptPrefix to a workspace file.
3
+ *
4
+ * The custom system prompt prefix used to live as a top-level config field
5
+ * (`config.json` → `systemPromptPrefix`). Editable system-prompt sections
6
+ * now live under `<workspace>/prompts/system/<id>.md`. This migration
7
+ * carries any existing config value over to `00-prefix.md` and strips the
8
+ * key from `config.json`.
9
+ *
10
+ * Behavior:
11
+ * - config has non-empty string + 00-prefix.md body is empty → write the
12
+ * prefix into the file body, preserving the bundled frontmatter
13
+ * - config has non-empty string + 00-prefix.md body already has content →
14
+ * leave the file alone (user authorship wins). Just strip the config key
15
+ * - config has null / missing field / whitespace-only string → no-op for
16
+ * the file; strip the key
17
+ *
18
+ * On filesystem write failure we **throw**. The runner marks the migration
19
+ * as `"failed"` so the operator can see it in checkpoints; we explicitly do
20
+ * not silently mark it complete. Note the config key is only deleted on a
21
+ * successful path, so the user's prefix is preserved in `config.json` until
22
+ * the next attempt.
23
+ *
24
+ * Per workspace-migration AGENTS.md, all helpers are inlined.
25
+ */
26
+
27
+ import {
28
+ existsSync,
29
+ mkdirSync,
30
+ readFileSync,
31
+ renameSync,
32
+ writeFileSync,
33
+ } from "node:fs";
34
+ import { join } from "node:path";
35
+
36
+ import { getLogger } from "../../util/logger.js";
37
+ import type { WorkspaceMigration } from "./types.js";
38
+
39
+ const log = getLogger("workspace-migration-083-system-prompt-prefix-to-file");
40
+
41
+ /**
42
+ * Default frontmatter used when the bundled 00-prefix.md is somehow missing
43
+ * (e.g. running a development build where seeding hasn't happened yet). In
44
+ * the normal startup order, `ensurePromptFiles()` runs before this migration,
45
+ * so the file already exists with the canonical bundled frontmatter.
46
+ */
47
+ const FALLBACK_PREFIX_FRONTMATTER = [
48
+ "---",
49
+ 'enabled: "!excludeCustomPrefix"',
50
+ "---",
51
+ "",
52
+ ].join("\n");
53
+
54
+ /** Matches a `---`-delimited frontmatter block at the start of a file. */
55
+ const FRONTMATTER_REGEX = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/;
56
+
57
+ export const systemPromptPrefixToFileMigration: WorkspaceMigration = {
58
+ id: "083-system-prompt-prefix-to-file",
59
+ description:
60
+ "Move config.systemPromptPrefix to <workspace>/prompts/system/00-prefix.md",
61
+
62
+ run(workspaceDir: string): void {
63
+ const configPath = join(workspaceDir, "config.json");
64
+ if (!existsSync(configPath)) return;
65
+
66
+ let raw: string;
67
+ try {
68
+ raw = readFileSync(configPath, "utf-8");
69
+ } catch (err) {
70
+ log.warn({ err, configPath }, "Failed to read config.json, skipping");
71
+ return;
72
+ }
73
+
74
+ let config: Record<string, unknown>;
75
+ try {
76
+ const parsed: unknown = JSON.parse(raw);
77
+ if (
78
+ parsed === null ||
79
+ typeof parsed !== "object" ||
80
+ Array.isArray(parsed)
81
+ ) {
82
+ log.warn({ configPath }, "config.json is not a JSON object, skipping");
83
+ return;
84
+ }
85
+ config = parsed as Record<string, unknown>;
86
+ } catch (err) {
87
+ log.warn({ err, configPath }, "Failed to parse config.json, skipping");
88
+ return;
89
+ }
90
+
91
+ if (!Object.prototype.hasOwnProperty.call(config, "systemPromptPrefix")) {
92
+ // Already migrated (or never set). Nothing to do.
93
+ return;
94
+ }
95
+
96
+ const value = config.systemPromptPrefix;
97
+ const trimmed = typeof value === "string" ? value.trim() : "";
98
+
99
+ const sysDir = join(workspaceDir, "prompts", "system");
100
+ const prefixFile = join(sysDir, "00-prefix.md");
101
+
102
+ if (trimmed.length > 0) {
103
+ // Read the existing file (if any) so we can preserve frontmatter and
104
+ // avoid clobbering user-authored content.
105
+ let existingFrontmatter = FALLBACK_PREFIX_FRONTMATTER;
106
+ let existingBody = "";
107
+ if (existsSync(prefixFile)) {
108
+ const existingRaw = readFileSync(prefixFile, "utf-8");
109
+ const fmMatch = existingRaw.match(FRONTMATTER_REGEX);
110
+ if (fmMatch) {
111
+ existingFrontmatter = fmMatch[0];
112
+ existingBody = existingRaw.slice(fmMatch[0].length).trim();
113
+ } else {
114
+ existingBody = existingRaw.trim();
115
+ }
116
+ }
117
+
118
+ if (existingBody.length > 0) {
119
+ // User already authored content here. Leave the file alone, but
120
+ // continue on to strip the now-superseded config key.
121
+ log.info(
122
+ { prefixFile },
123
+ "00-prefix.md already has user content; keeping it, dropping config key only",
124
+ );
125
+ } else {
126
+ mkdirSync(sysDir, { recursive: true });
127
+ writeFileSync(
128
+ prefixFile,
129
+ existingFrontmatter + trimmed + "\n",
130
+ "utf-8",
131
+ );
132
+ log.info({ prefixFile }, "Wrote system prompt prefix to file");
133
+ }
134
+ }
135
+
136
+ // Strip the field from config.json regardless of whether we wrote a body
137
+ // — the field is removed from the schema either way.
138
+ delete config.systemPromptPrefix;
139
+ writeJsonAtomic(configPath, config);
140
+ },
141
+
142
+ down(workspaceDir: string): void {
143
+ const configPath = join(workspaceDir, "config.json");
144
+ const prefixFile = join(workspaceDir, "prompts", "system", "00-prefix.md");
145
+
146
+ if (!existsSync(configPath) || !existsSync(prefixFile)) return;
147
+
148
+ let config: Record<string, unknown>;
149
+ try {
150
+ const parsed: unknown = JSON.parse(readFileSync(configPath, "utf-8"));
151
+ if (
152
+ parsed === null ||
153
+ typeof parsed !== "object" ||
154
+ Array.isArray(parsed)
155
+ )
156
+ return;
157
+ config = parsed as Record<string, unknown>;
158
+ } catch {
159
+ return;
160
+ }
161
+
162
+ let body = "";
163
+ try {
164
+ const raw = readFileSync(prefixFile, "utf-8");
165
+ const stripped = raw.replace(FRONTMATTER_REGEX, "").trim();
166
+ // Strip `_` comment lines too — same convention as runtime renderer.
167
+ body = stripped
168
+ .split("\n")
169
+ .filter((line) => !line.trimStart().startsWith("_"))
170
+ .join("\n")
171
+ .trim();
172
+ } catch {
173
+ return;
174
+ }
175
+
176
+ config.systemPromptPrefix = body.length > 0 ? body : null;
177
+ writeJsonAtomic(configPath, config);
178
+ },
179
+ };
180
+
181
+ /**
182
+ * Atomic JSON write: write to temp file alongside the target, then rename.
183
+ * Throws on failure so callers can propagate to the migration runner (which
184
+ * marks the migration `"failed"`). Inlined per workspace-migration
185
+ * self-containment rule.
186
+ */
187
+ function writeJsonAtomic(path: string, data: unknown): void {
188
+ const tmp = `${path}.tmp.${process.pid}.${Date.now()}`;
189
+ writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n", "utf-8");
190
+ renameSync(tmp, path);
191
+ }