@vellumai/assistant 0.7.2 → 0.8.0

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 (424) hide show
  1. package/ARCHITECTURE.md +45 -29
  2. package/Dockerfile +1 -0
  3. package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
  4. package/bun.lock +3 -0
  5. package/docs/architecture/memory.md +5 -2
  6. package/knip.json +1 -0
  7. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  8. package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
  9. package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
  10. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
  11. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
  12. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
  13. package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
  14. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  15. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  16. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  17. package/openapi.yaml +470 -25
  18. package/package.json +3 -1
  19. package/src/__tests__/annotate-risk-options.test.ts +291 -0
  20. package/src/__tests__/app-control-flow.test.ts +21 -11
  21. package/src/__tests__/approval-cascade.test.ts +8 -16
  22. package/src/__tests__/approval-routes-http.test.ts +6 -0
  23. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  24. package/src/__tests__/assistant-event.test.ts +0 -10
  25. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  26. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  27. package/src/__tests__/auto-analysis-end-to-end.test.ts +48 -0
  28. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  29. package/src/__tests__/call-constants.test.ts +10 -1
  30. package/src/__tests__/call-controller.test.ts +127 -0
  31. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  32. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  33. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  34. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
  35. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  36. package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
  37. package/src/__tests__/config-schema.test.ts +1 -0
  38. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  39. package/src/__tests__/config-watcher.test.ts +140 -69
  40. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  41. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  42. package/src/__tests__/context-search-fanout.test.ts +0 -1
  43. package/src/__tests__/context-search-memory-source.test.ts +6 -33
  44. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  45. package/src/__tests__/context-search-pkb-source.test.ts +12 -7
  46. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  47. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -0
  48. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  51. package/src/__tests__/conversation-agent-loop.test.ts +457 -8
  52. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
  53. package/src/__tests__/conversation-error.test.ts +150 -3
  54. package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
  55. package/src/__tests__/conversation-process-callsite.test.ts +38 -0
  56. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +74 -0
  58. package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
  59. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  60. package/src/__tests__/conversation-store.test.ts +0 -18
  61. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
  62. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  63. package/src/__tests__/conversation-surfaces-data-persist.test.ts +476 -0
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +61 -5
  65. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  66. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  67. package/src/__tests__/credentials-cli.test.ts +7 -0
  68. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  69. package/src/__tests__/date-context.test.ts +164 -2
  70. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  71. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  72. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  73. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  74. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  75. package/src/__tests__/disk-usage.test.ts +150 -0
  76. package/src/__tests__/events-client-registration.test.ts +52 -0
  77. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  78. package/src/__tests__/file-write-tool.test.ts +4 -10
  79. package/src/__tests__/filing-service.test.ts +2 -20
  80. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  82. package/src/__tests__/heartbeat-service.test.ts +260 -11
  83. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  84. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  85. package/src/__tests__/host-bash-routes.test.ts +178 -13
  86. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  87. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  88. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  89. package/src/__tests__/host-file-proxy.test.ts +268 -6
  90. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  91. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  92. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  93. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  94. package/src/__tests__/injector-chain.test.ts +36 -16
  95. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  96. package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
  98. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  99. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  100. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  101. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  102. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  103. package/src/__tests__/notification-decision-fallback.test.ts +91 -0
  104. package/src/__tests__/notification-decision-strategy.test.ts +22 -0
  105. package/src/__tests__/oauth-cli.test.ts +121 -0
  106. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  107. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  108. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  109. package/src/__tests__/openai-provider.test.ts +45 -8
  110. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  111. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  112. package/src/__tests__/platform.test.ts +2 -1
  113. package/src/__tests__/playbook-execution.test.ts +0 -43
  114. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  115. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  116. package/src/__tests__/provider-tool-name.test.ts +23 -0
  117. package/src/__tests__/relay-server.test.ts +60 -5
  118. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  119. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  120. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  121. package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
  122. package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
  123. package/src/__tests__/secret-response-routing.test.ts +7 -5
  124. package/src/__tests__/server-history-render.test.ts +82 -0
  125. package/src/__tests__/skill-include-graph.test.ts +31 -0
  126. package/src/__tests__/skill-load-tool.test.ts +44 -16
  127. package/src/__tests__/skills.test.ts +39 -0
  128. package/src/__tests__/suggestion-routes.test.ts +46 -0
  129. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
  130. package/src/__tests__/tool-executor.test.ts +155 -0
  131. package/src/__tests__/twilio-validation.test.ts +2 -2
  132. package/src/__tests__/voice-session-bridge.test.ts +3 -0
  133. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  134. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  136. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
  137. package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
  138. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +78 -0
  139. package/src/agent/loop.ts +11 -0
  140. package/src/approvals/guardian-request-resolvers.ts +3 -32
  141. package/src/backup/snapshot-lock.ts +2 -27
  142. package/src/bundler/compiler-tools.ts +3 -2
  143. package/src/calls/call-constants.ts +5 -8
  144. package/src/calls/call-controller.ts +130 -67
  145. package/src/calls/call-conversation-messages.ts +46 -10
  146. package/src/calls/relay-server.ts +7 -1
  147. package/src/calls/voice-session-bridge.ts +1 -1
  148. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  149. package/src/cli/commands/bash.ts +35 -108
  150. package/src/cli/commands/contacts.ts +64 -25
  151. package/src/cli/commands/credentials.ts +56 -0
  152. package/src/cli/commands/memory-v2.ts +11 -10
  153. package/src/cli/commands/oauth/__tests__/connect.test.ts +401 -219
  154. package/src/cli/commands/oauth/connect.ts +124 -40
  155. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  156. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  157. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  158. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  159. package/src/cli/commands/platform/index.ts +16 -7
  160. package/src/cli/commands/status.ts +57 -0
  161. package/src/cli/program.ts +4 -2
  162. package/src/config/assistant-feature-flags.ts +13 -3
  163. package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
  164. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  165. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  166. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  167. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  168. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  169. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  170. package/src/config/env.ts +0 -8
  171. package/src/config/feature-flag-registry.json +13 -5
  172. package/src/config/loader.ts +199 -27
  173. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  174. package/src/config/schemas/call-site-catalog.ts +14 -0
  175. package/src/config/schemas/channels.ts +0 -5
  176. package/src/config/schemas/heartbeat.ts +1 -1
  177. package/src/config/schemas/llm.ts +2 -0
  178. package/src/config/schemas/memory-lifecycle.ts +13 -0
  179. package/src/config/schemas/memory-v2.ts +76 -12
  180. package/src/config/schemas/platform.ts +43 -3
  181. package/src/config/schemas/services.ts +28 -0
  182. package/src/config/seed-inference-profiles.ts +230 -33
  183. package/src/contacts/contact-store.ts +0 -25
  184. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
  185. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  186. package/src/daemon/assistant-attachments.ts +4 -4
  187. package/src/daemon/config-watcher.ts +85 -57
  188. package/src/daemon/conversation-agent-loop-handlers.ts +38 -0
  189. package/src/daemon/conversation-agent-loop.ts +183 -43
  190. package/src/daemon/conversation-error.ts +87 -15
  191. package/src/daemon/conversation-lifecycle.ts +22 -10
  192. package/src/daemon/conversation-process.ts +8 -0
  193. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  194. package/src/daemon/conversation-store.ts +2 -2
  195. package/src/daemon/conversation-surfaces.ts +211 -29
  196. package/src/daemon/conversation-tool-setup.ts +66 -19
  197. package/src/daemon/conversation.ts +18 -23
  198. package/src/daemon/date-context.ts +71 -22
  199. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  200. package/src/daemon/disk-pressure-guard.ts +343 -0
  201. package/src/daemon/disk-pressure-policy.ts +163 -0
  202. package/src/daemon/handlers/shared.ts +26 -1
  203. package/src/daemon/handlers/skills.ts +3 -4
  204. package/src/daemon/host-app-control-proxy.ts +137 -41
  205. package/src/daemon/host-bash-proxy.ts +47 -22
  206. package/src/daemon/host-browser-proxy.ts +1 -1
  207. package/src/daemon/host-cu-proxy.ts +50 -4
  208. package/src/daemon/host-file-proxy.ts +44 -8
  209. package/src/daemon/host-transfer-proxy.ts +97 -6
  210. package/src/daemon/lifecycle.ts +167 -101
  211. package/src/daemon/meet-host-supervisor.ts +4 -4
  212. package/src/daemon/meet-manifest-loader.ts +0 -1
  213. package/src/daemon/memory-v2-startup.ts +66 -15
  214. package/src/daemon/message-protocol.ts +3 -0
  215. package/src/daemon/message-types/conversations.ts +4 -0
  216. package/src/daemon/message-types/disk-pressure.ts +9 -0
  217. package/src/daemon/message-types/messages.ts +22 -1
  218. package/src/daemon/profiler-run-store.ts +5 -5
  219. package/src/daemon/tool-setup-types.ts +2 -2
  220. package/src/documents/document-store.ts +119 -0
  221. package/src/filing/filing-service.ts +29 -5
  222. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  223. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  224. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  225. package/src/heartbeat/heartbeat-service.ts +205 -31
  226. package/src/home/feed-scheduler.ts +18 -0
  227. package/src/inbound/platform-callback-registration.ts +8 -15
  228. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  229. package/src/ipc/assistant-server.ts +149 -38
  230. package/src/ipc/gateway-client.ts +37 -3
  231. package/src/ipc/skill-server.ts +99 -42
  232. package/src/live-voice/live-voice-archive.ts +4 -4
  233. package/src/live-voice/protocol.ts +5 -7
  234. package/src/media/image-service.ts +1 -7
  235. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  236. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +34 -51
  237. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  238. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  239. package/src/memory/admin.ts +5 -9
  240. package/src/memory/context-search/agent-runner.ts +19 -2
  241. package/src/memory/context-search/sources/conversations.ts +2 -11
  242. package/src/memory/context-search/sources/memory-v2.ts +1 -16
  243. package/src/memory/context-search/sources/memory.ts +2 -3
  244. package/src/memory/context-search/sources/pkb.ts +2 -3
  245. package/src/memory/context-search/types.ts +0 -1
  246. package/src/memory/conversation-crud.ts +4 -12
  247. package/src/memory/db-init.ts +2 -0
  248. package/src/memory/embedding-runtime-manager.ts +119 -5
  249. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +136 -82
  250. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
  251. package/src/memory/graph/conversation-graph-memory.ts +72 -61
  252. package/src/memory/graph/extraction.ts +1 -3
  253. package/src/memory/graph/graph-search.test.ts +11 -67
  254. package/src/memory/graph/graph-search.ts +4 -24
  255. package/src/memory/graph/retriever.test.ts +12 -1
  256. package/src/memory/graph/retriever.ts +10 -15
  257. package/src/memory/graph/tool-handlers.ts +3 -4
  258. package/src/memory/graph/tools.ts +4 -4
  259. package/src/memory/indexer.ts +53 -45
  260. package/src/memory/job-handlers/backfill.ts +2 -11
  261. package/src/memory/job-handlers/cleanup.ts +43 -0
  262. package/src/memory/job-handlers/embedding.ts +6 -8
  263. package/src/memory/job-handlers/summarization.ts +2 -7
  264. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
  265. package/src/memory/jobs/embed-concept-page.ts +223 -87
  266. package/src/memory/jobs-store.ts +48 -0
  267. package/src/memory/jobs-worker.ts +85 -43
  268. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  269. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  270. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  271. package/src/memory/migrations/index.ts +1 -0
  272. package/src/memory/pkb/pkb-search.test.ts +7 -0
  273. package/src/memory/pkb/pkb-search.ts +4 -5
  274. package/src/memory/qdrant-client.ts +3 -13
  275. package/src/memory/rerank-local.ts +374 -0
  276. package/src/memory/search/semantic.ts +10 -72
  277. package/src/memory/trace-event-store.ts +1 -17
  278. package/src/memory/v2/__tests__/activation.test.ts +346 -255
  279. package/src/memory/v2/__tests__/consolidation-job.test.ts +61 -40
  280. package/src/memory/v2/__tests__/injection.test.ts +297 -190
  281. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  282. package/src/memory/v2/__tests__/qdrant.test.ts +326 -9
  283. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  284. package/src/memory/v2/__tests__/sim.test.ts +113 -196
  285. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  286. package/src/memory/v2/__tests__/static-context.test.ts +77 -14
  287. package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
  288. package/src/memory/v2/activation.ts +149 -156
  289. package/src/memory/v2/consolidation-job.ts +69 -20
  290. package/src/memory/v2/injection.ts +75 -68
  291. package/src/memory/v2/page-store.ts +39 -0
  292. package/src/memory/v2/prompts/consolidation.ts +41 -1
  293. package/src/memory/v2/qdrant.ts +306 -46
  294. package/src/memory/v2/reranker.ts +177 -0
  295. package/src/memory/v2/sim.ts +77 -110
  296. package/src/memory/v2/skill-content.ts +4 -3
  297. package/src/memory/v2/skill-store.ts +82 -59
  298. package/src/memory/v2/static-context.ts +26 -8
  299. package/src/memory/v2/sweep-job.ts +5 -6
  300. package/src/memory/v2/types.ts +17 -10
  301. package/src/notifications/copy-composer.ts +47 -0
  302. package/src/notifications/decision-engine.ts +46 -0
  303. package/src/notifications/signal.ts +4 -0
  304. package/src/oauth/AGENTS.md +3 -1
  305. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  306. package/src/oauth/connect-orchestrator.ts +2 -0
  307. package/src/oauth/connection-resolver.test.ts +66 -1
  308. package/src/oauth/connection-resolver.ts +55 -1
  309. package/src/oauth/oauth-connect-state.ts +77 -0
  310. package/src/oauth/seed-providers.ts +58 -1
  311. package/src/permissions/gateway-threshold-reader.ts +116 -8
  312. package/src/permissions/prompter.ts +86 -96
  313. package/src/permissions/secret-prompter.ts +31 -31
  314. package/src/plugins/defaults/injectors.ts +36 -4
  315. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  316. package/src/plugins/types.ts +7 -0
  317. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  318. package/src/proactive-artifact/decision.test.ts +226 -0
  319. package/src/proactive-artifact/decision.ts +165 -0
  320. package/src/proactive-artifact/index.ts +7 -0
  321. package/src/proactive-artifact/job.test.ts +914 -0
  322. package/src/proactive-artifact/job.ts +366 -0
  323. package/src/proactive-artifact/message-copy.ts +58 -0
  324. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  325. package/src/proactive-artifact/trigger-state.ts +119 -0
  326. package/src/prompts/normalize-onboarding.ts +80 -0
  327. package/src/prompts/persona-resolver.ts +101 -9
  328. package/src/prompts/system-prompt.ts +21 -7
  329. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  330. package/src/prompts/templates/SOUL.md +13 -28
  331. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  332. package/src/providers/model-intents.ts +7 -0
  333. package/src/providers/openrouter/client.ts +8 -0
  334. package/src/providers/retry.ts +50 -0
  335. package/src/providers/types.ts +1 -0
  336. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  337. package/src/runtime/agent-wake.ts +238 -100
  338. package/src/runtime/assistant-event-hub.ts +36 -6
  339. package/src/runtime/assistant-event.ts +0 -1
  340. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  341. package/src/runtime/auth/route-policy.ts +15 -1
  342. package/src/runtime/auth/same-actor.ts +216 -0
  343. package/src/runtime/channel-approvals.ts +3 -2
  344. package/src/runtime/channel-retry-sweep.ts +65 -1
  345. package/src/runtime/local-actor-identity.ts +52 -11
  346. package/src/runtime/pending-interactions.ts +27 -15
  347. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  348. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  349. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  350. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
  351. package/src/runtime/routes/approval-routes.ts +7 -3
  352. package/src/runtime/routes/client-routes.ts +20 -2
  353. package/src/runtime/routes/consolidation-routes.ts +8 -9
  354. package/src/runtime/routes/contact-routes.ts +0 -25
  355. package/src/runtime/routes/conversation-query-routes.ts +44 -1
  356. package/src/runtime/routes/conversation-routes.ts +35 -26
  357. package/src/runtime/routes/debug-bash-routes.ts +165 -0
  358. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  359. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  360. package/src/runtime/routes/documents-routes.ts +2 -75
  361. package/src/runtime/routes/events-routes.ts +41 -9
  362. package/src/runtime/routes/filing-routes.ts +2 -3
  363. package/src/runtime/routes/host-bash-routes.ts +23 -3
  364. package/src/runtime/routes/host-cu-routes.ts +33 -6
  365. package/src/runtime/routes/host-file-routes.ts +32 -6
  366. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  367. package/src/runtime/routes/identity-routes.ts +7 -138
  368. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  369. package/src/runtime/routes/index.ts +6 -0
  370. package/src/runtime/routes/memory-item-routes.test.ts +37 -17
  371. package/src/runtime/routes/memory-item-routes.ts +5 -6
  372. package/src/runtime/routes/memory-v2-routes.ts +136 -17
  373. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  374. package/src/runtime/verification-outbound-actions.ts +4 -4
  375. package/src/schedule/run-script.ts +37 -5
  376. package/src/schedule/scheduler.ts +20 -1
  377. package/src/security/encrypted-store.ts +2 -0
  378. package/src/security/secure-keys.ts +55 -0
  379. package/src/skills/include-graph.ts +35 -13
  380. package/src/skills/remote-skill-policy.ts +4 -10
  381. package/src/subagent/index.ts +1 -7
  382. package/src/subagent/manager.ts +1 -15
  383. package/src/tasks/task-runner.ts +0 -1
  384. package/src/tasks/task-store.ts +0 -3
  385. package/src/tools/background-tool-registry.ts +17 -3
  386. package/src/tools/document/document-tool.ts +20 -0
  387. package/src/tools/executor.ts +18 -2
  388. package/src/tools/host-filesystem/edit.test.ts +151 -0
  389. package/src/tools/host-filesystem/edit.ts +43 -1
  390. package/src/tools/host-filesystem/read.test.ts +129 -0
  391. package/src/tools/host-filesystem/read.ts +43 -1
  392. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  393. package/src/tools/host-filesystem/transfer.ts +56 -11
  394. package/src/tools/host-filesystem/write.test.ts +134 -0
  395. package/src/tools/host-filesystem/write.ts +43 -1
  396. package/src/tools/host-terminal/host-shell.ts +13 -6
  397. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  398. package/src/tools/memory/register.test.ts +14 -9
  399. package/src/tools/memory/register.ts +1 -2
  400. package/src/tools/permission-checker.ts +15 -0
  401. package/src/tools/provider-tool-name.ts +28 -0
  402. package/src/tools/registry.ts +30 -9
  403. package/src/tools/skills/load.ts +24 -20
  404. package/src/tools/terminal/shell.ts +9 -1
  405. package/src/tools/tool-approval-handler.ts +31 -6
  406. package/src/tools/tool-name-aliases.ts +19 -0
  407. package/src/tools/types.ts +43 -3
  408. package/src/tts/provider-catalog.ts +3 -5
  409. package/src/util/disk-usage.ts +138 -0
  410. package/src/util/platform.ts +21 -11
  411. package/src/util/process-liveness.ts +26 -0
  412. package/src/workspace/heartbeat-service.ts +19 -0
  413. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  414. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  415. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +14 -0
  416. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  417. package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
  418. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
  419. package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
  420. package/src/workspace/migrations/registry.ts +14 -0
  421. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  422. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  423. package/src/memory/v2/skill-qdrant.ts +0 -404
  424. package/src/signals/bash.ts +0 -198
@@ -10,7 +10,6 @@ mock.module("../../../util/logger.js", () => ({
10
10
  import {
11
11
  sampleConcepts as sharedSampleConcepts,
12
12
  sampleConfig,
13
- sampleSkills,
14
13
  } from "../../../memory/__tests__/fixtures/memory-v2-activation-fixtures.js";
15
14
 
16
15
  let rawConfigFixture: Record<string, unknown> = {};
@@ -36,7 +35,6 @@ import {
36
35
  backfillMemoryV2ActivationMessageId,
37
36
  type MemoryV2ConceptRowRecord,
38
37
  type MemoryV2ConfigSnapshot,
39
- type MemoryV2SkillRowRecord,
40
38
  recordMemoryV2ActivationLog,
41
39
  } from "../../../memory/memory-v2-activation-log-store.js";
42
40
  import {
@@ -153,7 +151,6 @@ describe("GET /v1/messages/:id/llm-context — memoryV2Activation", () => {
153
151
  turn: 4,
154
152
  mode: "per-turn",
155
153
  concepts: sampleConcepts,
156
- skills: sampleSkills,
157
154
  config: sampleConfig,
158
155
  });
159
156
  backfillMemoryV2ActivationMessageId(conversationId, messageId);
@@ -163,7 +160,6 @@ describe("GET /v1/messages/:id/llm-context — memoryV2Activation", () => {
163
160
  turn: number;
164
161
  mode: "context-load" | "per-turn";
165
162
  concepts: MemoryV2ConceptRowRecord[];
166
- skills: MemoryV2SkillRowRecord[];
167
163
  config: MemoryV2ConfigSnapshot;
168
164
  } | null;
169
165
  memoryRecall: unknown;
@@ -173,7 +169,6 @@ describe("GET /v1/messages/:id/llm-context — memoryV2Activation", () => {
173
169
  expect(body.memoryV2Activation!.turn).toBe(4);
174
170
  expect(body.memoryV2Activation!.mode).toBe("per-turn");
175
171
  expect(body.memoryV2Activation!.concepts).toEqual(sampleConcepts);
176
- expect(body.memoryV2Activation!.skills).toEqual(sampleSkills);
177
172
  expect(body.memoryV2Activation!.config).toEqual(sampleConfig);
178
173
  // Backwards-compat: memoryRecall field still present.
179
174
  expect(body).toHaveProperty("memoryRecall");
@@ -90,7 +90,7 @@ describe("setHeartbeatConfig handler", () => {
90
90
  // invalidation + getConfig() read picked up the new on-disk state.
91
91
  expect(result.success).toBe(true);
92
92
  expect(result.enabled).toBe(true);
93
- expect(result.intervalMs).toBe(6 * 3_600_000);
93
+ expect(result.intervalMs).toBe(30 * 60_000);
94
94
  expect(result.activeHoursStart).toBe(8);
95
95
  expect(result.activeHoursEnd).toBe(22);
96
96
  });
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Tests for the memory v2 route handlers in `memory-v2-routes.ts`.
3
+ *
4
+ * Currently focused on `memory_v2_list_concept_pages`:
5
+ * - empty workspace → returns no pages
6
+ * - populated workspace → surfaces slug, bodyBytes, edgeCount, updatedAtMs
7
+ * - corrupt page on disk → logged-and-skipped, does not poison listing
8
+ */
9
+
10
+ import { mkdtempSync, rmSync } from "node:fs";
11
+ import { mkdir, writeFile } from "node:fs/promises";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
15
+
16
+ import { writePage } from "../../../memory/v2/page-store.js";
17
+ import type { ConceptPage } from "../../../memory/v2/types.js";
18
+ import type { MemoryV2ListConceptPagesResult } from "../memory-v2-routes.js";
19
+ import { ROUTES } from "../memory-v2-routes.js";
20
+ import type { RouteDefinition } from "../types.js";
21
+
22
+ // ─── Setup ─────────────────────────────────────────────────────────────────
23
+
24
+ let workspaceDir: string;
25
+ let origWorkspaceDir: string | undefined;
26
+
27
+ function findHandler(operationId: string): RouteDefinition["handler"] {
28
+ const route = ROUTES.find((r) => r.operationId === operationId);
29
+ if (!route) throw new Error(`Route ${operationId} not found`);
30
+ return route.handler;
31
+ }
32
+
33
+ beforeEach(() => {
34
+ workspaceDir = mkdtempSync(join(tmpdir(), "vellum-memv2-list-"));
35
+ origWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
36
+ process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
37
+ });
38
+
39
+ afterEach(() => {
40
+ if (origWorkspaceDir === undefined) {
41
+ delete process.env.VELLUM_WORKSPACE_DIR;
42
+ } else {
43
+ process.env.VELLUM_WORKSPACE_DIR = origWorkspaceDir;
44
+ }
45
+ try {
46
+ rmSync(workspaceDir, { recursive: true, force: true });
47
+ } catch {
48
+ // best-effort cleanup
49
+ }
50
+ });
51
+
52
+ // ─── Tests ─────────────────────────────────────────────────────────────────
53
+
54
+ describe("memory_v2_list_concept_pages handler", () => {
55
+ test("returns empty list for an empty workspace", async () => {
56
+ const handler = findHandler("memory_v2_list_concept_pages");
57
+ const result = (await handler({
58
+ body: {},
59
+ })) as MemoryV2ListConceptPagesResult;
60
+
61
+ expect(result).toEqual({ pages: [] });
62
+ });
63
+
64
+ test("returns slugs, body bytes, edge counts, and mtimes for populated workspace", async () => {
65
+ const before = Date.now();
66
+
67
+ const pages: ConceptPage[] = [
68
+ {
69
+ slug: "alice",
70
+ frontmatter: { edges: ["bob", "carol"], ref_files: [] },
71
+ body: "Alice prefers VS Code.\n",
72
+ },
73
+ {
74
+ slug: "bob",
75
+ frontmatter: { edges: [], ref_files: [] },
76
+ body: "Bob ships at end of day.\nLikes async standups.\n",
77
+ },
78
+ {
79
+ slug: "people/carol",
80
+ frontmatter: { edges: ["alice"], ref_files: [] },
81
+ body: "Carol leads the platform team.\n",
82
+ },
83
+ ];
84
+ for (const page of pages) {
85
+ await writePage(workspaceDir, page);
86
+ }
87
+
88
+ const handler = findHandler("memory_v2_list_concept_pages");
89
+ const result = (await handler({
90
+ body: {},
91
+ })) as MemoryV2ListConceptPagesResult;
92
+
93
+ expect(result.pages).toHaveLength(3);
94
+
95
+ const bySlug = new Map(result.pages.map((p) => [p.slug, p]));
96
+
97
+ const alice = bySlug.get("alice");
98
+ expect(alice).toBeDefined();
99
+ expect(alice!.bodyBytes).toBe(Buffer.byteLength(pages[0]!.body, "utf8"));
100
+ expect(alice!.edgeCount).toBe(2);
101
+ expect(alice!.updatedAtMs).toBeGreaterThanOrEqual(before);
102
+ // updatedAtMs must be an integer on the wire — Swift clients decode it as
103
+ // Int64 and a sub-millisecond float (which fs.Stats.mtimeMs returns by
104
+ // default) breaks JSONDecoder strict number parsing.
105
+ expect(Number.isInteger(alice!.updatedAtMs)).toBe(true);
106
+
107
+ const bob = bySlug.get("bob");
108
+ expect(bob).toBeDefined();
109
+ expect(bob!.bodyBytes).toBe(Buffer.byteLength(pages[1]!.body, "utf8"));
110
+ expect(bob!.edgeCount).toBe(0);
111
+ expect(bob!.updatedAtMs).toBeGreaterThanOrEqual(before);
112
+ expect(Number.isInteger(bob!.updatedAtMs)).toBe(true);
113
+
114
+ const carol = bySlug.get("people/carol");
115
+ expect(carol).toBeDefined();
116
+ expect(carol!.bodyBytes).toBe(Buffer.byteLength(pages[2]!.body, "utf8"));
117
+ expect(carol!.edgeCount).toBe(1);
118
+ expect(carol!.updatedAtMs).toBeGreaterThanOrEqual(before);
119
+ expect(Number.isInteger(carol!.updatedAtMs)).toBe(true);
120
+ });
121
+
122
+ test("tolerates a single corrupt page — returns valid pages and skips the broken one", async () => {
123
+ await writePage(workspaceDir, {
124
+ slug: "valid-page",
125
+ frontmatter: { edges: [], ref_files: [] },
126
+ body: "Body of the valid page.\n",
127
+ });
128
+
129
+ // A `.md` file with frontmatter that fails schema validation — `edges`
130
+ // must be a list of strings, not a single number — so `readPage` throws.
131
+ const conceptsDir = join(workspaceDir, "memory", "concepts");
132
+ await mkdir(conceptsDir, { recursive: true });
133
+ await writeFile(
134
+ join(conceptsDir, "broken.md"),
135
+ "---\nedges: 42\n---\nbroken body\n",
136
+ "utf-8",
137
+ );
138
+
139
+ const handler = findHandler("memory_v2_list_concept_pages");
140
+ const result = (await handler({
141
+ body: {},
142
+ })) as MemoryV2ListConceptPagesResult;
143
+
144
+ expect(result.pages).toHaveLength(1);
145
+ expect(result.pages.map((p) => p.slug)).toEqual(["valid-page"]);
146
+ });
147
+ });
@@ -62,8 +62,9 @@ function handleConfirm({ body }: RouteHandlerArgs) {
62
62
  throw new BadRequestError("decision must resolve to allow or deny");
63
63
  }
64
64
 
65
- // Validation passed — consume the pending interaction.
66
- const interaction = pendingInteractions.resolve(requestId)!;
65
+ // Validation passed. Use get() here — the prompter (or ACP directResolve path)
66
+ // owns deregistration via pendingInteractions.resolve().
67
+ const interaction = peeked;
67
68
 
68
69
  log.info(
69
70
  {
@@ -93,7 +94,9 @@ function handleConfirm({ body }: RouteHandlerArgs) {
93
94
  });
94
95
 
95
96
  // ACP permissions: resolve directly without a Conversation object.
97
+ // No PermissionPrompter involved, so the route owns deregistration.
96
98
  if (interaction.directResolve) {
99
+ pendingInteractions.resolve(requestId);
97
100
  interaction.directResolve(effectiveDecision as UserDecision);
98
101
  return { accepted: true };
99
102
  }
@@ -139,7 +142,8 @@ function handleSecret({ body }: RouteHandlerArgs) {
139
142
  throw new BadRequestError('delivery must be "store" or "transient_send"');
140
143
  }
141
144
 
142
- const interaction = pendingInteractions.resolve(requestId);
145
+ // Use get() — SecretPrompter.resolveSecret() owns deregistration.
146
+ const interaction = pendingInteractions.get(requestId);
143
147
  if (!interaction) {
144
148
  throw new NotFoundError("No pending interaction found for this requestId");
145
149
  }
@@ -8,6 +8,7 @@
8
8
  import { z } from "zod";
9
9
 
10
10
  import type { HostProxyCapability } from "../../channels/types.js";
11
+ import { isHttpAuthDisabled } from "../../config/env.js";
11
12
  import { datesToISO } from "../../util/json.js";
12
13
  import { assistantEventHub } from "../assistant-event-hub.js";
13
14
  import { NotFoundError } from "./errors.js";
@@ -33,7 +34,7 @@ export const ROUTES: RouteDefinition[] = [
33
34
  responseBody: z.object({
34
35
  clients: z.array(z.object({}).passthrough()),
35
36
  }),
36
- handler: ({ queryParams }) => {
37
+ handler: ({ queryParams, headers }) => {
37
38
  const capability = queryParams?.capability as
38
39
  | HostProxyCapability
39
40
  | undefined;
@@ -42,8 +43,25 @@ export const ROUTES: RouteDefinition[] = [
42
43
  ? assistantEventHub.listClientsByCapability(capability)
43
44
  : assistantEventHub.listClients();
44
45
 
46
+ // Defense-in-depth: filter the listing to clients owned by the calling
47
+ // actor so users cannot enumerate other users' connected client IDs.
48
+ // Clients with no stored `actorPrincipalId` (legacy SSE subscribers from
49
+ // before host-proxy-same-user, service-gateway tokens) are filtered out
50
+ // — fail-closed is the right default for this security boundary.
51
+ // Dev-bypass mode (DISABLE_HTTP_AUTH=true, mirroring
52
+ // require-bound-guardian.ts) preserves the previous "return all" behavior
53
+ // for platform-managed deployments where the platform handles auth.
54
+ const callerPrincipalId = headers?.["x-vellum-actor-principal-id"];
55
+ const filtered = isHttpAuthDisabled()
56
+ ? clients
57
+ : clients.filter(
58
+ (c) =>
59
+ c.actorPrincipalId !== undefined &&
60
+ c.actorPrincipalId === callerPrincipalId,
61
+ );
62
+
45
63
  return {
46
- clients: clients.map((c) =>
64
+ clients: filtered.map((c) =>
47
65
  datesToISO({
48
66
  clientId: c.clientId,
49
67
  interfaceId: c.interfaceId,
@@ -8,13 +8,13 @@
8
8
  * only surface its config and provide an on-demand trigger for the Settings UI.
9
9
  *
10
10
  * `available` mirrors the filing route's `available` field: it reflects which
11
- * background memory job is active for this instance. When `memory-v2-enabled`
12
- * is off, consolidation returns `available: false` and the UI hides the row.
11
+ * background memory job is active for this instance. When
12
+ * `config.memory.v2.enabled` is false, consolidation returns
13
+ * `available: false` and the UI hides the row.
13
14
  */
14
15
 
15
16
  import { z } from "zod";
16
17
 
17
- import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
18
18
  import { getConfig } from "../../config/loader.js";
19
19
  import { getMemoryCheckpoint } from "../../memory/checkpoints.js";
20
20
  import {
@@ -26,7 +26,7 @@ import { BadRequestError } from "./errors.js";
26
26
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
27
27
 
28
28
  function isConsolidationAvailable(): boolean {
29
- return isAssistantFeatureFlagEnabled("memory-v2-enabled", getConfig());
29
+ return getConfig().memory.v2.enabled;
30
30
  }
31
31
 
32
32
  function consolidationIntervalMs(): number {
@@ -66,14 +66,13 @@ export const ROUTES: RouteDefinition[] = [
66
66
  success: z.boolean(),
67
67
  }),
68
68
  handler: async (_args: RouteHandlerArgs) => {
69
- const available = isConsolidationAvailable();
70
- const v2Config = getConfig().memory.v2;
69
+ const enabled = getConfig().memory.v2.enabled;
71
70
  const intervalMs = consolidationIntervalMs();
72
71
  const lastRunAt = readLastRunAt();
73
72
  const nextRunAt = lastRunAt != null ? lastRunAt + intervalMs : null;
74
73
  return {
75
- available,
76
- enabled: available && v2Config.enabled,
74
+ available: enabled,
75
+ enabled,
77
76
  intervalMs,
78
77
  nextRunAt,
79
78
  lastRunAt,
@@ -99,7 +98,7 @@ export const ROUTES: RouteDefinition[] = [
99
98
  handler: async (_args: RouteHandlerArgs) => {
100
99
  if (!isConsolidationAvailable()) {
101
100
  throw new BadRequestError(
102
- "Consolidation is not available (memory-v2-enabled is off)",
101
+ "Consolidation is not available (memory.v2.enabled is false)",
103
102
  );
104
103
  }
105
104
  // Coalesce: don't pile up duplicate jobs if the worker hasn't picked up
@@ -11,7 +11,6 @@
11
11
  import { z } from "zod";
12
12
 
13
13
  import {
14
- deleteContact,
15
14
  getAssistantContactMetadata,
16
15
  getChannelById,
17
16
  getContact,
@@ -42,7 +41,6 @@ import {
42
41
  import {
43
42
  BadRequestError,
44
43
  ConflictError,
45
- ForbiddenError,
46
44
  NotFoundError,
47
45
  } from "./errors.js";
48
46
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
@@ -154,17 +152,6 @@ function handleGetContact(contactId: string) {
154
152
  };
155
153
  }
156
154
 
157
- function handleDeleteContact(contactId: string) {
158
- const result = deleteContact(contactId);
159
- if (result === "not_found") {
160
- throw new NotFoundError(`Contact "${contactId}" not found`);
161
- }
162
- if (result === "is_guardian") {
163
- throw new ForbiddenError("Cannot delete a guardian contact");
164
- }
165
- return null;
166
- }
167
-
168
155
  // ---------------------------------------------------------------------------
169
156
  // Invite handlers (transport-agnostic)
170
157
  // ---------------------------------------------------------------------------
@@ -502,18 +489,6 @@ export const ROUTES: RouteDefinition[] = [
502
489
  handler: ({ pathParams }: RouteHandlerArgs) =>
503
490
  handleGetContact(pathParams!.id),
504
491
  },
505
- {
506
- operationId: "deleteContact",
507
- endpoint: "contacts/:id",
508
- method: "DELETE",
509
- policyKey: "contacts",
510
- summary: "Delete a contact",
511
- description: "Delete a contact by ID.",
512
- tags: ["contacts"],
513
- responseStatus: "204",
514
- handler: ({ pathParams }: RouteHandlerArgs) =>
515
- handleDeleteContact(pathParams!.id),
516
- },
517
492
  {
518
493
  operationId: "upsert_contact",
519
494
  endpoint: "contacts",
@@ -22,7 +22,9 @@ import { z } from "zod";
22
22
 
23
23
  import {
24
24
  deepMergeOverwrite,
25
+ fillContextDefaultsForMissingKeys,
25
26
  getConfig,
27
+ getDeploymentContextDefaults,
26
28
  invalidateConfigCache,
27
29
  loadRawConfig,
28
30
  saveRawConfig,
@@ -312,9 +314,50 @@ async function handleSetEmbeddingConfig({ body }: RouteHandlerArgs) {
312
314
  }
313
315
  }
314
316
 
317
+ /**
318
+ * Apply deployment-context defaults to a raw config payload before it goes
319
+ * out over the wire from `GET /v1/config`. The in-memory `loadConfig()`
320
+ * already layers these defaults for daemon-internal consumers; the GET
321
+ * response needs the same treatment so external clients (macOS, web, CLI)
322
+ * see the effective value rather than `undefined` when the daemon hasn't
323
+ * persisted an explicit choice yet. Without this, macOS's
324
+ * `loadServiceModes(config:)` short-circuits when `services.inference.mode`
325
+ * is missing and falls back to the SwiftUI `@Published` default of
326
+ * "your-own", which renders the wrong segment selection on freshly-hatched
327
+ * platform-managed assistants.
328
+ *
329
+ * Guards against `loadRawConfig()` handing us a value that is technically
330
+ * valid JSON but not a plain object (e.g. literal `null`, a number, or an
331
+ * array). `loadRawConfig` is typed `Record<string, unknown>` but `JSON.parse`
332
+ * itself doesn't enforce that — a malformed-but-parseable `config.json`
333
+ * would blow up `fillContextDefaultsForMissingKeys` on its `target[key]` /
334
+ * `fileConfig[key]` accesses, turning `GET /v1/config` into a 500 where it
335
+ * used to succeed (returning the malformed payload as-is). When `raw` is
336
+ * not a plain object, we return it unchanged.
337
+ *
338
+ * Exported for direct unit testing.
339
+ */
340
+ export function applyContextDefaultsToRawConfig(raw: unknown): unknown {
341
+ const contextDefaults = getDeploymentContextDefaults();
342
+ if (
343
+ Object.keys(contextDefaults).length === 0 ||
344
+ raw === null ||
345
+ typeof raw !== "object" ||
346
+ Array.isArray(raw)
347
+ ) {
348
+ return raw;
349
+ }
350
+ fillContextDefaultsForMissingKeys(
351
+ raw as Record<string, unknown>,
352
+ raw as Record<string, unknown>,
353
+ contextDefaults,
354
+ );
355
+ return raw;
356
+ }
357
+
315
358
  function handleGetConfig() {
316
359
  try {
317
- return loadRawConfig();
360
+ return applyContextDefaultsToRawConfig(loadRawConfig());
318
361
  } catch (err) {
319
362
  const message = err instanceof Error ? err.message : String(err);
320
363
  throw new InternalError(`Failed to read config: ${message}`);
@@ -39,6 +39,7 @@ import {
39
39
  resolveSlash,
40
40
  } from "../../daemon/conversation-slash.js";
41
41
  import { getOrCreateConversation as getOrCreateConversationInstance } from "../../daemon/conversation-store.js";
42
+ import { canonicalizeTimeZone } from "../../daemon/date-context.js";
42
43
  import {
43
44
  getCannedFirstGreeting,
44
45
  isWakeUpGreeting,
@@ -86,6 +87,8 @@ import {
86
87
  getOrCreateConversation,
87
88
  } from "../../memory/conversation-key-store.js";
88
89
  import { searchConversations } from "../../memory/conversation-queries.js";
90
+ import { normalizeOnboardingContext } from "../../prompts/normalize-onboarding.js";
91
+ import { writeOnboardingSection } from "../../prompts/persona-resolver.js";
89
92
  import { getConfiguredProvider } from "../../providers/provider-send-message.js";
90
93
  import type { Provider } from "../../providers/types.js";
91
94
  import { checkIngressForSecrets } from "../../security/secret-ingress.js";
@@ -999,7 +1002,7 @@ function mergeConsecutiveAssistantMessages(messages: MessageRow[]): {
999
1002
  /**
1000
1003
  * Persist the pre-chat onboarding payload to disk.
1001
1004
  *
1002
- * Runs only on the very first message of a fresh conversation. Three
1005
+ * Runs only on the very first message of a fresh conversation. Four
1003
1006
  * artifacts are produced:
1004
1007
  *
1005
1008
  * 1. `data/onboarding-context.json` — sidecar read by the
@@ -1007,12 +1010,15 @@ function mergeConsecutiveAssistantMessages(messages: MessageRow[]): {
1007
1010
  * the pure-recomputation write cycle (every turn boundary rebuilds
1008
1011
  * facts from markdown; the sidecar is the durable source for the
1009
1012
  * tool/task/tone chips).
1010
- * 2. `IDENTITY.md` / `USER.md` persona seed files, only written
1011
- * when missing so we never clobber existing content. These feed
1012
- * the system prompt and the relationship-state writer's
1013
- * `parseIdentity` / `parseUserName` helpers after a daemon
1014
- * restart when the in-memory onboarding context is gone.
1015
- * 3. `data/relationship-state.json` kicked off fire-and-forget so
1013
+ * 2. `IDENTITY.md` assistant persona seed file, only written when
1014
+ * missing so we never clobber existing content. Feeds the system
1015
+ * prompt and the relationship-state writer's `parseIdentity`
1016
+ * helper after a daemon restart when the in-memory onboarding
1017
+ * context is gone.
1018
+ * 3. Onboarding section in the guardian persona file — written via
1019
+ * `writeOnboardingSection`, which handles the user's preferred
1020
+ * name (with fallback to root `USER.md`).
1021
+ * 4. `data/relationship-state.json` — kicked off fire-and-forget so
1016
1022
  * the Home page can populate immediately on first visit instead
1017
1023
  * of waiting for the first agent-turn boundary.
1018
1024
  *
@@ -1057,25 +1063,11 @@ export function persistOnboardingArtifacts(onboarding: {
1057
1063
  }
1058
1064
  }
1059
1065
 
1060
- const userName = onboarding.userName?.trim();
1061
- if (userName) {
1062
- const userPath = getWorkspacePromptPath("USER.md");
1063
- try {
1064
- if (existsSync(userPath)) {
1065
- const content = readFileSync(userPath, "utf-8");
1066
- const updated = content.replace(
1067
- /^- (?:\*\*)?Name:(?:\*\*)?\s*.*$/m,
1068
- () => `- **Name:** ${userName}`,
1069
- );
1070
- if (updated !== content) {
1071
- writeFileSync(userPath, updated, "utf-8");
1072
- }
1073
- } else {
1074
- writeFileSync(userPath, `# User\n\n- **Name:** ${userName}\n`, "utf-8");
1075
- }
1076
- } catch (err) {
1077
- log.warn({ err, userPath }, "Failed to seed USER.md from onboarding");
1078
- }
1066
+ try {
1067
+ const normalized = normalizeOnboardingContext(onboarding);
1068
+ writeOnboardingSection(normalized);
1069
+ } catch (err) {
1070
+ log.warn({ err }, "Failed to write onboarding section to persona file");
1079
1071
  }
1080
1072
 
1081
1073
  void writeRelationshipState().catch((err) => {
@@ -1104,6 +1096,7 @@ export async function handleSendMessage(
1104
1096
  bypassSecretCheck?: boolean;
1105
1097
  hostHomeDir?: string;
1106
1098
  hostUsername?: string;
1099
+ clientTimezone?: unknown;
1107
1100
  clientId?: string;
1108
1101
  clientMessageId?: string;
1109
1102
  inferenceProfile?: string | null;
@@ -1183,6 +1176,10 @@ export async function handleSendMessage(
1183
1176
  )}`,
1184
1177
  );
1185
1178
  }
1179
+ const clientTimezone =
1180
+ typeof body.clientTimezone === "string"
1181
+ ? (canonicalizeTimeZone(body.clientTimezone) ?? undefined)
1182
+ : undefined;
1186
1183
 
1187
1184
  // When conversationKey is omitted, derive a stable default from
1188
1185
  // sourceChannel + sourceInterface so that repeated calls from the same
@@ -1302,10 +1299,12 @@ export async function handleSendMessage(
1302
1299
  interfaceId: sourceInterface,
1303
1300
  hostHomeDir: body.hostHomeDir,
1304
1301
  hostUsername: body.hostUsername,
1302
+ ...(clientTimezone ? { clientTimezone } : {}),
1305
1303
  } satisfies HostProxyTransportMetadata)
1306
1304
  : ({
1307
1305
  channelId: sourceChannel,
1308
1306
  interfaceId: sourceInterface,
1307
+ ...(clientTimezone ? { clientTimezone } : {}),
1309
1308
  } satisfies NonHostProxyTransportMetadata);
1310
1309
 
1311
1310
  const conversation = await smDeps.getOrCreateConversation(
@@ -1991,6 +1990,13 @@ async function generateLlmSuggestion(
1991
1990
  // prefill-safe model. Keep `stop_sequences: ["</reply>"]` as an
1992
1991
  // early-termination hint; the parser below handles both tagged and
1993
1992
  // untagged responses so untagged "casual answer" replies still work.
1993
+ //
1994
+ // Force `thinking: disabled` + `effort: none` so the call works on any
1995
+ // user profile — including thinking-enabled profiles (Opus 4.x at
1996
+ // `effort: high|xhigh`, etc.) where Anthropic 400s on `temperature` ≠ 1
1997
+ // when thinking is enabled or in adaptive mode. A 60-token reply chip
1998
+ // doesn't benefit from extended thinking anyway, and burning thinking
1999
+ // tokens here would be wasteful.
1994
2000
  const response = await provider.sendMessage(
1995
2001
  [{ role: "user", content: [{ type: "text", text: userPrompt }] }],
1996
2002
  [], // no tools
@@ -2001,6 +2007,8 @@ async function generateLlmSuggestion(
2001
2007
  max_tokens: 60,
2002
2008
  stop_sequences: ["</reply>"],
2003
2009
  temperature: 0.7,
2010
+ thinking: { type: "disabled" },
2011
+ effort: "none",
2004
2012
  },
2005
2013
  },
2006
2014
  );
@@ -2274,6 +2282,7 @@ export const ROUTES: RouteDefinition[] = [
2274
2282
  .optional(),
2275
2283
  conversationType: z.string().optional(),
2276
2284
  slashCommand: z.string().optional(),
2285
+ clientTimezone: z.string().optional(),
2277
2286
  inferenceProfile: z.string().nullable().optional(),
2278
2287
  riskThreshold: z.enum(VALID_RISK_THRESHOLDS).optional(),
2279
2288
  }),