@vellumai/assistant 0.8.7 → 0.8.8

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 (387) hide show
  1. package/Dockerfile +20 -4
  2. package/docker-entrypoint.sh +4 -2
  3. package/docker-init-apt-root.sh +3 -1
  4. package/docker-kata-apt-env.sh +3 -1
  5. package/docker-kata-runtime-family.sh +12 -0
  6. package/docs/architecture/memory.md +1 -1
  7. package/docs/plugins.md +75 -79
  8. package/examples/plugins/echo/README.md +6 -12
  9. package/examples/plugins/echo/register.ts +0 -41
  10. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
  11. package/openapi.yaml +3381 -348
  12. package/package.json +1 -1
  13. package/scripts/generate-openapi.ts +68 -41
  14. package/src/__tests__/agent-loop-exit-reason.test.ts +34 -39
  15. package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
  16. package/src/__tests__/agent-loop.test.ts +37 -87
  17. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  18. package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
  19. package/src/__tests__/annotate-risk-options.test.ts +2 -3
  20. package/src/__tests__/anthropic-provider.test.ts +95 -2
  21. package/src/__tests__/assistant-event-hub.test.ts +25 -0
  22. package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
  23. package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
  24. package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
  25. package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
  26. package/src/__tests__/btw-routes.test.ts +62 -3
  27. package/src/__tests__/build-persisted-content.test.ts +184 -0
  28. package/src/__tests__/catalog-files.test.ts +1 -1
  29. package/src/__tests__/clawhub-files.test.ts +1 -1
  30. package/src/__tests__/compaction-pipeline.test.ts +1 -1
  31. package/src/__tests__/compaction.benchmark.test.ts +0 -30
  32. package/src/__tests__/config-watcher.test.ts +1 -1
  33. package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
  34. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -2
  35. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -4
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +313 -1136
  37. package/src/__tests__/conversation-agent-loop.test.ts +596 -1616
  38. package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
  39. package/src/__tests__/conversation-history-web-search.test.ts +11 -1
  40. package/src/__tests__/conversation-pairing.test.ts +4 -31
  41. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +26 -5
  43. package/src/__tests__/conversation-queue.test.ts +2 -0
  44. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
  45. package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +170 -229
  47. package/src/__tests__/conversation-runtime-workspace.test.ts +3 -24
  48. package/src/__tests__/conversation-slash-commands.test.ts +8 -42
  49. package/src/__tests__/conversation-slash-queue.test.ts +6 -1
  50. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
  51. package/src/__tests__/conversation-sync-tags.test.ts +27 -15
  52. package/src/__tests__/conversation-title-service.test.ts +135 -2
  53. package/src/__tests__/conversation-workspace-injection.test.ts +6 -1
  54. package/src/__tests__/cross-provider-web-search.test.ts +214 -1
  55. package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
  56. package/src/__tests__/dm-persistence.test.ts +5 -1
  57. package/src/__tests__/empty-response-hook.test.ts +304 -0
  58. package/src/__tests__/feature-flag-test-helpers.ts +2 -2
  59. package/src/__tests__/gemini-image-service.test.ts +13 -0
  60. package/src/__tests__/helpers/mock-provider.ts +110 -0
  61. package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
  62. package/src/__tests__/history-repair-hook.test.ts +1 -0
  63. package/src/__tests__/identity-intro-cache.test.ts +12 -100
  64. package/src/__tests__/identity-routes.test.ts +248 -7
  65. package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
  66. package/src/__tests__/injector-background-turn.test.ts +2 -8
  67. package/src/__tests__/injector-chain.test.ts +106 -270
  68. package/src/__tests__/injector-disk-pressure.test.ts +3 -12
  69. package/src/__tests__/injector-document-comments.test.ts +2 -2
  70. package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
  71. package/src/__tests__/injector-v3-suppression.test.ts +31 -37
  72. package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
  73. package/src/__tests__/list-messages-page-latest.test.ts +60 -0
  74. package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
  75. package/src/__tests__/llm-usage-store.test.ts +223 -1
  76. package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
  77. package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
  78. package/src/__tests__/native-web-search.test.ts +191 -0
  79. package/src/__tests__/onboarding-template-contract.test.ts +2 -0
  80. package/src/__tests__/openai-image-service.test.ts +17 -0
  81. package/src/__tests__/openai-provider.test.ts +31 -1
  82. package/src/__tests__/persist-unsendable-image.test.ts +215 -0
  83. package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
  84. package/src/__tests__/pipeline-runner.test.ts +29 -39
  85. package/src/__tests__/pkb-autoinject.test.ts +2 -5
  86. package/src/__tests__/plugin-bootstrap.test.ts +13 -28
  87. package/src/__tests__/plugin-registry.test.ts +0 -27
  88. package/src/__tests__/plugin-types.test.ts +2 -125
  89. package/src/__tests__/process-message-display-content.test.ts +6 -2
  90. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
  91. package/src/__tests__/resolve-trust-class.test.ts +4 -4
  92. package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
  93. package/src/__tests__/schedule-routes.test.ts +603 -2
  94. package/src/__tests__/schedule-store.test.ts +41 -0
  95. package/src/__tests__/schedule-tools.test.ts +35 -0
  96. package/src/__tests__/server-history-render.test.ts +314 -1
  97. package/src/__tests__/skillssh-files.test.ts +1 -1
  98. package/src/__tests__/system-prompt.test.ts +20 -0
  99. package/src/__tests__/task-scheduler.test.ts +162 -1
  100. package/src/__tests__/terminal-tools.test.ts +6 -1
  101. package/src/__tests__/title-generate-hook.test.ts +319 -0
  102. package/src/__tests__/tool-error-hook.test.ts +278 -0
  103. package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
  104. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  105. package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
  106. package/src/__tests__/tool-result-truncation.test.ts +0 -2
  107. package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
  108. package/src/__tests__/ui-work-result-surface.test.ts +159 -0
  109. package/src/__tests__/usage-routes.test.ts +285 -1
  110. package/src/__tests__/user-plugin-loader.test.ts +2 -2
  111. package/src/__tests__/voice-session-bridge.test.ts +6 -3
  112. package/src/__tests__/web-search-backend-failure.test.ts +166 -0
  113. package/src/agent/loop.ts +346 -442
  114. package/src/api/events/assistant-thinking-delta.ts +33 -0
  115. package/src/api/events/tool-output-chunk.ts +45 -0
  116. package/src/api/events/tool-use-preview-start.ts +32 -0
  117. package/src/api/events/trace-event.ts +69 -0
  118. package/src/api/index.ts +48 -13
  119. package/src/api/responses/conversation-message.ts +368 -0
  120. package/src/avatar/__tests__/avatar-store.test.ts +34 -29
  121. package/src/cli/commands/__tests__/notifications.test.ts +58 -14
  122. package/src/cli/commands/notifications.ts +112 -60
  123. package/src/config/assistant-feature-flags.ts +22 -11
  124. package/src/config/bundled-skills/app-builder/SKILL.md +3 -20
  125. package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
  126. package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
  127. package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
  128. package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
  129. package/src/config/bundled-skills/document-editor/SKILL.md +1 -1
  130. package/src/config/bundled-skills/messaging/SKILL.md +0 -7
  131. package/src/config/feature-flag-cache.ts +3 -3
  132. package/src/config/feature-flag-registry.json +35 -3
  133. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  134. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
  135. package/src/config/schemas/llm.ts +1 -0
  136. package/src/config/schemas/memory-v2.ts +8 -0
  137. package/src/config/schemas/memory-v3.ts +8 -0
  138. package/src/config/schemas/platform.ts +8 -0
  139. package/src/config/seed-inference-profiles.ts +2 -2
  140. package/src/config/skills.ts +13 -0
  141. package/src/context/compactor.ts +1 -1
  142. package/src/context/strip-injections.ts +122 -0
  143. package/src/context/token-estimator.ts +23 -0
  144. package/src/context/tool-result-truncation.ts +0 -23
  145. package/src/context/window-manager.ts +3 -6
  146. package/src/credential-execution/executable-discovery.ts +16 -0
  147. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
  148. package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
  149. package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
  150. package/src/daemon/assistant-attachments.ts +1 -1
  151. package/src/daemon/config-watcher.ts +2 -2
  152. package/src/daemon/context-overflow-reducer.ts +0 -1
  153. package/src/daemon/conversation-agent-loop-handlers.ts +605 -153
  154. package/src/daemon/conversation-agent-loop.ts +281 -760
  155. package/src/daemon/conversation-history.ts +5 -4
  156. package/src/daemon/conversation-lifecycle.ts +3 -4
  157. package/src/daemon/conversation-messaging.ts +7 -6
  158. package/src/daemon/conversation-process.ts +11 -16
  159. package/src/daemon/conversation-runtime-assembly.ts +130 -347
  160. package/src/daemon/conversation-slash.ts +6 -25
  161. package/src/daemon/conversation-surfaces.ts +222 -4
  162. package/src/daemon/conversation-tool-setup.ts +2 -29
  163. package/src/daemon/conversation.ts +32 -14
  164. package/src/daemon/external-plugins-bootstrap.ts +9 -10
  165. package/src/daemon/handlers/config-a2a.ts +51 -36
  166. package/src/daemon/handlers/config-slack-channel.ts +20 -14
  167. package/src/daemon/handlers/config-telegram.ts +16 -2
  168. package/src/daemon/handlers/shared.ts +156 -84
  169. package/src/daemon/handlers/skills.ts +39 -10
  170. package/src/daemon/lifecycle.ts +4 -0
  171. package/src/daemon/message-types/apps.ts +1 -29
  172. package/src/daemon/message-types/messages.ts +9 -57
  173. package/src/daemon/message-types/skills.ts +2 -0
  174. package/src/daemon/message-types/surfaces.ts +136 -3
  175. package/src/daemon/now-scratchpad.ts +21 -0
  176. package/src/daemon/orphan-reaper.test.ts +210 -0
  177. package/src/daemon/orphan-reaper.ts +240 -0
  178. package/src/daemon/persist-unsendable-image.ts +117 -0
  179. package/src/daemon/process-message.ts +1 -3
  180. package/src/daemon/trace-emitter.ts +6 -4
  181. package/src/daemon/trust-context.ts +19 -0
  182. package/src/daemon/wake-target-adapter.ts +3 -1
  183. package/src/home/home-greeting-cache.ts +24 -1
  184. package/src/ipc/gateway-client.test.ts +2 -2
  185. package/src/ipc/gateway-client.ts +3 -3
  186. package/src/media/gemini-image-service.ts +15 -0
  187. package/src/media/openai-image-service.ts +14 -0
  188. package/src/media/types.ts +34 -0
  189. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
  190. package/src/memory/auth-fallback-events-store.ts +94 -0
  191. package/src/memory/conversation-title-service.ts +65 -41
  192. package/src/memory/db-init.ts +4 -0
  193. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  194. package/src/memory/graph/conversation-graph-memory.ts +65 -0
  195. package/src/memory/jobs-store.ts +33 -0
  196. package/src/memory/jobs-worker.ts +31 -4
  197. package/src/memory/llm-usage-store.ts +224 -50
  198. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
  199. package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
  200. package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
  201. package/src/memory/migrations/index.ts +2 -0
  202. package/src/memory/pkb/autoinject.ts +61 -0
  203. package/src/memory/pkb/context.ts +50 -0
  204. package/src/memory/pkb/types.ts +14 -0
  205. package/src/memory/schedule-attribution-sql.ts +104 -0
  206. package/src/memory/schema/infrastructure.ts +16 -0
  207. package/src/memory/usage-grouped-buckets.ts +6 -1
  208. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -1
  209. package/src/memory/v2/consolidation-job.ts +1 -1
  210. package/src/memory/v3/__tests__/health.test.ts +16 -0
  211. package/src/memory/v3/__tests__/orchestrate.test.ts +45 -9
  212. package/src/memory/v3/__tests__/provider-blocks.test.ts +13 -0
  213. package/src/memory/v3/__tests__/router.test.ts +101 -29
  214. package/src/memory/v3/__tests__/selector.test.ts +93 -27
  215. package/src/memory/v3/__tests__/shadow-plugin.test.ts +23 -5
  216. package/src/memory/v3/health.ts +0 -0
  217. package/src/memory/v3/llm-retry.ts +32 -0
  218. package/src/memory/v3/orchestrate.ts +26 -14
  219. package/src/memory/v3/provider-blocks.ts +15 -5
  220. package/src/memory/v3/router.ts +48 -42
  221. package/src/memory/v3/selector.ts +57 -42
  222. package/src/memory/v3/shadow-plugin.ts +47 -15
  223. package/src/memory/v3/types.ts +8 -0
  224. package/src/notifications/conversation-pairing.ts +8 -15
  225. package/src/notifications/decision-engine.ts +6 -3
  226. package/src/notifications/home-feed-side-effect.ts +12 -1
  227. package/src/permissions/prompter.ts +4 -0
  228. package/src/plugin-api/constants.ts +4 -0
  229. package/src/plugin-api/index.ts +8 -1
  230. package/src/plugin-api/types.ts +151 -1
  231. package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
  232. package/src/plugins/defaults/empty-response/register.ts +8 -13
  233. package/src/plugins/defaults/index.ts +1 -15
  234. package/src/plugins/defaults/injectors/register.ts +243 -74
  235. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +91 -0
  236. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
  237. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
  238. package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
  239. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
  240. package/src/plugins/defaults/title-generate/package.json +1 -1
  241. package/src/plugins/defaults/title-generate/register.ts +18 -18
  242. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
  243. package/src/plugins/defaults/tool-error/package.json +1 -1
  244. package/src/plugins/defaults/tool-error/register.ts +9 -21
  245. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
  246. package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
  247. package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
  248. package/src/plugins/pipeline.ts +6 -18
  249. package/src/plugins/registry.ts +8 -25
  250. package/src/plugins/types.ts +43 -474
  251. package/src/proactive-artifact/aux-message-injector.ts +3 -3
  252. package/src/proactive-artifact/job.test.ts +7 -12
  253. package/src/prompts/__tests__/system-prompt.test.ts +36 -0
  254. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +62 -0
  255. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  256. package/src/prompts/templates/system-sections.ts +15 -0
  257. package/src/providers/anthropic/client.ts +37 -29
  258. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
  259. package/src/providers/openai/chat-completions-provider.ts +44 -0
  260. package/src/providers/openrouter/client.ts +1 -0
  261. package/src/providers/placeholder-sentinels.ts +35 -0
  262. package/src/runtime/__tests__/agent-wake.test.ts +5 -1
  263. package/src/runtime/agent-wake.ts +2 -2
  264. package/src/runtime/assistant-event-hub.ts +36 -6
  265. package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
  266. package/src/runtime/http-router.ts +16 -21
  267. package/src/runtime/http-types.ts +16 -70
  268. package/src/runtime/pending-interactions.ts +1 -0
  269. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
  270. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
  271. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
  272. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  273. package/src/runtime/routes/app-management-routes.ts +6 -117
  274. package/src/runtime/routes/app-routes.ts +13 -15
  275. package/src/runtime/routes/attachment-routes.ts +26 -15
  276. package/src/runtime/routes/avatar-routes.ts +26 -0
  277. package/src/runtime/routes/btw-routes.ts +29 -23
  278. package/src/runtime/routes/consolidation-routes.ts +120 -20
  279. package/src/runtime/routes/conversation-query-routes.ts +2 -0
  280. package/src/runtime/routes/conversation-routes.ts +358 -184
  281. package/src/runtime/routes/documents-routes.ts +4 -0
  282. package/src/runtime/routes/domain-routes.ts +51 -37
  283. package/src/runtime/routes/epoch-millis-range.ts +34 -0
  284. package/src/runtime/routes/events-routes.ts +28 -34
  285. package/src/runtime/routes/gateway-log-routes.ts +26 -4
  286. package/src/runtime/routes/heartbeat-routes.ts +32 -12
  287. package/src/runtime/routes/identity-intro-cache.ts +11 -34
  288. package/src/runtime/routes/identity-routes.ts +208 -17
  289. package/src/runtime/routes/image-generation-routes.ts +40 -2
  290. package/src/runtime/routes/index.ts +2 -0
  291. package/src/runtime/routes/integrations/a2a.ts +12 -10
  292. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
  293. package/src/runtime/routes/integrations/slack/channel.ts +4 -0
  294. package/src/runtime/routes/integrations/slack/share.ts +27 -6
  295. package/src/runtime/routes/integrations/telegram.ts +6 -0
  296. package/src/runtime/routes/integrations/twilio.ts +42 -0
  297. package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
  298. package/src/runtime/routes/log-export-routes.ts +8 -0
  299. package/src/runtime/routes/memory-v2-routes.ts +15 -8
  300. package/src/runtime/routes/memory-v3-routes.ts +50 -28
  301. package/src/runtime/routes/oauth-apps.ts +66 -12
  302. package/src/runtime/routes/oauth-providers.ts +44 -5
  303. package/src/runtime/routes/platform-routes.ts +81 -5
  304. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
  305. package/src/runtime/routes/playground/force-compact.ts +1 -1
  306. package/src/runtime/routes/rename-conversation-routes.ts +5 -0
  307. package/src/runtime/routes/schedule-routes.ts +152 -42
  308. package/src/runtime/routes/secret-routes.ts +14 -2
  309. package/src/runtime/routes/skills-routes.ts +43 -14
  310. package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
  311. package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
  312. package/src/runtime/routes/trust-rules-routes.ts +26 -2
  313. package/src/runtime/routes/tts-routes.ts +35 -0
  314. package/src/runtime/routes/types.ts +66 -8
  315. package/src/runtime/routes/usage-routes.ts +47 -39
  316. package/src/runtime/routes/webhook-routes.ts +41 -2
  317. package/src/runtime/routes/workspace-routes.ts +4 -0
  318. package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
  319. package/src/runtime/services/analyze-conversation.ts +2 -2
  320. package/src/schedule/schedule-store.ts +20 -1
  321. package/src/schedule/schedule-usage-store.ts +83 -0
  322. package/src/schedule/scheduler.ts +12 -5
  323. package/src/skills/catalog-files.ts +2 -2
  324. package/src/skills/catalog-install.ts +3 -0
  325. package/src/skills/categories-cache.ts +118 -0
  326. package/src/skills/clawhub-files.ts +1 -2
  327. package/src/skills/skillssh-files.ts +1 -2
  328. package/src/telemetry/types.ts +29 -1
  329. package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
  330. package/src/telemetry/usage-telemetry-reporter.ts +57 -2
  331. package/src/tools/executor.ts +1 -53
  332. package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
  333. package/src/tools/network/__tests__/web-search.test.ts +11 -3
  334. package/src/tools/network/web-search-error.test.ts +248 -0
  335. package/src/tools/network/web-search-error.ts +267 -0
  336. package/src/tools/network/web-search.ts +207 -48
  337. package/src/tools/schedule/create.ts +2 -0
  338. package/src/tools/terminal/safe-env.ts +10 -1
  339. package/src/tools/ui-surface/definitions.ts +9 -1
  340. package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
  341. package/src/tts/provider-catalog.ts +76 -1
  342. package/src/util/mutex.ts +47 -0
  343. package/src/workspace/git-service.ts +1 -42
  344. package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
  345. package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
  346. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +93 -0
  347. package/src/workspace/migrations/registry.ts +6 -0
  348. package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
  349. package/src/__tests__/empty-response-pipeline.test.ts +0 -423
  350. package/src/__tests__/llm-call-pipeline.test.ts +0 -287
  351. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
  352. package/src/__tests__/persistence-pipeline.test.ts +0 -503
  353. package/src/__tests__/title-generate-pipeline.test.ts +0 -211
  354. package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
  355. package/src/__tests__/tool-error-pipeline.test.ts +0 -241
  356. package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
  357. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
  358. package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
  359. package/src/gallery/default-gallery.ts +0 -1359
  360. package/src/gallery/gallery-manifest.ts +0 -28
  361. package/src/home/feature-gate.ts +0 -22
  362. package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
  363. package/src/plugins/defaults/empty-response/terminal.ts +0 -106
  364. package/src/plugins/defaults/injectors/package.json +0 -15
  365. package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
  366. package/src/plugins/defaults/llm-call/package.json +0 -15
  367. package/src/plugins/defaults/llm-call/register.ts +0 -45
  368. package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
  369. package/src/plugins/defaults/memory-retrieval/package.json +0 -15
  370. package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
  371. package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
  372. package/src/plugins/defaults/persistence/package.json +0 -15
  373. package/src/plugins/defaults/persistence/register.ts +0 -38
  374. package/src/plugins/defaults/persistence/terminal.ts +0 -83
  375. package/src/plugins/defaults/title-generate/terminal.ts +0 -31
  376. package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
  377. package/src/plugins/defaults/token-estimate/package.json +0 -15
  378. package/src/plugins/defaults/token-estimate/register.ts +0 -34
  379. package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
  380. package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
  381. package/src/plugins/defaults/tool-error/terminal.ts +0 -47
  382. package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
  383. package/src/plugins/defaults/tool-execute/package.json +0 -15
  384. package/src/plugins/defaults/tool-execute/register.ts +0 -49
  385. package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
  386. package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
  387. package/src/skills/category-inference.ts +0 -111
@@ -69,6 +69,15 @@ export const KATA_SAFE_ENV_VARS = [
69
69
  export const KATA_INJECTED_ENV_VARS = ["LD_LIBRARY_PATH"] as const;
70
70
 
71
71
  const KATA_APT_DATA_ROOT = "/data/system";
72
+ const KATA_FAMILY_SANDBOX_RUNTIMES = new Set([
73
+ "kata",
74
+ "firecracker",
75
+ "cloud-hypervisor",
76
+ ]);
77
+
78
+ function isKataFamilyRuntime(runtime: string | undefined): boolean {
79
+ return runtime != null && KATA_FAMILY_SANDBOX_RUNTIMES.has(runtime);
80
+ }
72
81
 
73
82
  function kataAptPaths(dataRoot: string): string[] {
74
83
  return [
@@ -118,7 +127,7 @@ function appendUniquePathEntries(
118
127
 
119
128
  export function buildSanitizedEnv(): Record<string, string> {
120
129
  const env: Record<string, string> = {};
121
- const isKataRuntime = process.env.VELLUM_SANDBOX_RUNTIME === "kata";
130
+ const isKataRuntime = isKataFamilyRuntime(process.env.VELLUM_SANDBOX_RUNTIME);
122
131
  const safeEnvVars = isKataRuntime
123
132
  ? [...SAFE_ENV_VARS, ...KATA_SAFE_ENV_VARS]
124
133
  : SAFE_ENV_VARS;
@@ -105,13 +105,17 @@ export const uiShowTool = {
105
105
  'Surface structured data or UI in the conversation. For long-form writing use the document skill. For interactive apps, dashboards, games, calculators, or durable tools, call `skill_load` with `skill: "app-builder"` and use the app-builder workflow; do not use `dynamic_page` as a substitute for a persistent app. App-like `dynamic_page` calls are rejected.\n\n' +
106
106
  "Surface types (data shapes):\n" +
107
107
  '- card: { title, subtitle?, body, metadata?: [{ label, value }], template?, templateData? }. Templates: "weather_forecast" (native weather widget), "task_progress" (live step tracker - update via ui_update on data.templateData; shape: { title, status: "in_progress"|"completed"|"failed", steps: [{ label, status: "pending"|"in_progress"|"completed"|"failed", detail? }] })\n' +
108
+ "- copy_block: { text, label?, language? }. Shows copyable text with a visible copy button; use for prompts, commands, paths, or snippets the user should copy.\n" +
109
+ '- choice: { description?, options: [{ id, title, description?, recommended?, data? }], selectionMode?: "single"|"multiple", commitOnSelect?, submitLabel? }. Single-select choices commit on option click by default. Use for outcome offers and follow-up choices; mark the strongest option with recommended: true.\n' +
110
+ "- oauth_connect: { providerKey, displayName?, description?, logoUrl? }. Shows a managed OAuth connection CTA in chat; use when the current task needs a managed integration account (Google, Linear, GitHub, etc.) instead of asking the user to visit settings or attempting OAuth through shell/tools. The client supplies the CTA label. Do not include OAuth scopes in the surface; managed providers use the platform's configured scopes.\n" +
108
111
  '- table: { columns: [{ id, label }], rows: [{ id, cells: Record<id, string | { text, icon?, iconColor?: "success"|"warning"|"error"|"muted" }>, selectable?, selected? }], selectionMode?: "none"|"single"|"multiple", caption? }\n' +
109
112
  '- form: { description?, fields: [{ id, type: "text"|"textarea"|"select"|"toggle"|"number"|"password", label, placeholder?, required?, defaultValue?, options?: [{ label, value }] }], submitLabel? }. Multi-page: { pages: [{ id, title, description?, fields }], pageLabels?: { next?, back?, submit? }, submitLabel? }\n' +
110
113
  '- list: { items: [{ id, title, subtitle?, icon?, selected? }], selectionMode: "single"|"multiple"|"none" }\n' +
111
114
  "- confirmation: { message, detail?, confirmLabel?, confirmedLabel?, cancelLabel?, destructive? }\n" +
112
115
  "- dynamic_page: { html, width?, height?, preview?: { title, subtitle?, description?, icon?, metrics?: [{ label, value }] } }\n" +
113
116
  "- file_upload: { prompt, acceptedTypes?, maxFiles? }\n" +
114
- "- task_preferences: {} (no data needed — categories are rendered client-side)\n\n" +
117
+ "- task_preferences: {} (no data needed — categories are rendered client-side)\n" +
118
+ '- work_result: { eyebrow?, status?: "completed"|"partial"|"failed"|"in_progress", summary?, metrics?: [{ label, value, detail?, tone?: "neutral"|"positive"|"warning"|"negative" }], sections?: [{ id?, title, description?, type?: "items"|"timeline"|"diff"|"artifacts"|"warnings", items?: [{ id?, title, description?, status?, tone?, metadata?: [{ label, value }], href? }], diffs?: [{ label?, before?, after? }] }] }. Shows a structured receipt after real work: what changed, what was skipped, proof points, and next actions. Keep display-only unless explicit follow-up buttons are needed.\n\n' +
115
119
  "Proactively show a task_progress card before multi-step or long-running work (web searches, file operations, research). Show it before your first tool call, then update steps as work progresses.",
116
120
  category: "ui-surface",
117
121
  defaultRiskLevel: RiskLevel.Low,
@@ -124,6 +128,9 @@ export const uiShowTool = {
124
128
  type: "string",
125
129
  enum: [
126
130
  "card",
131
+ "choice",
132
+ "copy_block",
133
+ "oauth_connect",
127
134
  "form",
128
135
  "list",
129
136
  "table",
@@ -131,6 +138,7 @@ export const uiShowTool = {
131
138
  "dynamic_page",
132
139
  "file_upload",
133
140
  "task_preferences",
141
+ "work_result",
134
142
  ],
135
143
  description: "The type of surface to display",
136
144
  },
@@ -30,8 +30,10 @@ import {
30
30
  interface ClientCatalogProvider {
31
31
  id: string;
32
32
  displayName: string;
33
+ subtitle?: string;
34
+ supportsVoiceSelection?: boolean;
33
35
  credentialMode?: string;
34
- credentialsGuide?: { url: string };
36
+ credentialsGuide?: { description: string; url: string; linkLabel: string };
35
37
  }
36
38
 
37
39
  interface ClientCatalog {
@@ -144,6 +146,88 @@ describe("TTS provider catalog / client artifact consistency", () => {
144
146
  }
145
147
  });
146
148
 
149
+ // -- Display field parity --------------------------------------------------
150
+
151
+ test("subtitle matches between assistant catalog and client artifact", () => {
152
+ const violations: string[] = [];
153
+ for (const clientEntry of clientCatalog.providers) {
154
+ try {
155
+ const assistantEntry = getCatalogProvider(clientEntry.id as any);
156
+ if (clientEntry.subtitle !== assistantEntry.subtitle) {
157
+ violations.push(
158
+ `"${clientEntry.id}": client="${clientEntry.subtitle}" vs assistant="${assistantEntry.subtitle}"`,
159
+ );
160
+ }
161
+ } catch {
162
+ // Unknown ID — covered by provider ID parity tests above.
163
+ }
164
+ }
165
+ if (violations.length > 0) {
166
+ expect(violations, "Subtitle mismatch:\n" + violations.join("\n")).toEqual(
167
+ [],
168
+ );
169
+ }
170
+ });
171
+
172
+ test("supportsVoiceSelection matches between assistant catalog and client artifact", () => {
173
+ const violations: string[] = [];
174
+ for (const clientEntry of clientCatalog.providers) {
175
+ try {
176
+ const assistantEntry = getCatalogProvider(clientEntry.id as any);
177
+ if (
178
+ clientEntry.supportsVoiceSelection !==
179
+ assistantEntry.supportsVoiceSelection
180
+ ) {
181
+ violations.push(
182
+ `"${clientEntry.id}": client=${clientEntry.supportsVoiceSelection} vs assistant=${assistantEntry.supportsVoiceSelection}`,
183
+ );
184
+ }
185
+ } catch {
186
+ // Unknown ID — covered by provider ID parity tests above.
187
+ }
188
+ }
189
+ if (violations.length > 0) {
190
+ expect(
191
+ violations,
192
+ "supportsVoiceSelection mismatch:\n" + violations.join("\n"),
193
+ ).toEqual([]);
194
+ }
195
+ });
196
+
197
+ test("credentialsGuide matches between assistant catalog and client artifact", () => {
198
+ const violations: string[] = [];
199
+ for (const clientEntry of clientCatalog.providers) {
200
+ try {
201
+ const assistantEntry = getCatalogProvider(clientEntry.id as any);
202
+ const cg = clientEntry.credentialsGuide;
203
+ const ag = assistantEntry.credentialsGuide;
204
+ if (cg && ag) {
205
+ if (cg.url !== ag.url) {
206
+ violations.push(`"${clientEntry.id}": credentialsGuide.url mismatch`);
207
+ }
208
+ if (cg.description !== ag.description) {
209
+ violations.push(
210
+ `"${clientEntry.id}": credentialsGuide.description mismatch`,
211
+ );
212
+ }
213
+ if (cg.linkLabel !== ag.linkLabel) {
214
+ violations.push(
215
+ `"${clientEntry.id}": credentialsGuide.linkLabel mismatch`,
216
+ );
217
+ }
218
+ }
219
+ } catch {
220
+ // Unknown ID — covered by provider ID parity tests above.
221
+ }
222
+ }
223
+ if (violations.length > 0) {
224
+ expect(
225
+ violations,
226
+ "credentialsGuide mismatch:\n" + violations.join("\n"),
227
+ ).toEqual([]);
228
+ }
229
+ });
230
+
147
231
  // -- Structural sanity ----------------------------------------------------
148
232
 
149
233
  test("client artifact version is a positive integer", () => {
@@ -55,12 +55,21 @@ interface TtsProviderCatalogCapabilities {
55
55
  readonly supportedFormats: readonly string[];
56
56
  }
57
57
 
58
+ /**
59
+ * Link to a provider's API-key management page, shown in settings UI.
60
+ */
61
+ interface TtsCredentialsGuide {
62
+ readonly description: string;
63
+ readonly url: string;
64
+ readonly linkLabel: string;
65
+ }
66
+
58
67
  /**
59
68
  * A single entry in the TTS provider catalog.
60
69
  *
61
70
  * Captures everything the system needs to know about a provider at a
62
71
  * metadata level — identity, display name, telephony call mode,
63
- * capabilities, and secret requirements.
72
+ * capabilities, secret requirements, and client-facing display metadata.
64
73
  */
65
74
  interface TtsProviderCatalogEntry {
66
75
  /** Unique provider identifier matching {@link TtsProviderId}. */
@@ -69,6 +78,18 @@ interface TtsProviderCatalogEntry {
69
78
  /** Human-readable name for display in settings UI and logs. */
70
79
  readonly displayName: string;
71
80
 
81
+ /** Short description shown beneath the provider name in settings UI. */
82
+ readonly subtitle: string;
83
+
84
+ /** Whether the provider supports user-chosen voice IDs. */
85
+ readonly supportsVoiceSelection: boolean;
86
+
87
+ /** Placeholder text for the API-key input in settings UI. */
88
+ readonly apiKeyPlaceholder: string;
89
+
90
+ /** Link to the provider's API-key management page. */
91
+ readonly credentialsGuide: TtsCredentialsGuide;
92
+
72
93
  /** How this provider integrates with the telephony call path. */
73
94
  readonly callMode: TtsCallMode;
74
95
 
@@ -106,6 +127,16 @@ const CATALOG: readonly TtsProviderCatalogEntry[] = [
106
127
  {
107
128
  id: "elevenlabs",
108
129
  displayName: "ElevenLabs",
130
+ subtitle:
131
+ "High-quality voice synthesis for conversations and read-aloud. Requires an ElevenLabs API key.",
132
+ supportsVoiceSelection: true,
133
+ apiKeyPlaceholder: "sk_…",
134
+ credentialsGuide: {
135
+ description:
136
+ "Sign in to ElevenLabs, go to your Profile, and copy your API key.",
137
+ url: "https://elevenlabs.io/app/settings/api-keys",
138
+ linkLabel: "Open ElevenLabs API Keys",
139
+ },
109
140
  callMode: "native-twilio",
110
141
  allowNativeFallback: true,
111
142
  capabilities: {
@@ -124,6 +155,16 @@ const CATALOG: readonly TtsProviderCatalogEntry[] = [
124
155
  {
125
156
  id: "fish-audio",
126
157
  displayName: "Fish Audio",
158
+ subtitle:
159
+ "Natural-sounding voice synthesis with custom voice cloning. Requires a Fish Audio API key and voice reference ID.",
160
+ supportsVoiceSelection: true,
161
+ apiKeyPlaceholder: "Enter your Fish Audio API key",
162
+ credentialsGuide: {
163
+ description:
164
+ "Sign in to Fish Audio, navigate to API Keys in your dashboard, and create a new key.",
165
+ url: "https://fish.audio/app/api-keys/",
166
+ linkLabel: "Open Fish Audio API Keys",
167
+ },
127
168
  callMode: "synthesized-play",
128
169
  allowNativeFallback: true,
129
170
  capabilities: {
@@ -142,6 +183,16 @@ const CATALOG: readonly TtsProviderCatalogEntry[] = [
142
183
  {
143
184
  id: "deepgram",
144
185
  displayName: "Deepgram",
186
+ subtitle:
187
+ "Fast, accurate text-to-speech synthesis. Uses the same API key as Deepgram speech-to-text.",
188
+ supportsVoiceSelection: false,
189
+ apiKeyPlaceholder: "Enter your Deepgram API key",
190
+ credentialsGuide: {
191
+ description:
192
+ "Sign in to Deepgram, navigate to your API Keys page, and create or copy an existing key. This is the same key used for speech-to-text.",
193
+ url: "https://console.deepgram.com/",
194
+ linkLabel: "Open Deepgram Console",
195
+ },
145
196
  callMode: "synthesized-play",
146
197
  allowNativeFallback: false,
147
198
  capabilities: {
@@ -159,6 +210,16 @@ const CATALOG: readonly TtsProviderCatalogEntry[] = [
159
210
  {
160
211
  id: "xai",
161
212
  displayName: "xAI",
213
+ subtitle:
214
+ "Text-to-speech from xAI with expressive voices (eve, ara, rex, sal, leo). Requires an xAI API key.",
215
+ supportsVoiceSelection: false,
216
+ apiKeyPlaceholder: "Enter your xAI API key",
217
+ credentialsGuide: {
218
+ description:
219
+ "Sign in to the xAI console, navigate to API Keys, and create a new key.",
220
+ url: "https://console.x.ai/",
221
+ linkLabel: "Open xAI Console",
222
+ },
162
223
  callMode: "synthesized-play",
163
224
  allowNativeFallback: false,
164
225
  capabilities: {
@@ -199,6 +260,20 @@ export function listCatalogProviderIds(): TtsProviderId[] {
199
260
  return CATALOG.map((entry) => entry.id);
200
261
  }
201
262
 
263
+ /**
264
+ * List all catalog providers projected to client-facing display fields only.
265
+ */
266
+ export function listCatalogProvidersForDisplay() {
267
+ return CATALOG.map((e) => ({
268
+ id: e.id,
269
+ displayName: e.displayName,
270
+ subtitle: e.subtitle,
271
+ supportsVoiceSelection: e.supportsVoiceSelection,
272
+ apiKeyPlaceholder: e.apiKeyPlaceholder,
273
+ credentialsGuide: e.credentialsGuide,
274
+ }));
275
+ }
276
+
202
277
  /**
203
278
  * Look up a catalog entry by provider ID.
204
279
  *
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Async mutex for serializing access to a shared resource.
3
+ *
4
+ * Callers wait in FIFO order. `withLock(fn)` is the primary API —
5
+ * it acquires the lock, runs `fn`, and releases the lock when `fn`
6
+ * settles (even on throw).
7
+ *
8
+ * Used by git-service (per-workspace repo operations) and
9
+ * conversation-title-service (serial LLM calls) to prevent
10
+ * concurrent access to resources that cannot safely overlap.
11
+ */
12
+ export class Mutex {
13
+ private locked = false;
14
+ private waitQueue: Array<() => void> = [];
15
+
16
+ async acquire(): Promise<void> {
17
+ if (!this.locked) {
18
+ this.locked = true;
19
+ return;
20
+ }
21
+ await new Promise<void>((resolve) => {
22
+ this.waitQueue.push(resolve);
23
+ });
24
+ }
25
+
26
+ release(): void {
27
+ const next = this.waitQueue.shift();
28
+ if (next) {
29
+ next();
30
+ } else {
31
+ this.locked = false;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Execute `fn` while holding the lock.
37
+ * Automatically releases the lock when done, even if `fn` throws.
38
+ */
39
+ async withLock<T>(fn: () => Promise<T>): Promise<T> {
40
+ await this.acquire();
41
+ try {
42
+ return await fn();
43
+ } finally {
44
+ this.release();
45
+ }
46
+ }
47
+ }
@@ -12,6 +12,7 @@ import { promisify } from "node:util";
12
12
 
13
13
  import { getConfig } from "../config/loader.js";
14
14
  import { getLogger } from "../util/logger.js";
15
+ import { Mutex } from "../util/mutex.js";
15
16
  import { PromiseGuard } from "../util/promise-guard.js";
16
17
 
17
18
  const execFileAsync = promisify(execFile);
@@ -119,48 +120,6 @@ interface ExecError extends Error {
119
120
  code?: string | number;
120
121
  }
121
122
 
122
- /**
123
- * Simple mutex implementation for per-workspace git operation serialization.
124
- * Prevents concurrent git operations from corrupting the repository state.
125
- */
126
- class Mutex {
127
- private locked = false;
128
- private waitQueue: Array<() => void> = [];
129
-
130
- async acquire(): Promise<void> {
131
- if (!this.locked) {
132
- this.locked = true;
133
- return;
134
- }
135
- // Wait for the lock to be released
136
- await new Promise<void>((resolve) => {
137
- this.waitQueue.push(resolve);
138
- });
139
- }
140
-
141
- release(): void {
142
- const next = this.waitQueue.shift();
143
- if (next) {
144
- next();
145
- } else {
146
- this.locked = false;
147
- }
148
- }
149
-
150
- /**
151
- * Execute a function while holding the lock.
152
- * Automatically releases the lock when done, even if the function throws.
153
- */
154
- async withLock<T>(fn: () => Promise<T>): Promise<T> {
155
- await this.acquire();
156
- try {
157
- return await fn();
158
- } finally {
159
- this.release();
160
- }
161
- }
162
- }
163
-
164
123
  interface GitCommitMetadata {
165
124
  /** Optional metadata to include in the commit message or as git notes */
166
125
  [key: string]: unknown;
@@ -0,0 +1,51 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ const THIRTY_MINUTES_MS = 30 * 60 * 1000;
7
+ const SIXTY_MINUTES_MS = 60 * 60 * 1000;
8
+
9
+ /**
10
+ * Bump persisted heartbeat default from 30 minutes to 60 minutes.
11
+ *
12
+ * Migration 065 moved legacy 3h/6h defaults to 30 minutes. The schema default
13
+ * has since been raised to 60 minutes, but existing users whose config.json
14
+ * already has 1800000 persisted won't pick up the new default. This migration
15
+ * idempotently updates those configs.
16
+ */
17
+ export const bumpHeartbeatInterval30mTo60mMigration: WorkspaceMigration = {
18
+ id: "095-bump-heartbeat-interval-30m-to-60m",
19
+ description:
20
+ "Bump persisted heartbeat.intervalMs from 30 minutes to 60 minutes",
21
+ run(workspaceDir: string): void {
22
+ const configPath = join(workspaceDir, "config.json");
23
+ if (!existsSync(configPath)) return;
24
+
25
+ let config: Record<string, unknown>;
26
+ try {
27
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
28
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
29
+ config = raw as Record<string, unknown>;
30
+ } catch {
31
+ return;
32
+ }
33
+
34
+ const heartbeat = config.heartbeat;
35
+ if (!heartbeat || typeof heartbeat !== "object" || Array.isArray(heartbeat))
36
+ return;
37
+
38
+ const heartbeatConfig = heartbeat as Record<string, unknown>;
39
+ const intervalMs = heartbeatConfig.intervalMs;
40
+ if (typeof intervalMs !== "number" || intervalMs !== THIRTY_MINUTES_MS) {
41
+ return;
42
+ }
43
+
44
+ heartbeatConfig.intervalMs = SIXTY_MINUTES_MS;
45
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
46
+ },
47
+ down(_workspaceDir: string): void {
48
+ // Forward-only: cannot distinguish users who explicitly chose 60 minutes
49
+ // from those migrated by this migration.
50
+ },
51
+ };
@@ -0,0 +1,72 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ // Reduce effort from "max" to "high" on the quality-optimized profile.
7
+ //
8
+ // With adaptive thinking enabled, the effort parameter acts as a nudge for
9
+ // how much thinking the model does. "max" means "always think with no
10
+ // constraints on thinking depth", which defeats adaptive thinking's ability
11
+ // to skip or minimize thinking for simple queries. "high" (the API default)
12
+ // means "almost always thinks, deep reasoning on complex tasks" — letting
13
+ // adaptive thinking decide when full-depth reasoning is actually needed.
14
+ //
15
+ // This migration patches both managed and user quality-optimized profiles
16
+ // that still have effort: "max". It only downgrades "max" → "high"; any
17
+ // other effort value is preserved.
18
+
19
+ const TARGET_PROFILES = ["quality-optimized", "custom-quality-optimized"];
20
+
21
+ export const reduceQualityProfileEffortMigration: WorkspaceMigration = {
22
+ id: "096-reduce-quality-profile-effort",
23
+ description:
24
+ 'Reduce effort from "max" to "high" on quality-optimized profiles to let adaptive thinking work',
25
+ run(workspaceDir: string): void {
26
+ const configPath = join(workspaceDir, "config.json");
27
+ if (!existsSync(configPath)) return;
28
+
29
+ let config: Record<string, unknown>;
30
+ try {
31
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
32
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
33
+ config = raw as Record<string, unknown>;
34
+ } catch {
35
+ return;
36
+ }
37
+
38
+ const llm = readObject(config.llm);
39
+ if (llm === null) return;
40
+
41
+ const profiles = readObject(llm.profiles);
42
+ if (profiles === null) return;
43
+
44
+ let changed = false;
45
+
46
+ for (const name of TARGET_PROFILES) {
47
+ const profile = readObject(profiles[name]);
48
+ if (profile === null) continue;
49
+ if (profile.effort !== "max") continue;
50
+
51
+ profile.effort = "high";
52
+ profiles[name] = profile;
53
+ changed = true;
54
+ }
55
+
56
+ if (changed) {
57
+ llm.profiles = profiles;
58
+ config.llm = llm;
59
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
60
+ }
61
+ },
62
+ down(_workspaceDir: string): void {
63
+ // Forward-only.
64
+ },
65
+ };
66
+
67
+ function readObject(value: unknown): Record<string, unknown> | null {
68
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
69
+ return null;
70
+ }
71
+ return value as Record<string, unknown>;
72
+ }
@@ -0,0 +1,93 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ // Enable adaptive thinking on the managed "balanced" and "quality-optimized"
7
+ // profiles for existing platform instances.
8
+ //
9
+ // The assistant-side seed defaults (MANAGED_PROFILE_TEMPLATES in
10
+ // seed-inference-profiles.ts) already ship thinking: { enabled: true,
11
+ // streamThinking: true } for both profiles, which normalizes to
12
+ // { type: "adaptive" } on the wire. Off-platform (BYOK) instances pick this
13
+ // up on every boot because the seeder overwrites managed profiles. On-platform
14
+ // instances preserve existing profiles (the platform overlay is authoritative),
15
+ // so instances that were hatched before thinking was enabled in the templates
16
+ // are stuck with thinking disabled or absent.
17
+ //
18
+ // This migration patches the on-disk config for both profiles, adding
19
+ // thinking: { enabled: true, streamThinking: true } where it's missing or
20
+ // explicitly disabled. It skips profiles that:
21
+ // - Don't exist (no profile to patch)
22
+ // - Already have thinking enabled (idempotent)
23
+ // - Are not source: "managed" (user-created profiles are untouched)
24
+ // - Use a non-Anthropic provider (adaptive thinking is Anthropic-specific)
25
+
26
+ const ADAPTIVE_THINKING = { enabled: true, streamThinking: true } as const;
27
+ const TARGET_PROFILES = ["balanced", "quality-optimized"] as const;
28
+
29
+ export const enableAdaptiveThinkingManagedProfilesMigration: WorkspaceMigration =
30
+ {
31
+ id: "097-enable-adaptive-thinking-managed-profiles",
32
+ description:
33
+ "Enable adaptive thinking on managed balanced and quality-optimized profiles",
34
+ run(workspaceDir: string): void {
35
+ const configPath = join(workspaceDir, "config.json");
36
+ if (!existsSync(configPath)) return;
37
+
38
+ let config: Record<string, unknown>;
39
+ try {
40
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
41
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
42
+ config = raw as Record<string, unknown>;
43
+ } catch {
44
+ return;
45
+ }
46
+
47
+ const llm = readObject(config.llm);
48
+ if (llm === null) return;
49
+
50
+ const profiles = readObject(llm.profiles);
51
+ if (profiles === null) return;
52
+
53
+ let changed = false;
54
+
55
+ for (const name of TARGET_PROFILES) {
56
+ const profile = readObject(profiles[name]);
57
+ if (profile === null) continue;
58
+
59
+ // Only patch managed Anthropic profiles.
60
+ if (profile.source !== "managed") continue;
61
+ if (
62
+ typeof profile.provider === "string" &&
63
+ profile.provider !== "anthropic"
64
+ ) {
65
+ continue;
66
+ }
67
+
68
+ // Skip if thinking is already enabled.
69
+ const thinking = readObject(profile.thinking);
70
+ if (thinking !== null && thinking.enabled === true) continue;
71
+
72
+ profile.thinking = { ...ADAPTIVE_THINKING };
73
+ profiles[name] = profile;
74
+ changed = true;
75
+ }
76
+
77
+ if (changed) {
78
+ llm.profiles = profiles;
79
+ config.llm = llm;
80
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
81
+ }
82
+ },
83
+ down(_workspaceDir: string): void {
84
+ // Forward-only.
85
+ },
86
+ };
87
+
88
+ function readObject(value: unknown): Record<string, unknown> | null {
89
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
90
+ return null;
91
+ }
92
+ return value as Record<string, unknown>;
93
+ }
@@ -92,6 +92,9 @@ import { retightenMigrationOnboardingThreadMigration } from "./091-retighten-mig
92
92
  import { backfillV3LeavesMigration } from "./092-backfill-v3-leaves.js";
93
93
  import { backfillLeafIdsMigration } from "./093-backfill-leaf-ids.js";
94
94
  import { seedAvatarManifestMigration } from "./094-seed-avatar-manifest.js";
95
+ import { bumpHeartbeatInterval30mTo60mMigration } from "./095-bump-heartbeat-interval-30m-to-60m.js";
96
+ import { reduceQualityProfileEffortMigration } from "./096-reduce-quality-profile-effort.js";
97
+ import { enableAdaptiveThinkingManagedProfilesMigration } from "./097-enable-adaptive-thinking-managed-profiles.js";
95
98
  import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
96
99
  import type { WorkspaceMigration } from "./types.js";
97
100
 
@@ -195,4 +198,7 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
195
198
  backfillV3LeavesMigration,
196
199
  backfillLeafIdsMigration,
197
200
  seedAvatarManifestMigration,
201
+ bumpHeartbeatInterval30mTo60mMigration,
202
+ reduceQualityProfileEffortMigration,
203
+ enableAdaptiveThinkingManagedProfilesMigration,
198
204
  ];
@@ -1,44 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import {
4
- BOOTSTRAP_CLEANUP_USER_TURN_THRESHOLD,
5
- countBootstrapUserTurns,
6
- shouldCleanupBootstrapAfterTurn,
7
- } from "../daemon/bootstrap-turn-cleanup.js";
8
-
9
- function message(role: string, content: string) {
10
- return { role, content };
11
- }
12
-
13
- describe("bootstrap turn cleanup", () => {
14
- test("does not count the hidden wake-up greeting as a user turn", () => {
15
- const messages = [
16
- message(
17
- "user",
18
- JSON.stringify([{ type: "text", text: "Wake up, my friend." }]),
19
- ),
20
- message("assistant", "hello"),
21
- message("user", "real request"),
22
- ];
23
-
24
- expect(countBootstrapUserTurns(messages)).toBe(1);
25
- });
26
-
27
- test("cleans up after the configured user-turn threshold", () => {
28
- const messages = Array.from(
29
- { length: BOOTSTRAP_CLEANUP_USER_TURN_THRESHOLD },
30
- (_value, index) => message("user", `request ${index + 1}`),
31
- );
32
-
33
- expect(shouldCleanupBootstrapAfterTurn(messages)).toBe(true);
34
- });
35
-
36
- test("keeps bootstrap before the configured user-turn threshold", () => {
37
- const messages = Array.from(
38
- { length: BOOTSTRAP_CLEANUP_USER_TURN_THRESHOLD - 1 },
39
- (_value, index) => message("user", `request ${index + 1}`),
40
- );
41
-
42
- expect(shouldCleanupBootstrapAfterTurn(messages)).toBe(false);
43
- });
44
- });