@vellumai/assistant 0.8.1 → 0.8.2

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 (506) hide show
  1. package/ARCHITECTURE.md +2 -7
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +5 -0
  5. package/docker-init-apt-root.sh +94 -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 +325 -3
  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-wake-disk-pressure-callsite.test.ts +131 -0
  21. package/src/__tests__/anthropic-provider.test.ts +45 -0
  22. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  23. package/src/__tests__/app-executors.test.ts +220 -4
  24. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  25. package/src/__tests__/bundled-asset.test.ts +6 -6
  26. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  27. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  28. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  29. package/src/__tests__/clawhub.test.ts +75 -16
  30. package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
  31. package/src/__tests__/config-schema.test.ts +21 -0
  32. package/src/__tests__/config-set-route.test.ts +80 -0
  33. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  34. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  35. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  38. package/src/__tests__/context-token-estimator.test.ts +1 -0
  39. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  42. package/src/__tests__/conversation-agent-loop.test.ts +2 -0
  43. package/src/__tests__/conversation-error.test.ts +42 -3
  44. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  45. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  46. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  47. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  48. package/src/__tests__/conversation-pairing.test.ts +54 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-queue.test.ts +4 -1
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
  53. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  54. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  55. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  56. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  59. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  60. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  61. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  62. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  63. package/src/__tests__/dm-backfill.test.ts +121 -10
  64. package/src/__tests__/document-tool-security.test.ts +258 -0
  65. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  66. package/src/__tests__/edit-propagation.test.ts +33 -0
  67. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  68. package/src/__tests__/external-plugin-loader.test.ts +60 -36
  69. package/src/__tests__/filing-service.test.ts +140 -0
  70. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  71. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  72. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  73. package/src/__tests__/helpers/wait-for.ts +21 -0
  74. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  75. package/src/__tests__/history-repair.test.ts +73 -0
  76. package/src/__tests__/host-app-control-proxy.test.ts +266 -10
  77. package/src/__tests__/image-credentials.test.ts +1 -1
  78. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  79. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  80. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  81. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  82. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  83. package/src/__tests__/injector-chain.test.ts +10 -8
  84. package/src/__tests__/install-skill-routing.test.ts +155 -37
  85. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
  86. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  87. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  88. package/src/__tests__/llm-catalog-parity.test.ts +55 -13
  89. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
  90. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  91. package/src/__tests__/llm-usage-store.test.ts +114 -0
  92. package/src/__tests__/managed-profile-guard.test.ts +31 -29
  93. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  94. package/src/__tests__/managed-store.test.ts +84 -192
  95. package/src/__tests__/media-generate-image.test.ts +1 -1
  96. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  97. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  98. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  99. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  100. package/src/__tests__/openai-provider.test.ts +24 -0
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  102. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  103. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  104. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
  105. package/src/__tests__/platform.test.ts +2 -0
  106. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  107. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  108. package/src/__tests__/plugin-external-api.test.ts +68 -0
  109. package/src/__tests__/plugin-registry.test.ts +0 -77
  110. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  111. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  112. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  113. package/src/__tests__/plugin-types.test.ts +3 -13
  114. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  115. package/src/__tests__/process-message-display-content.test.ts +421 -0
  116. package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
  117. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  118. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
  119. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  120. package/src/__tests__/schedule-routes.test.ts +50 -3
  121. package/src/__tests__/schedule-store.test.ts +94 -0
  122. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  123. package/src/__tests__/schema-transforms.test.ts +20 -0
  124. package/src/__tests__/search-skills-unified.test.ts +0 -5
  125. package/src/__tests__/server-history-render.test.ts +43 -0
  126. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  127. package/src/__tests__/skill-load-tool.test.ts +27 -89
  128. package/src/__tests__/skill-memory.test.ts +23 -3
  129. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  130. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  131. package/src/__tests__/skills-install-extract.test.ts +49 -38
  132. package/src/__tests__/skills-install-staging.test.ts +159 -0
  133. package/src/__tests__/skills-uninstall.test.ts +9 -41
  134. package/src/__tests__/skills.test.ts +51 -58
  135. package/src/__tests__/slack-channel-config.test.ts +9 -0
  136. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  137. package/src/__tests__/system-prompt.test.ts +737 -63
  138. package/src/__tests__/terminal-tools.test.ts +28 -1
  139. package/src/__tests__/thread-backfill.test.ts +557 -27
  140. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  141. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  142. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  143. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  145. package/src/__tests__/tool-executor.test.ts +16 -4
  146. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  147. package/src/__tests__/turn-events-store.test.ts +256 -0
  148. package/src/__tests__/twilio-routes.test.ts +4 -0
  149. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  150. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  151. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  152. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  153. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  154. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  155. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  156. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  157. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  158. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  159. package/src/acp/resolve-agent.ts +1 -1
  160. package/src/agent/image-optimize.ts +13 -5
  161. package/src/calls/voice-session-bridge.ts +61 -42
  162. package/src/channels/types.ts +108 -0
  163. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  164. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  165. package/src/cli/commands/__tests__/schedules.test.ts +491 -0
  166. package/src/cli/commands/changelog.ts +106 -42
  167. package/src/cli/commands/conversations.ts +102 -17
  168. package/src/cli/commands/default-action.ts +10 -53
  169. package/src/cli/commands/notifications.ts +329 -317
  170. package/src/cli/commands/plugins.ts +185 -0
  171. package/src/cli/commands/schedules.ts +391 -0
  172. package/src/cli/commands/telemetry.ts +40 -0
  173. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  174. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  177. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  178. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  179. package/src/cli/lib/cli-colors.ts +12 -0
  180. package/src/cli/lib/confirm-prompt.ts +79 -0
  181. package/src/cli/lib/install-from-github.ts +304 -0
  182. package/src/cli/lib/list-installed-plugins.ts +137 -0
  183. package/src/cli/lib/uninstall-plugin.ts +82 -0
  184. package/src/cli/lib/unknown-command.ts +111 -0
  185. package/src/cli/program.ts +38 -2
  186. package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
  187. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  188. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  189. package/src/config/bundled-skills/document/SKILL.md +23 -3
  190. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  191. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  192. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  193. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  194. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  195. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  196. package/src/config/bundled-tool-registry.ts +6 -0
  197. package/src/config/feature-flag-registry.json +41 -1
  198. package/src/config/loader.ts +64 -38
  199. package/src/config/schema.ts +7 -10
  200. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  201. package/src/config/schemas/channels.ts +8 -0
  202. package/src/config/schemas/compaction.ts +28 -0
  203. package/src/config/schemas/heartbeat.ts +9 -0
  204. package/src/config/schemas/llm-request-logs.ts +31 -7
  205. package/src/config/schemas/llm.ts +3 -0
  206. package/src/config/schemas/memory-retrieval.ts +18 -0
  207. package/src/config/schemas/tools.ts +14 -0
  208. package/src/config/skills.ts +3 -96
  209. package/src/context/compactor.ts +1047 -0
  210. package/src/context/token-estimator.ts +2 -2
  211. package/src/context/window-manager.ts +197 -1520
  212. package/src/credential-execution/managed-catalog.ts +37 -0
  213. package/src/credential-health/credential-health-service.ts +280 -19
  214. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
  215. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  216. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  217. package/src/daemon/approval-generators.ts +8 -6
  218. package/src/daemon/config-watcher.ts +94 -31
  219. package/src/daemon/conversation-agent-loop.ts +169 -9
  220. package/src/daemon/conversation-error.ts +171 -37
  221. package/src/daemon/conversation-lifecycle.ts +53 -40
  222. package/src/daemon/conversation-messaging.ts +25 -6
  223. package/src/daemon/conversation-process.ts +49 -12
  224. package/src/daemon/conversation-runtime-assembly.ts +16 -1
  225. package/src/daemon/conversation-slash.ts +12 -5
  226. package/src/daemon/conversation-store.ts +11 -4
  227. package/src/daemon/conversation-tool-setup.ts +39 -7
  228. package/src/daemon/conversation.ts +33 -1
  229. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  230. package/src/daemon/first-greeting.ts +22 -2
  231. package/src/daemon/handlers/config-model.ts +6 -5
  232. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  233. package/src/daemon/handlers/shared.ts +14 -5
  234. package/src/daemon/handlers/skills.ts +111 -108
  235. package/src/daemon/history-repair.ts +28 -1
  236. package/src/daemon/host-app-control-proxy.ts +98 -23
  237. package/src/daemon/lifecycle.ts +45 -35
  238. package/src/daemon/meet-host-supervisor.ts +5 -4
  239. package/src/daemon/memory-v2-startup.ts +49 -0
  240. package/src/daemon/message-protocol.ts +1 -0
  241. package/src/daemon/message-types/conversations.ts +25 -0
  242. package/src/daemon/message-types/messages.ts +61 -0
  243. package/src/daemon/message-types/subagents.ts +1 -0
  244. package/src/daemon/message-types/sync.ts +1 -0
  245. package/src/daemon/pkb-reminder-builder.test.ts +1 -1
  246. package/src/daemon/pkb-reminder-builder.ts +1 -1
  247. package/src/daemon/plugin-source-watcher.ts +146 -0
  248. package/src/daemon/process-message.ts +21 -3
  249. package/src/daemon/server.ts +11 -2
  250. package/src/daemon/skill-memory-refresh.ts +29 -0
  251. package/src/documents/document-store.ts +221 -3
  252. package/src/embedded/plugin-api.ts +40 -0
  253. package/src/filing/filing-service.ts +39 -0
  254. package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
  255. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  256. package/src/heartbeat/heartbeat-service.ts +41 -0
  257. package/src/home/__tests__/feed-types.test.ts +40 -0
  258. package/src/home/feed-types.ts +22 -0
  259. package/src/home/post-connect-feed.ts +1 -0
  260. package/src/index.ts +18 -1
  261. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  262. package/src/mcp/client.ts +20 -4
  263. package/src/media/image-credentials.ts +3 -3
  264. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  265. package/src/memory/__tests__/conversation-queries.test.ts +263 -0
  266. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  267. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  268. package/src/memory/__tests__/message-content.test.ts +35 -0
  269. package/src/memory/bookmark-crud.ts +42 -10
  270. package/src/memory/context-search/sources/conversations.ts +62 -2
  271. package/src/memory/context-search/sources/workspace.ts +4 -0
  272. package/src/memory/conversation-crud.ts +63 -19
  273. package/src/memory/conversation-queries.ts +110 -10
  274. package/src/memory/db-init.ts +6 -0
  275. package/src/memory/delivery-crud.ts +152 -5
  276. package/src/memory/embedding-backend.ts +4 -4
  277. package/src/memory/external-conversation-store.ts +66 -5
  278. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
  279. package/src/memory/graph/conversation-graph-memory.ts +31 -15
  280. package/src/memory/graph/tools.ts +3 -3
  281. package/src/memory/indexer.ts +34 -29
  282. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  283. package/src/memory/jobs/embed-concept-page.ts +20 -11
  284. package/src/memory/jobs-worker.ts +6 -1
  285. package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
  286. package/src/memory/llm-request-log-source.ts +19 -52
  287. package/src/memory/llm-usage-store.ts +125 -5
  288. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  289. package/src/memory/message-content.ts +1 -1
  290. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  291. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  292. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  293. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  294. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  295. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  296. package/src/memory/migrations/index.ts +6 -0
  297. package/src/memory/migrations/registry.ts +8 -0
  298. package/src/memory/onboarding-events-store.ts +106 -0
  299. package/src/memory/schema/bookmarks.ts +0 -2
  300. package/src/memory/schema/calls.ts +1 -0
  301. package/src/memory/schema/inference.ts +1 -3
  302. package/src/memory/schema/infrastructure.ts +12 -0
  303. package/src/memory/turn-events-store.ts +127 -2
  304. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  305. package/src/memory/v2/__tests__/injection.test.ts +98 -8
  306. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  307. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  308. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  309. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  310. package/src/memory/v2/__tests__/router.test.ts +15 -0
  311. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  312. package/src/memory/v2/injection.ts +32 -6
  313. package/src/memory/v2/migration.ts +49 -19
  314. package/src/memory/v2/page-index.ts +35 -5
  315. package/src/memory/v2/prompts/router.ts +11 -8
  316. package/src/memory/v2/prompts/sweep.ts +2 -2
  317. package/src/memory/v2/qdrant.ts +135 -7
  318. package/src/memory/v2/router.ts +9 -8
  319. package/src/memory/v2/skill-store.ts +120 -35
  320. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  321. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  322. package/src/messaging/providers/slack/adapter.ts +43 -5
  323. package/src/messaging/providers/slack/client.ts +27 -0
  324. package/src/messaging/providers/slack/deep-link.ts +65 -0
  325. package/src/messaging/providers/slack/download.ts +104 -0
  326. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  327. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  328. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  329. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  330. package/src/messaging/providers/slack/types.ts +20 -1
  331. package/src/notifications/conversation-pairing.ts +2 -1
  332. package/src/notifications/decision-engine.ts +2 -1
  333. package/src/notifications/emit-signal.ts +20 -1
  334. package/src/notifications/home-feed-side-effect.ts +54 -0
  335. package/src/notifications/signal.ts +3 -1
  336. package/src/oauth/connection-resolver.ts +8 -4
  337. package/src/oauth/platform-connection.ts +6 -2
  338. package/src/oauth/seed-providers.ts +10 -1
  339. package/src/permissions/checker.ts +2 -0
  340. package/src/permissions/ipc-risk-types.ts +1 -0
  341. package/src/permissions/question-prompter.test.ts +416 -0
  342. package/src/permissions/question-prompter.ts +294 -0
  343. package/src/platform/client.test.ts +1 -1
  344. package/src/platform/client.ts +1 -1
  345. package/src/plugin-api/constants.ts +26 -0
  346. package/src/plugin-api/index.ts +34 -1
  347. package/src/plugin-api/types.ts +104 -22
  348. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  349. package/src/plugins/defaults/compaction.ts +0 -4
  350. package/src/plugins/defaults/empty-response.ts +0 -2
  351. package/src/plugins/defaults/history-repair.ts +0 -2
  352. package/src/plugins/defaults/injectors.ts +36 -3
  353. package/src/plugins/defaults/llm-call.ts +0 -2
  354. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  355. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  356. package/src/plugins/defaults/persistence.ts +0 -2
  357. package/src/plugins/defaults/title-generate.ts +0 -5
  358. package/src/plugins/defaults/token-estimate.ts +0 -2
  359. package/src/plugins/defaults/tool-error.ts +0 -7
  360. package/src/plugins/defaults/tool-execute.ts +0 -2
  361. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  362. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  363. package/src/plugins/external-api.ts +104 -0
  364. package/src/plugins/external-plugin-loader.ts +105 -32
  365. package/src/plugins/feature-gate.ts +22 -0
  366. package/src/plugins/pipeline.ts +37 -0
  367. package/src/plugins/registry.ts +48 -80
  368. package/src/plugins/types.ts +31 -26
  369. package/src/plugins/user-loader.ts +21 -2
  370. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  371. package/src/proactive-artifact/job.test.ts +37 -5
  372. package/src/prompts/__tests__/system-prompt.test.ts +12 -0
  373. package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
  374. package/src/prompts/normalize-onboarding.ts +27 -0
  375. package/src/prompts/sections.ts +302 -0
  376. package/src/prompts/system-prompt.ts +63 -166
  377. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  378. package/src/prompts/templates/system-sections.ts +173 -0
  379. package/src/providers/__tests__/inference.test.ts +22 -7
  380. package/src/providers/anthropic/client.ts +28 -28
  381. package/src/providers/connection-resolution.ts +7 -0
  382. package/src/providers/inference/adapter-factory.ts +41 -4
  383. package/src/providers/inference/connections.ts +74 -29
  384. package/src/providers/inference/resolve-auth.ts +12 -4
  385. package/src/providers/model-catalog.ts +294 -12
  386. package/src/providers/openai/chat-completions-provider.ts +10 -2
  387. package/src/providers/openrouter/client.ts +7 -0
  388. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
  389. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  390. package/src/providers/provider-availability.ts +17 -2
  391. package/src/providers/provider-catalog-visibility.ts +36 -0
  392. package/src/providers/registry.ts +22 -14
  393. package/src/providers/retry.ts +47 -1
  394. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  395. package/src/runtime/agent-wake.ts +42 -14
  396. package/src/runtime/auth/route-policy.ts +8 -1
  397. package/src/runtime/btw-sidechain.ts +2 -0
  398. package/src/runtime/http-types.ts +19 -0
  399. package/src/runtime/migrations/origin-mode.ts +1 -1
  400. package/src/runtime/pending-interactions.ts +1 -0
  401. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  402. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  403. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
  404. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  405. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  406. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  407. package/src/runtime/routes/acp-routes.ts +5 -3
  408. package/src/runtime/routes/auth-routes.ts +1 -1
  409. package/src/runtime/routes/bookmark-routes.ts +5 -3
  410. package/src/runtime/routes/btw-routes.ts +5 -1
  411. package/src/runtime/routes/channel-availability-routes.ts +121 -0
  412. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  413. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  414. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  415. package/src/runtime/routes/conversation-query-routes.ts +40 -35
  416. package/src/runtime/routes/conversation-routes.ts +90 -11
  417. package/src/runtime/routes/documents-routes.ts +25 -86
  418. package/src/runtime/routes/group-routes.ts +5 -0
  419. package/src/runtime/routes/inbound-conversation.ts +28 -8
  420. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  421. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  422. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  423. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  424. package/src/runtime/routes/index.ts +6 -0
  425. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  426. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  427. package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
  428. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  429. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  430. package/src/runtime/routes/integrations/twilio.ts +6 -13
  431. package/src/runtime/routes/notification-routes.ts +1 -1
  432. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  433. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  434. package/src/runtime/routes/question-routes.ts +259 -0
  435. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  436. package/src/runtime/routes/schedule-routes.ts +4 -7
  437. package/src/runtime/routes/subagents-routes.ts +57 -18
  438. package/src/runtime/routes/telemetry-routes.ts +27 -0
  439. package/src/runtime/routes/tts-routes.ts +27 -2
  440. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  441. package/src/runtime/routes/workspace-routes.ts +28 -0
  442. package/src/runtime/services/conversation-serializer.ts +39 -7
  443. package/src/runtime/sync/resource-sync-events.ts +93 -1
  444. package/src/schedule/schedule-store.ts +27 -2
  445. package/src/schedule/scheduler.ts +9 -1
  446. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  447. package/src/security/untrusted-content.ts +93 -8
  448. package/src/skills/catalog-files.ts +1 -1
  449. package/src/skills/catalog-install.ts +233 -116
  450. package/src/skills/clawhub.ts +70 -13
  451. package/src/skills/managed-store.ts +4 -119
  452. package/src/skills/skillssh-registry.ts +27 -48
  453. package/src/subagent/manager.ts +15 -7
  454. package/src/telemetry/types.ts +113 -1
  455. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  456. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  457. package/src/tools/apps/executors.ts +58 -7
  458. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  459. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  460. package/src/tools/browser/browser-execution.ts +15 -11
  461. package/src/tools/computer-use/definitions.ts +3 -3
  462. package/src/tools/credentials/vault.ts +1 -1
  463. package/src/tools/document/document-tool.ts +124 -1
  464. package/src/tools/filesystem/edit.ts +1 -1
  465. package/src/tools/filesystem/list.ts +1 -1
  466. package/src/tools/filesystem/read.ts +1 -1
  467. package/src/tools/filesystem/write.ts +5 -2
  468. package/src/tools/host-filesystem/transfer.ts +1 -1
  469. package/src/tools/host-terminal/host-shell.ts +1 -1
  470. package/src/tools/permission-checker.ts +1 -1
  471. package/src/tools/registry.ts +17 -7
  472. package/src/tools/schedule/create.ts +2 -2
  473. package/src/tools/schema-transforms.ts +7 -2
  474. package/src/tools/side-effects.ts +1 -0
  475. package/src/tools/skills/delete-managed.ts +4 -4
  476. package/src/tools/skills/execute.ts +1 -1
  477. package/src/tools/skills/scaffold-managed.ts +3 -2
  478. package/src/tools/subagent/notify-parent.ts +1 -1
  479. package/src/tools/system/request-permission.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +60 -1
  481. package/src/tools/tool-manifest.ts +2 -0
  482. package/src/tools/types.ts +72 -21
  483. package/src/tools/ui-surface/definitions.ts +6 -5
  484. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  485. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  486. package/src/types/onboarding-context.ts +2 -0
  487. package/src/util/errors.ts +17 -0
  488. package/src/util/platform.ts +10 -0
  489. package/src/watcher/__tests__/engine.test.ts +22 -0
  490. package/src/watcher/engine.ts +6 -2
  491. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  492. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  493. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  494. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  495. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  496. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  497. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  498. package/src/workspace/migrations/registry.ts +8 -0
  499. package/src/workspace/migrations/runner.ts +39 -9
  500. package/src/workspace/migrations/types.ts +4 -0
  501. package/examples/plugins/echo/bun.lock +0 -25
  502. package/src/__tests__/context-window-manager.test.ts +0 -2481
  503. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  504. package/src/context/prompts/compact.md +0 -26
  505. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  506. /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
@@ -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
+ }