@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
@@ -0,0 +1,490 @@
1
+ # Example — Habit Tracker
2
+
3
+ A 7-day habit grid: add habits, toggle a checkmark per day, delete habits. This is the
4
+ canonical **CRUD over a custom route** example — full create / read / update / delete
5
+ backed by a JSON file on disk, called from a multi-file TSX (`formatVersion: 2`) app via
6
+ `window.vellum.fetch`.
7
+
8
+ **What it demonstrates**
9
+
10
+ - A `routes/habits.ts` handler exporting `GET` / `POST` / `PATCH` / `DELETE`.
11
+ - Mutations addressed by `id` via a **query param** (`/v1/x/habits?id=…`) — route files
12
+ cannot use `[id].ts` path segments (see [CUSTOM_ROUTES.md](../CUSTOM_ROUTES.md)).
13
+ - A frontend that always checks `res.ok`, surfaces errors, and re-reads after every write
14
+ so the UI reflects persisted state rather than optimistic guesses.
15
+
16
+ ## File tree
17
+
18
+ ```
19
+ src/index.html
20
+ src/main.tsx
21
+ src/components/HabitTracker.tsx
22
+ src/components/HabitRow.tsx
23
+ src/styles.css
24
+ routes/habits.ts
25
+ ```
26
+
27
+ ## Route handler
28
+
29
+ ```typescript
30
+ // routes/habits.ts — Habit CRUD, persisted as a JSON file in the app workspace.
31
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
32
+ import { join } from "node:path";
33
+
34
+ export const description = "Habit CRUD — stores habits as a JSON file";
35
+
36
+ interface Habit {
37
+ id: string;
38
+ name: string;
39
+ completedDates: string[];
40
+ createdAt: string;
41
+ }
42
+
43
+ const DATA_DIR = join(process.env.VELLUM_WORKSPACE_DIR!, "data");
44
+ const DATA_FILE = join(DATA_DIR, "habits.json");
45
+
46
+ function loadHabits(): Habit[] {
47
+ mkdirSync(DATA_DIR, { recursive: true });
48
+ if (!existsSync(DATA_FILE)) return [];
49
+ return JSON.parse(readFileSync(DATA_FILE, "utf-8")) as Habit[];
50
+ }
51
+
52
+ function saveHabits(habits: Habit[]): void {
53
+ mkdirSync(DATA_DIR, { recursive: true });
54
+ writeFileSync(DATA_FILE, JSON.stringify(habits, null, 2));
55
+ }
56
+
57
+ export function GET(): Response {
58
+ return Response.json(loadHabits());
59
+ }
60
+
61
+ export async function POST(request: Request): Promise<Response> {
62
+ const body = (await request.json()) as { name?: unknown };
63
+ const name = typeof body.name === "string" ? body.name.trim() : "";
64
+ if (!name)
65
+ return Response.json({ error: "name is required" }, { status: 400 });
66
+
67
+ const habit: Habit = {
68
+ id: crypto.randomUUID(),
69
+ name,
70
+ completedDates: [],
71
+ createdAt: new Date().toISOString(),
72
+ };
73
+ const habits = loadHabits();
74
+ habits.push(habit);
75
+ saveHabits(habits);
76
+ return Response.json(habit, { status: 201 });
77
+ }
78
+
79
+ export async function PATCH(request: Request): Promise<Response> {
80
+ const id = new URL(request.url).searchParams.get("id");
81
+ if (!id) return Response.json({ error: "id is required" }, { status: 400 });
82
+
83
+ const body = (await request.json()) as { completedDates?: unknown };
84
+ if (!Array.isArray(body.completedDates)) {
85
+ return Response.json(
86
+ { error: "completedDates must be an array" },
87
+ { status: 400 },
88
+ );
89
+ }
90
+
91
+ const habits = loadHabits();
92
+ const habit = habits.find((h) => h.id === id);
93
+ if (!habit) return Response.json({ error: "not found" }, { status: 404 });
94
+
95
+ habit.completedDates = body.completedDates.filter(
96
+ (d): d is string => typeof d === "string",
97
+ );
98
+ saveHabits(habits);
99
+ return Response.json(habit);
100
+ }
101
+
102
+ export function DELETE(request: Request): Response {
103
+ const id = new URL(request.url).searchParams.get("id");
104
+ if (!id) return Response.json({ error: "id is required" }, { status: 400 });
105
+
106
+ const habits = loadHabits();
107
+ const next = habits.filter((h) => h.id !== id);
108
+ if (next.length === habits.length) {
109
+ return Response.json({ error: "not found" }, { status: 404 });
110
+ }
111
+ saveHabits(next);
112
+ return Response.json({ ok: true });
113
+ }
114
+ ```
115
+
116
+ ## Frontend
117
+
118
+ ```html
119
+ <!-- src/index.html -->
120
+ <!DOCTYPE html>
121
+ <html lang="en">
122
+ <head>
123
+ <meta charset="UTF-8" />
124
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
125
+ <title>Habit Tracker</title>
126
+ </head>
127
+ <body>
128
+ <div id="app"></div>
129
+ </body>
130
+ </html>
131
+ ```
132
+
133
+ ```tsx
134
+ // src/main.tsx
135
+ import { render } from "preact";
136
+ import { HabitTracker } from "./components/HabitTracker.js";
137
+ import "./styles.css";
138
+
139
+ render(<HabitTracker />, document.getElementById("app")!);
140
+ ```
141
+
142
+ ```tsx
143
+ // src/components/HabitTracker.tsx
144
+ import { useCallback, useEffect, useState } from "preact/hooks";
145
+ import { HabitRow } from "./HabitRow.js";
146
+
147
+ interface Habit {
148
+ id: string;
149
+ name: string;
150
+ completedDates: string[];
151
+ }
152
+
153
+ function getDates(): string[] {
154
+ const dates: string[] = [];
155
+ for (let i = 6; i >= 0; i--) {
156
+ const d = new Date();
157
+ d.setDate(d.getDate() - i);
158
+ dates.push(d.toISOString().slice(0, 10));
159
+ }
160
+ return dates;
161
+ }
162
+
163
+ function getDayNames(dates: string[]): string[] {
164
+ const names = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
165
+ return dates.map((d) => names[new Date(d + "T12:00:00").getDay()]);
166
+ }
167
+
168
+ export function HabitTracker() {
169
+ const [habits, setHabits] = useState<Habit[]>([]);
170
+ const [input, setInput] = useState("");
171
+ const [error, setError] = useState<string | null>(null);
172
+ const dates = getDates();
173
+ const dayNames = getDayNames(dates);
174
+
175
+ const loadHabits = useCallback(async () => {
176
+ try {
177
+ const res = await window.vellum.fetch("/v1/x/habits");
178
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
179
+ setHabits(await res.json());
180
+ setError(null);
181
+ } catch (e) {
182
+ setError("Couldn't load habits. Try again.");
183
+ console.error(e);
184
+ }
185
+ }, []);
186
+
187
+ useEffect(() => {
188
+ loadHabits();
189
+ }, [loadHabits]);
190
+
191
+ const addHabit = async () => {
192
+ const name = input.trim();
193
+ if (!name) return;
194
+ setInput("");
195
+ try {
196
+ const res = await window.vellum.fetch("/v1/x/habits", {
197
+ method: "POST",
198
+ headers: { "Content-Type": "application/json" },
199
+ body: JSON.stringify({ name }),
200
+ });
201
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
202
+ await loadHabits();
203
+ } catch (e) {
204
+ setError("Couldn't add habit. Try again.");
205
+ console.error(e);
206
+ }
207
+ };
208
+
209
+ const toggleDate = async (id: string, date: string) => {
210
+ const habit = habits.find((h) => h.id === id);
211
+ if (!habit) return;
212
+ const completed = habit.completedDates.includes(date)
213
+ ? habit.completedDates.filter((d) => d !== date)
214
+ : [...habit.completedDates, date];
215
+ try {
216
+ const res = await window.vellum.fetch(`/v1/x/habits?id=${id}`, {
217
+ method: "PATCH",
218
+ headers: { "Content-Type": "application/json" },
219
+ body: JSON.stringify({ completedDates: completed }),
220
+ });
221
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
222
+ await loadHabits();
223
+ } catch (e) {
224
+ setError("Couldn't update habit. Try again.");
225
+ console.error(e);
226
+ }
227
+ };
228
+
229
+ const deleteHabit = async (id: string) => {
230
+ try {
231
+ const res = await window.vellum.fetch(`/v1/x/habits?id=${id}`, {
232
+ method: "DELETE",
233
+ });
234
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
235
+ await loadHabits();
236
+ } catch (e) {
237
+ setError("Couldn't delete habit. Try again.");
238
+ console.error(e);
239
+ }
240
+ };
241
+
242
+ return (
243
+ <div>
244
+ <div class="header">
245
+ <h1>Habit Tracker</h1>
246
+ </div>
247
+ {error && <div class="error-banner">{error}</div>}
248
+ <div class="add-form">
249
+ <input
250
+ type="text"
251
+ value={input}
252
+ onInput={(e) => setInput((e.target as HTMLInputElement).value)}
253
+ onKeyDown={(e) => e.key === "Enter" && addHabit()}
254
+ placeholder="Add a new habit..."
255
+ />
256
+ <button class="btn-primary" onClick={addHabit}>
257
+ Add
258
+ </button>
259
+ </div>
260
+ <div class="days-header">
261
+ <div />
262
+ {dayNames.map((name, i) => (
263
+ <div key={i} class="day-label">
264
+ {name}
265
+ </div>
266
+ ))}
267
+ </div>
268
+ <div>
269
+ {habits.length === 0 ? (
270
+ <div class="empty-state">No habits yet. Add one above!</div>
271
+ ) : (
272
+ habits.map((habit) => (
273
+ <HabitRow
274
+ key={habit.id}
275
+ habit={habit}
276
+ dates={dates}
277
+ onToggle={toggleDate}
278
+ onDelete={deleteHabit}
279
+ />
280
+ ))
281
+ )}
282
+ </div>
283
+ </div>
284
+ );
285
+ }
286
+ ```
287
+
288
+ ```tsx
289
+ // src/components/HabitRow.tsx
290
+ interface Habit {
291
+ id: string;
292
+ name: string;
293
+ completedDates: string[];
294
+ }
295
+
296
+ interface HabitRowProps {
297
+ habit: Habit;
298
+ dates: string[];
299
+ onToggle: (id: string, date: string) => void;
300
+ onDelete: (id: string) => void;
301
+ }
302
+
303
+ export function HabitRow({ habit, dates, onToggle, onDelete }: HabitRowProps) {
304
+ return (
305
+ <div class="habit-row">
306
+ <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
307
+ <span class="habit-name">{habit.name}</span>
308
+ <button class="delete-btn" onClick={() => onDelete(habit.id)}>
309
+ x
310
+ </button>
311
+ </div>
312
+ {dates.map((date) => {
313
+ const checked = habit.completedDates.includes(date);
314
+ return (
315
+ <div key={date} class="check-cell">
316
+ <button
317
+ class={`check-btn${checked ? " checked" : ""}`}
318
+ onClick={() => onToggle(habit.id, date)}
319
+ >
320
+ {checked ? "\u2713" : ""}
321
+ </button>
322
+ </div>
323
+ );
324
+ })}
325
+ </div>
326
+ );
327
+ }
328
+ ```
329
+
330
+ ```css
331
+ /* src/styles.css */
332
+ :root {
333
+ --bg: #0f172a;
334
+ --surface: #1e293b;
335
+ --primary: #6366f1;
336
+ --primary-hover: #5558e6;
337
+ --success: #22c55e;
338
+ --text: #f1f5f9;
339
+ --text-secondary: #94a3b8;
340
+ --border: #334155;
341
+ --radius: 10px;
342
+ }
343
+ * {
344
+ margin: 0;
345
+ padding: 0;
346
+ box-sizing: border-box;
347
+ }
348
+ body {
349
+ font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
350
+ background: var(--bg);
351
+ color: var(--text);
352
+ padding: 24px;
353
+ min-height: 100vh;
354
+ }
355
+ .header {
356
+ display: flex;
357
+ justify-content: space-between;
358
+ align-items: center;
359
+ margin-bottom: 24px;
360
+ }
361
+ h1 {
362
+ font-size: 1.4rem;
363
+ font-weight: 600;
364
+ }
365
+ .error-banner {
366
+ background: rgba(239, 68, 68, 0.12);
367
+ color: #fca5a5;
368
+ padding: 10px 14px;
369
+ border-radius: var(--radius);
370
+ margin-bottom: 16px;
371
+ font-size: 0.85rem;
372
+ }
373
+ .add-form {
374
+ display: flex;
375
+ gap: 8px;
376
+ margin-bottom: 24px;
377
+ }
378
+ .add-form input {
379
+ flex: 1;
380
+ padding: 10px 14px;
381
+ background: var(--surface);
382
+ border: 1px solid var(--border);
383
+ border-radius: var(--radius);
384
+ color: var(--text);
385
+ font-family: inherit;
386
+ font-size: 0.9rem;
387
+ outline: none;
388
+ }
389
+ .add-form input:focus {
390
+ border-color: var(--primary);
391
+ }
392
+ .add-form input::placeholder {
393
+ color: var(--text-secondary);
394
+ }
395
+ button {
396
+ font-family: inherit;
397
+ font-size: 0.85rem;
398
+ font-weight: 500;
399
+ padding: 10px 18px;
400
+ border: none;
401
+ border-radius: var(--radius);
402
+ cursor: pointer;
403
+ transition: background 0.2s;
404
+ }
405
+ .btn-primary {
406
+ background: var(--primary);
407
+ color: white;
408
+ }
409
+ .btn-primary:hover {
410
+ background: var(--primary-hover);
411
+ }
412
+ .days-header {
413
+ display: grid;
414
+ grid-template-columns: 1fr repeat(7, 40px);
415
+ gap: 4px;
416
+ margin-bottom: 8px;
417
+ padding: 0 4px;
418
+ }
419
+ .day-label {
420
+ text-align: center;
421
+ font-size: 0.7rem;
422
+ color: var(--text-secondary);
423
+ text-transform: uppercase;
424
+ }
425
+ .habit-row {
426
+ display: grid;
427
+ grid-template-columns: 1fr repeat(7, 40px);
428
+ gap: 4px;
429
+ padding: 10px 4px;
430
+ border-radius: var(--radius);
431
+ margin-bottom: 4px;
432
+ align-items: center;
433
+ }
434
+ .habit-row:hover {
435
+ background: var(--surface);
436
+ }
437
+ .habit-name {
438
+ font-size: 0.9rem;
439
+ font-weight: 500;
440
+ overflow: hidden;
441
+ text-overflow: ellipsis;
442
+ white-space: nowrap;
443
+ }
444
+ .check-cell {
445
+ display: flex;
446
+ justify-content: center;
447
+ align-items: center;
448
+ }
449
+ .check-btn {
450
+ width: 28px;
451
+ height: 28px;
452
+ border-radius: 6px;
453
+ border: 2px solid var(--border);
454
+ background: transparent;
455
+ cursor: pointer;
456
+ padding: 0;
457
+ display: flex;
458
+ align-items: center;
459
+ justify-content: center;
460
+ transition: all 0.15s;
461
+ color: transparent;
462
+ font-size: 14px;
463
+ }
464
+ .check-btn.checked {
465
+ background: var(--success);
466
+ border-color: var(--success);
467
+ color: white;
468
+ }
469
+ .check-btn:hover {
470
+ border-color: var(--success);
471
+ }
472
+ .delete-btn {
473
+ background: transparent;
474
+ color: var(--text-secondary);
475
+ border: none;
476
+ padding: 4px 8px;
477
+ font-size: 0.8rem;
478
+ cursor: pointer;
479
+ border-radius: 4px;
480
+ }
481
+ .delete-btn:hover {
482
+ color: #ef4444;
483
+ background: rgba(239, 68, 68, 0.1);
484
+ }
485
+ .empty-state {
486
+ text-align: center;
487
+ padding: 48px 0;
488
+ color: var(--text-secondary);
489
+ }
490
+ ```
@@ -31,7 +31,7 @@ When the user asks to see, open, or pull up a document:
31
31
 
32
32
  1. Check the `<active_documents>` block in your context — it lists all documents in this conversation with their `surface_id` and title.
33
33
  2. If the document is NOT in `<active_documents>`, call `document_list` with a `query` matching the document title. For guardian/local users, this searches across previous conversations and sessions.
34
- 3. Once you have the `surface_id`, call `document_open` to open the editor panel. This both surfaces the editor on the client and returns the document content. If the user only needs the text (not the editor), use `document_read` instead.
34
+ 3. Once you have the `surface_id`, call `document_open` to open the editor panel. This surfaces the editor on the client and returns document metadata (`surface_id`, `title`, `word_count`) — not the full content. If you need the actual document text, follow up with `document_read`.
35
35
 
36
36
  **Never** search the filesystem, conversation history, or archives to find a document. Always use `document_list` with a `query`.
37
37
 
@@ -27,13 +27,6 @@ When the user mentions "email" - sending, reading, checking, decluttering, draft
27
27
 
28
28
  Do not offer the assistant's own email as an option unless the user specifically asks. If Gmail and Outlook are not connected, guide them through setup.
29
29
 
30
- ## Communication Style
31
-
32
- - **Be action-oriented.** When the user asks to do something ("declutter", "check my email"), start doing it immediately. Don't ask for permission to read their inbox - that's obviously what they want.
33
- - **Keep it human.** Never mention OAuth, tokens, APIs, sandboxes, credential proxies, or other technical internals. If something isn't working, say "Gmail needs to be reconnected" - not "the OAuth2 access token for google has expired."
34
- - **Show progress.** When running a tool that scans many emails, tell the user what you're doing: "Scanning your inbox for clutter..." Don't go silent.
35
- - **Be brief and warm.** One or two sentences per update is plenty. Don't over-explain what you're about to do - just do it and narrate lightly.
36
-
37
30
  When a platform is connected (auth test succeeds), always use the messaging API tools for that platform. Never fall back to browser automation, shell commands (bash, curl), or any other approach for operations that messaging tools can handle. The messaging tools handle authentication internally - never try to access tokens or call APIs directly. Browser automation is only appropriate for initial credential setup (OAuth consent screens), not for day-to-day messaging operations.
38
31
 
39
32
  **Exception: Slack.** Slack messaging should use the Slack Web API directly via CLI, not messaging tools. See the **slack** skill for details.
@@ -29,7 +29,7 @@
29
29
  */
30
30
 
31
31
  type FlagSlot = {
32
- overrides: Record<string, boolean> | null;
32
+ overrides: Record<string, boolean | string> | null;
33
33
  fromGateway: boolean;
34
34
  };
35
35
 
@@ -44,7 +44,7 @@ function slot(): FlagSlot {
44
44
  }
45
45
 
46
46
  /** Read the current override cache. `null` means not yet populated. */
47
- export function getCachedOverrides(): Record<string, boolean> | null {
47
+ export function getCachedOverrides(): Record<string, boolean | string> | null {
48
48
  return slot().overrides;
49
49
  }
50
50
 
@@ -66,7 +66,7 @@ export function isCachedFromGateway(): boolean {
66
66
  * `initFeatureFlagOverrides()` calls are no-ops.
67
67
  */
68
68
  export function setCachedOverrides(
69
- overrides: Record<string, boolean>,
69
+ overrides: Record<string, boolean | string>,
70
70
  options: { fromGateway: boolean },
71
71
  ): void {
72
72
  const s = slot();
@@ -33,6 +33,14 @@
33
33
  "description": "Enable the condensed pre-chat onboarding flow for a standard LaunchDarkly percentage rollout.",
34
34
  "defaultEnabled": false
35
35
  },
36
+ {
37
+ "id": "experiment-activation-flow-2026-06-03",
38
+ "scope": "client",
39
+ "key": "experiment-activation-flow-2026-06-03",
40
+ "label": "Activation Flow Experiment 2026-06-03",
41
+ "description": "Route allowlisted users to the activation-rail bootstrap template after pre-chat. Off by default and targeted through LaunchDarkly.",
42
+ "defaultEnabled": false
43
+ },
36
44
  {
37
45
  "id": "local-docker-enabled",
38
46
  "scope": "client",
@@ -57,6 +65,14 @@
57
65
  "description": "Control Developer nav visibility in macOS settings",
58
66
  "defaultEnabled": false
59
67
  },
68
+ {
69
+ "id": "system-schedule-toggles",
70
+ "scope": "assistant",
71
+ "key": "system-schedule-toggles",
72
+ "label": "System Schedule Toggles",
73
+ "description": "Show Heartbeat and Consolidation pause toggles on the schedules settings page. The toggles only pause automatic runs; manual Run now remains available.",
74
+ "defaultEnabled": false
75
+ },
60
76
  {
61
77
  "id": "developer-menu-items",
62
78
  "scope": "client",
@@ -363,7 +379,7 @@
363
379
  },
364
380
  {
365
381
  "id": "home-page",
366
- "scope": "both",
382
+ "scope": "client",
367
383
  "key": "home-page",
368
384
  "label": "Home Page",
369
385
  "description": "Enable the Home page as the default landing view.",
@@ -450,11 +466,27 @@
450
466
  "defaultEnabled": false
451
467
  },
452
468
  {
453
- "id": "self-intro-greeting",
469
+ "id": "empty-state-dynamic-greetings",
454
470
  "scope": "assistant",
471
+ "key": "empty-state-dynamic-greetings",
472
+ "label": "Dynamic Empty-State Greetings",
473
+ "description": "Enable the empty new-chat screen to refresh cached greeting options via the empty-state greeting LLM call site. When off, the assistant serves workspace-authored greetings or static fallbacks only.",
474
+ "defaultEnabled": false
475
+ },
476
+ {
477
+ "id": "self-intro-greeting",
478
+ "scope": "both",
455
479
  "key": "self-intro-greeting",
456
480
  "label": "Self-intro first message",
457
- "description": "On the first conversation, send a natural self-introduction (e.g. \"Hi Vela, I'm alex. Nice to meet you.\") on the user's behalf and route it through real LLM inference, instead of serving the canned first greeting. Names come from the onboarding context; falls back to the canned greeting when no name is known. See assistant/src/daemon/first-greeting.ts (buildSelfIntroMessage).",
481
+ "description": "On the first conversation, send a natural self-introduction (e.g. \"Hi Vela, I'm alex. Nice to meet you.\") on the user's behalf and route it through real LLM inference, instead of serving the canned first greeting. Names come from the onboarding context; falls back to the canned greeting when no name is known. Exposed to clients so pre-hatch onboarding can compute the source-of-truth initial message, and to the assistant so older clients can still be gated server-side. See assistant/src/daemon/first-greeting.ts (buildSelfIntroMessage).",
482
+ "defaultEnabled": false
483
+ },
484
+ {
485
+ "id": "provider-first-profile-creation",
486
+ "scope": "client",
487
+ "key": "provider-first-profile-creation",
488
+ "label": "Provider-First Profile Creation",
489
+ "description": "New profile creation flow: pick (or inline-create) a provider first, then a model, with pre-filled provider and profile name/key, plus a quick-add \"+\" in the chat composer's Model Profile menu. When off, profile creation uses the previous field order and there is no composer quick-add.",
458
490
  "defaultEnabled": false
459
491
  }
460
492
  ]
@@ -23,6 +23,7 @@ describe("MemoryV2ConfigSchema", () => {
23
23
  bm25_k1: 1.2,
24
24
  bm25_b: 0.4,
25
25
  consolidation_interval_hours: 4,
26
+ consolidation_enabled: true,
26
27
  consolidation_max_buffer_lines: 100,
27
28
  max_page_chars: 5000,
28
29
  consolidation_prompt_path: null,
@@ -0,0 +1,25 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { MemoryV3ConfigSchema } from "../memory-v3.js";
4
+
5
+ describe("MemoryV3ConfigSchema", () => {
6
+ test("parses an empty object to documented defaults", () => {
7
+ const parsed = MemoryV3ConfigSchema.parse({});
8
+ expect(parsed).toEqual({
9
+ workingSet: { maxPages: 150, evictWindow: 5 },
10
+ l2Concurrency: 16,
11
+ });
12
+ });
13
+
14
+ test("accepts an explicit l2Concurrency override", () => {
15
+ expect(MemoryV3ConfigSchema.parse({ l2Concurrency: 8 }).l2Concurrency).toBe(
16
+ 8,
17
+ );
18
+ });
19
+
20
+ test("rejects a non-positive or non-integer l2Concurrency", () => {
21
+ expect(() => MemoryV3ConfigSchema.parse({ l2Concurrency: 0 })).toThrow();
22
+ expect(() => MemoryV3ConfigSchema.parse({ l2Concurrency: -4 })).toThrow();
23
+ expect(() => MemoryV3ConfigSchema.parse({ l2Concurrency: 1.5 })).toThrow();
24
+ });
25
+ });
@@ -21,6 +21,7 @@ const LLMProvider = z.enum([
21
21
  "fireworks",
22
22
  "openrouter",
23
23
  "openai-compatible",
24
+ "minimax",
24
25
  ]);
25
26
  type LLMProvider = z.infer<typeof LLMProvider>;
26
27
 
@@ -194,6 +194,14 @@ export const MemoryV2ConfigSchema = z
194
194
  .describe(
195
195
  "Hours between scheduled consolidation runs that synthesize buffered memories into concept pages",
196
196
  ),
197
+ consolidation_enabled: z
198
+ .boolean({
199
+ error: "memory.v2.consolidation_enabled must be a boolean",
200
+ })
201
+ .default(true)
202
+ .describe(
203
+ "Whether automatic scheduled consolidation runs are enabled. Manual run-now requests remain available while memory.v2.enabled is true.",
204
+ ),
197
205
  consolidation_max_buffer_lines: z
198
206
  .number({
199
207
  error: "memory.v2.consolidation_max_buffer_lines must be a number",
@@ -33,6 +33,14 @@ export const MemoryV3ConfigSchema = z
33
33
  workingSet: MemoryV3WorkingSetSchema.default(
34
34
  MemoryV3WorkingSetSchema.parse({}),
35
35
  ),
36
+ l2Concurrency: z
37
+ .number({ error: "memory.v3.l2Concurrency must be a number" })
38
+ .int("memory.v3.l2Concurrency must be an integer")
39
+ .positive("memory.v3.l2Concurrency must be a positive integer")
40
+ .default(16)
41
+ .describe(
42
+ "Bounded fan-out for the per-leaf L2 selection (number of leaf selector calls in flight at once).",
43
+ ),
36
44
  })
37
45
  .describe("Memory v3 — topic-tree routing with a carry-forward working set");
38
46