@vellumai/assistant 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (535) hide show
  1. package/ARCHITECTURE.md +32 -49
  2. package/Dockerfile +1 -0
  3. package/README.md +1 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
  5. package/bun.lock +26 -26
  6. package/docs/architecture/security.md +20 -0
  7. package/docs/plugins.md +7 -9
  8. package/knip.json +1 -0
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
  10. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +39 -1
  11. package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
  12. package/node_modules/@vellumai/service-contracts/package.json +2 -0
  13. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
  14. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
  15. package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
  16. package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
  17. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
  18. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +9 -0
  19. package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
  20. package/node_modules/@vellumai/twilio-client/package.json +18 -0
  21. package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
  22. package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
  23. package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
  24. package/openapi.yaml +565 -12
  25. package/package.json +6 -3
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
  27. package/src/__tests__/app-bundler.test.ts +170 -1
  28. package/src/__tests__/app-control-flow.test.ts +374 -0
  29. package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
  30. package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
  31. package/src/__tests__/app-executors.test.ts +30 -43
  32. package/src/__tests__/approval-routes-http.test.ts +23 -6
  33. package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
  34. package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
  35. package/src/__tests__/assistant-event-hub.test.ts +109 -2
  36. package/src/__tests__/assistant-event.test.ts +10 -0
  37. package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -2
  38. package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -7
  39. package/src/__tests__/background-shell-host-bash.test.ts +14 -15
  40. package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
  41. package/src/__tests__/btw-routes.test.ts +13 -4
  42. package/src/__tests__/call-controller.test.ts +49 -1
  43. package/src/__tests__/call-domain.test.ts +0 -2
  44. package/src/__tests__/call-routes-http.test.ts +0 -2
  45. package/src/__tests__/channel-readiness-service.test.ts +59 -1
  46. package/src/__tests__/checker.test.ts +3 -4
  47. package/src/__tests__/config-loader-backfill.test.ts +90 -155
  48. package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
  49. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  50. package/src/__tests__/config-set-platform-guard.test.ts +48 -4
  51. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
  54. package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
  55. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  56. package/src/__tests__/conversation-lifecycle.test.ts +36 -0
  57. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
  58. package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
  59. package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
  60. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  61. package/src/__tests__/conversation-slash-commands.test.ts +0 -4
  62. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -0
  64. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
  65. package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
  66. package/src/__tests__/credentials-cli.test.ts +5 -12
  67. package/src/__tests__/cu-unified-flow.test.ts +185 -23
  68. package/src/__tests__/daemon-credential-client.test.ts +101 -19
  69. package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
  70. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  71. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  72. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
  73. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
  74. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
  75. package/src/__tests__/heartbeat-service.test.ts +718 -1
  76. package/src/__tests__/helpers/call-route-handler.ts +7 -1
  77. package/src/__tests__/host-app-control-proxy.test.ts +602 -0
  78. package/src/__tests__/host-app-control-routes.test.ts +263 -0
  79. package/src/__tests__/host-bash-proxy.test.ts +246 -47
  80. package/src/__tests__/host-bash-routes.test.ts +294 -0
  81. package/src/__tests__/host-browser-proxy.test.ts +24 -22
  82. package/src/__tests__/host-browser-routes.test.ts +39 -13
  83. package/src/__tests__/host-cu-proxy.test.ts +41 -52
  84. package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
  85. package/src/__tests__/host-file-edit-tool.test.ts +47 -1
  86. package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
  87. package/src/__tests__/host-file-proxy.test.ts +37 -43
  88. package/src/__tests__/host-file-read-tool.test.ts +17 -0
  89. package/src/__tests__/host-file-routes-targeted.test.ts +262 -0
  90. package/src/__tests__/host-file-write-tool.test.ts +42 -1
  91. package/src/__tests__/host-proxy-base.test.ts +312 -0
  92. package/src/__tests__/host-shell-tool.test.ts +22 -4
  93. package/src/__tests__/host-transfer-proxy-targeted.test.ts +583 -0
  94. package/src/__tests__/host-transfer-proxy.test.ts +121 -22
  95. package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
  96. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  97. package/src/__tests__/identity-intro-cache.test.ts +29 -0
  98. package/src/__tests__/identity-routes.test.ts +103 -1
  99. package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
  100. package/src/__tests__/inline-command-runner.test.ts +0 -1
  101. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
  102. package/src/__tests__/integration-status.test.ts +85 -5
  103. package/src/__tests__/intent-routing.test.ts +0 -1
  104. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
  105. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
  106. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  107. package/src/__tests__/mcp-auth-routes.test.ts +197 -0
  108. package/src/__tests__/mcp-cli.test.ts +338 -2
  109. package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
  110. package/src/__tests__/migration-import-commit-http.test.ts +108 -2
  111. package/src/__tests__/mock-gateway-ipc.ts +1 -0
  112. package/src/__tests__/oauth-cli.test.ts +0 -2
  113. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
  114. package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
  115. package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
  116. package/src/__tests__/prechat-onboarding-contract.test.ts +3 -1
  117. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  118. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  119. package/src/__tests__/public-ingress-urls.test.ts +97 -0
  120. package/src/__tests__/require-fresh-approval.test.ts +0 -1
  121. package/src/__tests__/retry-backoff.test.ts +87 -0
  122. package/src/__tests__/runtime-events-sse.test.ts +10 -6
  123. package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
  124. package/src/__tests__/schedule-retry.test.ts +715 -0
  125. package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
  126. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  127. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  128. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  129. package/src/__tests__/skill-feature-flags.test.ts +43 -41
  130. package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
  131. package/src/__tests__/skill-load-inline-command.test.ts +0 -51
  132. package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
  133. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  134. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  135. package/src/__tests__/slack-channel-config.test.ts +9 -14
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
  137. package/src/__tests__/system-prompt.test.ts +0 -1
  138. package/src/__tests__/telegram-config.test.ts +0 -1
  139. package/src/__tests__/test-preload.ts +8 -0
  140. package/src/__tests__/tool-approval-handler.test.ts +3 -4
  141. package/src/__tests__/tool-audit-listener.test.ts +48 -0
  142. package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
  143. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  145. package/src/__tests__/tool-executor.test.ts +0 -1
  146. package/src/__tests__/twilio-config.test.ts +3 -16
  147. package/src/__tests__/twilio-routes.test.ts +3 -5
  148. package/src/__tests__/twilio-validation.test.ts +93 -0
  149. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
  150. package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
  151. package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
  152. package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
  153. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
  154. package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
  155. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
  156. package/src/backup/__tests__/paths.test.ts +0 -22
  157. package/src/backup/__tests__/restore.test.ts +51 -151
  158. package/src/backup/paths.ts +2 -18
  159. package/src/backup/restore.ts +107 -231
  160. package/src/bundler/app-bundler.ts +51 -3
  161. package/src/calls/relay-server.ts +4 -44
  162. package/src/calls/twilio-config.ts +2 -17
  163. package/src/calls/twilio-rest.ts +33 -105
  164. package/src/calls/twilio-routes.ts +11 -12
  165. package/src/channels/types.ts +8 -7
  166. package/src/cli/commands/__tests__/backup.test.ts +6 -277
  167. package/src/cli/commands/__tests__/gateway.test.ts +288 -0
  168. package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
  169. package/src/cli/commands/__tests__/webhooks.test.ts +0 -1
  170. package/src/cli/commands/backup.ts +6 -331
  171. package/src/cli/commands/clients.ts +36 -37
  172. package/src/cli/commands/contacts.ts +73 -0
  173. package/src/cli/commands/conversations.ts +2 -5
  174. package/src/cli/commands/credentials.ts +15 -7
  175. package/src/cli/commands/domain.ts +66 -15
  176. package/src/cli/commands/gateway.ts +183 -0
  177. package/src/cli/commands/keys.ts +9 -6
  178. package/src/cli/commands/mcp.ts +116 -156
  179. package/src/cli/commands/memory-v2.ts +296 -1
  180. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  181. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
  182. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
  183. package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
  184. package/src/cli/commands/platform/disconnect.ts +5 -4
  185. package/src/cli/commands/platform/index.ts +0 -18
  186. package/src/cli/lib/daemon-credential-client.ts +110 -28
  187. package/src/cli/program.ts +2 -0
  188. package/src/config/assistant-feature-flags.ts +67 -10
  189. package/src/config/bundled-skills/acp/SKILL.md +6 -0
  190. package/src/config/bundled-skills/acp/TOOLS.json +1 -22
  191. package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
  192. package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
  193. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
  194. package/src/config/bundled-skills/app-control/SKILL.md +75 -0
  195. package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
  196. package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
  197. package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
  198. package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
  199. package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
  200. package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
  201. package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
  202. package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
  203. package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
  204. package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
  205. package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
  206. package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
  207. package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
  208. package/src/config/bundled-skills/document/TOOLS.json +0 -8
  209. package/src/config/bundled-skills/followups/TOOLS.json +0 -12
  210. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  211. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
  212. package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
  213. package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
  214. package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
  215. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
  216. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
  217. package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
  218. package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
  219. package/src/config/bundled-skills/settings/SKILL.md +4 -0
  220. package/src/config/bundled-skills/settings/TOOLS.json +0 -12
  221. package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
  222. package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
  223. package/src/config/bundled-skills/subagent/SKILL.md +6 -2
  224. package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
  225. package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
  226. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
  227. package/src/config/bundled-tool-registry.ts +21 -0
  228. package/src/config/env-registry.ts +0 -2
  229. package/src/config/env.ts +19 -12
  230. package/src/config/feature-flag-registry.json +21 -133
  231. package/src/config/loader.ts +73 -99
  232. package/src/config/sanitize-for-transfer.ts +2 -0
  233. package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
  234. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -4
  235. package/src/config/schemas/calls.ts +0 -9
  236. package/src/config/schemas/heartbeat.ts +63 -0
  237. package/src/config/schemas/ingress.ts +10 -6
  238. package/src/config/schemas/llm.ts +5 -10
  239. package/src/config/schemas/memory-lifecycle.ts +77 -24
  240. package/src/config/schemas/memory-v2.ts +48 -4
  241. package/src/config/schemas/platform.ts +6 -0
  242. package/src/config/schemas/services.ts +1 -15
  243. package/src/config/schemas/skills.ts +0 -6
  244. package/src/config/seed-inference-profiles.ts +1 -1
  245. package/src/contacts/contact-store.ts +0 -30
  246. package/src/contacts/contacts-write.ts +0 -27
  247. package/src/context/window-manager.ts +1 -2
  248. package/src/credential-execution/feature-gates.ts +10 -10
  249. package/src/credential-execution/process-manager.ts +12 -41
  250. package/src/daemon/__tests__/conversation-tool-setup.test.ts +126 -5
  251. package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
  252. package/src/daemon/config-watcher.ts +4 -3
  253. package/src/daemon/conversation-agent-loop-handlers.ts +21 -3
  254. package/src/daemon/conversation-agent-loop.ts +32 -28
  255. package/src/daemon/conversation-lifecycle.ts +8 -1
  256. package/src/daemon/conversation-process.ts +16 -11
  257. package/src/daemon/conversation-runtime-assembly.ts +2 -2
  258. package/src/daemon/conversation-surfaces.ts +125 -4
  259. package/src/daemon/conversation-tool-setup.ts +16 -55
  260. package/src/daemon/conversation.ts +21 -2
  261. package/src/daemon/doordash-steps.ts +1 -1
  262. package/src/daemon/handlers/shared.ts +4 -1
  263. package/src/daemon/host-app-control-proxy.ts +293 -0
  264. package/src/daemon/host-bash-proxy.ts +84 -74
  265. package/src/daemon/host-browser-proxy.ts +67 -82
  266. package/src/daemon/host-cu-proxy.ts +81 -86
  267. package/src/daemon/host-file-proxy.ts +93 -69
  268. package/src/daemon/host-proxy-base.ts +294 -0
  269. package/src/daemon/host-proxy-preactivation.ts +82 -0
  270. package/src/daemon/host-transfer-proxy.ts +247 -129
  271. package/src/daemon/lifecycle.ts +115 -117
  272. package/src/daemon/message-protocol.ts +3 -8
  273. package/src/daemon/message-types/contacts.ts +23 -1
  274. package/src/daemon/message-types/conversations.ts +11 -8
  275. package/src/daemon/message-types/host-app-control.ts +150 -0
  276. package/src/daemon/message-types/host-bash.ts +4 -0
  277. package/src/daemon/message-types/host-cu.ts +2 -0
  278. package/src/daemon/message-types/host-file.ts +4 -0
  279. package/src/daemon/message-types/host-transfer.ts +3 -0
  280. package/src/daemon/message-types/schedules.ts +8 -3
  281. package/src/daemon/message-types/skills.ts +2 -2
  282. package/src/daemon/process-message.ts +18 -1
  283. package/src/daemon/shutdown-handlers.ts +0 -3
  284. package/src/daemon/tool-setup-types.ts +51 -0
  285. package/src/daemon/tool-side-effects.ts +1 -1
  286. package/src/events/tool-audit-listener.ts +2 -1
  287. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +15 -7
  288. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
  289. package/src/heartbeat/heartbeat-run-store.ts +236 -0
  290. package/src/heartbeat/heartbeat-service.ts +280 -49
  291. package/src/home/__tests__/post-connect-feed.test.ts +99 -0
  292. package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
  293. package/src/home/__tests__/suggested-prompts.test.ts +89 -0
  294. package/src/home/post-connect-feed.ts +68 -0
  295. package/src/home/relationship-state-writer.ts +17 -92
  296. package/src/home/suggested-prompts.ts +46 -10
  297. package/src/inbound/public-ingress-urls.ts +32 -34
  298. package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
  299. package/src/ipc/assistant-server.ts +14 -1
  300. package/src/ipc/cli-client.ts +32 -1
  301. package/src/live-voice/live-voice-metrics.ts +10 -10
  302. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
  303. package/src/mcp/mcp-auth-orchestrator.ts +213 -0
  304. package/src/mcp/mcp-auth-state.ts +133 -0
  305. package/src/mcp/mcp-oauth-provider.ts +19 -0
  306. package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
  307. package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
  308. package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
  309. package/src/memory/anisotropy.test.ts +247 -0
  310. package/src/memory/anisotropy.ts +443 -0
  311. package/src/memory/auto-analysis-constants.ts +17 -0
  312. package/src/memory/auto-analysis-guard.ts +5 -15
  313. package/src/memory/canonical-guardian-store.ts +7 -7
  314. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
  315. package/src/memory/context-search/agent-protocol.ts +6 -6
  316. package/src/memory/context-search/agent-runner.ts +32 -7
  317. package/src/memory/context-search/sources/memory-v2.ts +17 -5
  318. package/src/memory/conversation-crud.ts +1 -1
  319. package/src/memory/conversation-key-store.ts +2 -15
  320. package/src/memory/db-init.ts +4 -0
  321. package/src/memory/embedding-backend.ts +9 -21
  322. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -4
  323. package/src/memory/graph/conversation-graph-memory.ts +1 -24
  324. package/src/memory/graph/graph-search.ts +8 -0
  325. package/src/memory/graph/retriever.ts +28 -0
  326. package/src/memory/graph/tools.ts +1 -1
  327. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
  328. package/src/memory/jobs/embed-concept-page.ts +28 -2
  329. package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
  330. package/src/memory/jobs-store.ts +66 -22
  331. package/src/memory/jobs-worker.ts +112 -63
  332. package/src/memory/memory-v2-activation-log-store.ts +1 -1
  333. package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
  334. package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
  335. package/src/memory/migrations/index.ts +5 -0
  336. package/src/memory/migrations/registry.ts +8 -0
  337. package/src/memory/pkb/pkb-search.ts +7 -0
  338. package/src/memory/qdrant-client.ts +50 -20
  339. package/src/memory/schema/infrastructure.ts +15 -0
  340. package/src/memory/search/semantic.ts +7 -0
  341. package/src/memory/sparse-tokenize.ts +49 -0
  342. package/src/memory/v2/__tests__/activation.test.ts +77 -95
  343. package/src/memory/v2/__tests__/injection.test.ts +43 -21
  344. package/src/memory/v2/__tests__/sim.test.ts +166 -6
  345. package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
  346. package/src/memory/v2/__tests__/static-context.test.ts +0 -1
  347. package/src/memory/v2/activation.ts +69 -88
  348. package/src/memory/v2/consolidation-job.ts +3 -5
  349. package/src/memory/v2/constants.ts +7 -0
  350. package/src/memory/v2/injection.ts +86 -53
  351. package/src/memory/v2/prompts/consolidation.ts +312 -91
  352. package/src/memory/v2/qdrant.ts +99 -1
  353. package/src/memory/v2/sim.ts +126 -16
  354. package/src/memory/v2/skill-qdrant.ts +12 -3
  355. package/src/memory/v2/skill-store.ts +16 -1
  356. package/src/memory/v2/sparse-bm25.ts +245 -0
  357. package/src/memory/v2/static-context.ts +6 -5
  358. package/src/messaging/providers/gmail/types.ts +0 -49
  359. package/src/messaging/providers/slack/adapter.ts +1 -31
  360. package/src/messaging/providers/slack/types.ts +0 -32
  361. package/src/notifications/README.md +10 -10
  362. package/src/notifications/broadcaster.ts +1 -1
  363. package/src/notifications/guardian-question-mode.ts +5 -5
  364. package/src/oauth/connect-orchestrator.ts +4 -0
  365. package/src/oauth/credential-token-resolver.ts +1 -3
  366. package/src/oauth/manual-token-connection.ts +0 -4
  367. package/src/outbound-proxy/index.ts +1 -37
  368. package/src/outbound-proxy/logging.ts +1 -1
  369. package/src/outbound-proxy/policy.ts +6 -5
  370. package/src/outbound-proxy/router.ts +2 -1
  371. package/src/permissions/approval-policy.test.ts +6 -275
  372. package/src/permissions/approval-policy.ts +0 -51
  373. package/src/permissions/checker.test.ts +0 -1
  374. package/src/permissions/checker.ts +3 -17
  375. package/src/permissions/gateway-threshold-reader.ts +2 -0
  376. package/src/permissions/prompter.ts +34 -1
  377. package/src/permissions/secret-prompter.ts +6 -2
  378. package/src/prompts/bootstrap-cleanup.ts +27 -0
  379. package/src/prompts/system-prompt.ts +3 -18
  380. package/src/prompts/templates/SOUL.md +13 -1
  381. package/src/providers/speech-to-text/provider-catalog.ts +7 -8
  382. package/src/runtime/assistant-event-hub.ts +118 -96
  383. package/src/runtime/assistant-event.ts +1 -0
  384. package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
  385. package/src/runtime/auth/middleware.ts +0 -96
  386. package/src/runtime/auth/route-policy.ts +19 -0
  387. package/src/runtime/btw-sidechain.ts +2 -3
  388. package/src/runtime/channel-invite-transport.ts +2 -48
  389. package/src/runtime/channel-invite-transports/email.ts +1 -1
  390. package/src/runtime/channel-invite-transports/slack.ts +1 -1
  391. package/src/runtime/channel-invite-transports/telegram.ts +1 -1
  392. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  393. package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
  394. package/src/runtime/channel-invite-types.ts +54 -0
  395. package/src/runtime/channel-readiness-service.ts +32 -13
  396. package/src/runtime/http-server.ts +3 -329
  397. package/src/runtime/http-types.ts +0 -5
  398. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
  399. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
  400. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
  401. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
  402. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
  403. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  404. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
  405. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
  406. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
  407. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
  408. package/src/runtime/migrations/migration-transport.ts +7 -7
  409. package/src/runtime/migrations/vbundle-builder.ts +327 -60
  410. package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
  411. package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
  412. package/src/runtime/migrations/vbundle-importer.ts +245 -68
  413. package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
  414. package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
  415. package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
  416. package/src/runtime/migrations/vbundle-validator.ts +114 -0
  417. package/src/runtime/pending-interactions.ts +35 -9
  418. package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
  419. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  420. package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
  421. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
  422. package/src/runtime/routes/approval-interception-types.ts +13 -0
  423. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
  424. package/src/runtime/routes/backup-routes.ts +15 -38
  425. package/src/runtime/routes/btw-routes.ts +14 -37
  426. package/src/runtime/routes/client-routes.ts +1 -0
  427. package/src/runtime/routes/contact-prompt-routes.ts +183 -0
  428. package/src/runtime/routes/conversation-query-routes.ts +36 -1
  429. package/src/runtime/routes/conversation-routes.ts +30 -13
  430. package/src/runtime/routes/document-pdf-renderer.ts +165 -0
  431. package/src/runtime/routes/documents-routes.ts +30 -0
  432. package/src/runtime/routes/errors.ts +19 -4
  433. package/src/runtime/routes/events-routes.ts +12 -6
  434. package/src/runtime/routes/gateway-log-routes.ts +79 -0
  435. package/src/runtime/routes/guardian-approval-interception.ts +2 -8
  436. package/src/runtime/routes/heartbeat-routes.ts +103 -38
  437. package/src/runtime/routes/host-app-control-routes.ts +134 -0
  438. package/src/runtime/routes/host-bash-routes.ts +36 -6
  439. package/src/runtime/routes/host-browser-routes.ts +108 -13
  440. package/src/runtime/routes/host-cu-routes.ts +44 -14
  441. package/src/runtime/routes/host-file-routes.ts +33 -10
  442. package/src/runtime/routes/host-transfer-routes.ts +64 -24
  443. package/src/runtime/routes/http-adapter.ts +1 -0
  444. package/src/runtime/routes/identity-intro-cache.ts +30 -0
  445. package/src/runtime/routes/identity-routes.ts +15 -43
  446. package/src/runtime/routes/inbound-message-handler.ts +1 -9
  447. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
  448. package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
  449. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
  450. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
  451. package/src/runtime/routes/index.ts +8 -0
  452. package/src/runtime/routes/mcp-auth-routes.ts +132 -0
  453. package/src/runtime/routes/memory-item-routes.ts +10 -12
  454. package/src/runtime/routes/memory-v2-routes.ts +441 -1
  455. package/src/runtime/routes/migration-routes.ts +96 -0
  456. package/src/runtime/routes/schedule-routes.ts +7 -0
  457. package/src/runtime/verification-templates.ts +4 -7
  458. package/src/schedule/integration-status.ts +66 -2
  459. package/src/schedule/recurrence-engine.ts +4 -1
  460. package/src/schedule/retry-backoff.ts +18 -0
  461. package/src/schedule/retry-policy.ts +82 -0
  462. package/src/schedule/schedule-recovery.ts +64 -0
  463. package/src/schedule/schedule-store.ts +106 -2
  464. package/src/schedule/scheduler-types.ts +25 -0
  465. package/src/schedule/scheduler.ts +63 -38
  466. package/src/security/oauth-callback-registry.ts +8 -0
  467. package/src/sequence/analytics.ts +5 -5
  468. package/src/sequence/engine.ts +1 -1
  469. package/src/skills/catalog-files.ts +2 -8
  470. package/src/skills/include-graph.ts +5 -5
  471. package/src/skills/remote-skill-policy.ts +5 -5
  472. package/src/skills/skill-file-provider.ts +1 -1
  473. package/src/skills/skill-file-types.ts +13 -0
  474. package/src/skills/skillssh-audit-types.ts +28 -0
  475. package/src/skills/skillssh-registry.ts +8 -21
  476. package/src/telemetry/types.ts +2 -0
  477. package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
  478. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  479. package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
  480. package/src/tools/apps/executors.ts +56 -69
  481. package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
  482. package/src/tools/browser/browser-execution.ts +2 -2
  483. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
  484. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
  485. package/src/tools/browser/cdp-client/factory.ts +23 -24
  486. package/src/tools/browser/cdp-client/index.ts +1 -14
  487. package/src/tools/computer-use/definitions.ts +42 -20
  488. package/src/tools/executor.ts +2 -0
  489. package/src/tools/host-filesystem/edit.ts +26 -0
  490. package/src/tools/host-filesystem/read.ts +26 -0
  491. package/src/tools/host-filesystem/transfer.ts +31 -1
  492. package/src/tools/host-filesystem/write.ts +26 -0
  493. package/src/tools/host-terminal/host-shell.ts +58 -0
  494. package/src/tools/schedule/create.ts +6 -0
  495. package/src/tools/schedule/list.ts +2 -0
  496. package/src/tools/schedule/update.ts +10 -0
  497. package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
  498. package/src/tools/shared/filesystem/path-policy.ts +25 -1
  499. package/src/tools/skills/load.ts +0 -32
  500. package/src/tools/tool-approval-handler.ts +1 -5
  501. package/src/tools/types.ts +4 -0
  502. package/src/usage/pricing.ts +1 -1
  503. package/src/workspace/hatched-date.ts +86 -0
  504. package/src/workspace/migrations/003-seed-device-id.ts +1 -1
  505. package/src/workspace/migrations/006-services-config.ts +8 -5
  506. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
  507. package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
  508. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
  509. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
  510. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
  511. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
  512. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
  513. package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
  514. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
  515. package/src/workspace/migrations/AGENTS.md +1 -1
  516. package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
  517. package/src/workspace/migrations/utils.ts +21 -0
  518. package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
  519. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
  520. package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
  521. package/src/__tests__/twilio-rest.test.ts +0 -34
  522. package/src/backup/__tests__/backup-key.test.ts +0 -152
  523. package/src/backup/__tests__/backup-worker.test.ts +0 -782
  524. package/src/backup/__tests__/offsite-writer.test.ts +0 -641
  525. package/src/backup/__tests__/stream-crypt.test.ts +0 -228
  526. package/src/backup/backup-key.ts +0 -137
  527. package/src/backup/backup-worker.ts +0 -472
  528. package/src/backup/offsite-writer.ts +0 -222
  529. package/src/backup/stream-crypt.ts +0 -263
  530. package/src/daemon/message-types/pairing.ts +0 -58
  531. package/src/outbound-proxy/config.ts +0 -20
  532. package/src/outbound-proxy/health.ts +0 -18
  533. package/src/outbound-proxy/types.ts +0 -150
  534. package/src/runtime/capability-tokens.ts +0 -190
  535. package/src/signals/mcp-reload.ts +0 -18
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  existsSync,
3
3
  mkdirSync,
4
+ readdirSync,
4
5
  readFileSync,
5
6
  rmSync,
6
7
  writeFileSync,
@@ -64,8 +65,8 @@ afterAll(() => {
64
65
  });
65
66
 
66
67
  import {
67
- deepMergeMissing,
68
68
  deepMergeOverwrite,
69
+ getConfig,
69
70
  invalidateConfigCache,
70
71
  loadConfig,
71
72
  } from "../config/loader.js";
@@ -79,73 +80,11 @@ function writeConfig(obj: unknown): void {
79
80
  writeFileSync(CONFIG_PATH, JSON.stringify(obj, null, 2) + "\n");
80
81
  }
81
82
 
82
- function readConfig(): Record<string, unknown> {
83
- return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
84
- }
85
-
86
- // ---------------------------------------------------------------------------
87
- // Tests: deepMergeMissing (unit)
88
- // ---------------------------------------------------------------------------
89
-
90
- describe("deepMergeMissing", () => {
91
- test("adds missing top-level keys", () => {
92
- const target: Record<string, unknown> = { a: 1 };
93
- const defaults: Record<string, unknown> = { a: 99, b: 2 };
94
- const changed = deepMergeMissing(target, defaults);
95
- expect(changed).toBe(true);
96
- expect(target).toEqual({ a: 1, b: 2 });
97
- });
98
-
99
- test("does not overwrite existing values", () => {
100
- const target: Record<string, unknown> = { a: 1, b: "user" };
101
- const defaults: Record<string, unknown> = { a: 99, b: "default" };
102
- const changed = deepMergeMissing(target, defaults);
103
- expect(changed).toBe(false);
104
- expect(target).toEqual({ a: 1, b: "user" });
105
- });
106
-
107
- test("recursively fills nested objects", () => {
108
- const target: Record<string, unknown> = {
109
- nested: { existingKey: "keep" },
110
- };
111
- const defaults: Record<string, unknown> = {
112
- nested: { existingKey: "default", newKey: 42 },
113
- };
114
- const changed = deepMergeMissing(target, defaults);
115
- expect(changed).toBe(true);
116
- expect(target).toEqual({
117
- nested: { existingKey: "keep", newKey: 42 },
118
- });
119
- });
120
-
121
- test("returns false when no changes needed", () => {
122
- const target: Record<string, unknown> = { a: 1, b: { c: 3 } };
123
- const defaults: Record<string, unknown> = { a: 99, b: { c: 100 } };
124
- const changed = deepMergeMissing(target, defaults);
125
- expect(changed).toBe(false);
126
- });
127
-
128
- test("does not merge arrays", () => {
129
- const target: Record<string, unknown> = { items: [1, 2] };
130
- const defaults: Record<string, unknown> = { items: [3, 4, 5] };
131
- const changed = deepMergeMissing(target, defaults);
132
- expect(changed).toBe(false);
133
- expect(target).toEqual({ items: [1, 2] });
134
- });
135
-
136
- test("adds entire missing nested section", () => {
137
- const target: Record<string, unknown> = {};
138
- const defaults: Record<string, unknown> = {
139
- slack: { deliverAuthBypass: false },
140
- };
141
- const changed = deepMergeMissing(target, defaults);
142
- expect(changed).toBe(true);
143
- expect(target).toEqual({ slack: { deliverAuthBypass: false } });
144
- });
145
- });
146
-
147
83
  // ---------------------------------------------------------------------------
148
84
  // Tests: deepMergeOverwrite (unit) — JSON-null-as-deletion semantics
85
+ //
86
+ // `deepMergeOverwrite` is used by `mergeDefaultWorkspaceConfig` and platform
87
+ // override paths.
149
88
  // ---------------------------------------------------------------------------
150
89
 
151
90
  describe("deepMergeOverwrite", () => {
@@ -275,7 +214,11 @@ describe("deepMergeOverwrite", () => {
275
214
  heartbeat: { activeHoursStart: null, activeHoursEnd: null },
276
215
  });
277
216
  expect(target).toEqual({
278
- heartbeat: { intervalMs: 6000, activeHoursStart: null, activeHoursEnd: null },
217
+ heartbeat: {
218
+ intervalMs: 6000,
219
+ activeHoursStart: null,
220
+ activeHoursEnd: null,
221
+ },
279
222
  });
280
223
  });
281
224
 
@@ -318,10 +261,15 @@ describe("deepMergeOverwrite", () => {
318
261
  });
319
262
 
320
263
  // ---------------------------------------------------------------------------
321
- // Tests: startup backfill integration
264
+ // Tests: loadConfig() startup behavior
265
+ //
266
+ // Contract: disk = user intent, in-memory cache = effective values. loadConfig
267
+ // must NOT silently materialize schema defaults into config.json on load.
268
+ // The legitimate self-healing paths that DO rewrite the file (deprecated-key
269
+ // strip, fresh-config seed, corrupt-JSON quarantine) are protected below.
322
270
  // ---------------------------------------------------------------------------
323
271
 
324
- describe("config loader backfill", () => {
272
+ describe("loadConfig startup behavior", () => {
325
273
  beforeEach(() => {
326
274
  ensureTestDir();
327
275
  const resetPaths = [
@@ -335,6 +283,16 @@ describe("config loader backfill", () => {
335
283
  rmSync(path, { recursive: true, force: true });
336
284
  }
337
285
  }
286
+ // Also clear any leftover quarantine files from previous test runs.
287
+ if (existsSync(WORKSPACE_DIR)) {
288
+ for (const entry of readdirSync(WORKSPACE_DIR)) {
289
+ if (entry.startsWith("config.json.corrupt-")) {
290
+ rmSync(join(WORKSPACE_DIR, entry), { force: true });
291
+ }
292
+ }
293
+ }
294
+ const updatesPath = join(WORKSPACE_DIR, "UPDATES.md");
295
+ if (existsSync(updatesPath)) rmSync(updatesPath, { force: true });
338
296
  ensureTestDir();
339
297
  _setStorePath(join(WORKSPACE_DIR, "keys.enc"));
340
298
  invalidateConfigCache();
@@ -345,119 +303,96 @@ describe("config loader backfill", () => {
345
303
  invalidateConfigCache();
346
304
  });
347
305
 
348
- test("backfills missing schema keys into existing config.json", () => {
349
- // Write a minimal config that is missing many sections
350
- writeConfig({ provider: "anthropic", model: "claude-opus-4-6" });
306
+ test("does not modify existing config.json on load", () => {
307
+ // Write a partial config and confirm the file's bytes are unchanged
308
+ // after loadConfig(). Schema defaults must apply in-memory only; disk
309
+ // is the user's source of truth.
310
+ writeConfig({ provider: "anthropic" });
311
+ const before = readFileSync(CONFIG_PATH);
351
312
 
352
313
  loadConfig();
353
314
 
354
- // Re-read the file from disk — it should have been backfilled
355
- const raw = readConfig();
356
- // New fields from this PR should be present
357
- expect(raw.telegram).toBeDefined();
358
- expect((raw.telegram as Record<string, unknown>).apiBaseUrl).toBe(
359
- "https://api.telegram.org",
360
- );
361
- expect((raw.telegram as Record<string, unknown>).deliverAuthBypass).toBe(
362
- false,
363
- );
364
- expect((raw.telegram as Record<string, unknown>).timeoutMs).toBe(15_000);
365
- expect((raw.telegram as Record<string, unknown>).maxRetries).toBe(3);
366
- expect((raw.telegram as Record<string, unknown>).initialBackoffMs).toBe(
367
- 1_000,
368
- );
315
+ const after = readFileSync(CONFIG_PATH);
316
+ expect(after.equals(before)).toBe(true);
317
+ });
369
318
 
370
- expect(raw.whatsapp).toBeDefined();
371
- expect((raw.whatsapp as Record<string, unknown>).deliverAuthBypass).toBe(
372
- false,
373
- );
374
- expect((raw.whatsapp as Record<string, unknown>).timeoutMs).toBe(15_000);
375
- expect((raw.whatsapp as Record<string, unknown>).maxRetries).toBe(3);
376
- expect((raw.whatsapp as Record<string, unknown>).initialBackoffMs).toBe(
377
- 1_000,
378
- );
319
+ test("getConfig().memory.v2.bm25_b returns schema default when absent on disk", () => {
320
+ // Consumer-side correctness: even though loadConfig no longer writes
321
+ // schema defaults back to disk, accessors still see them via the
322
+ // in-memory `cached: AssistantConfig` populated by `applyNestedDefaults`.
323
+ writeConfig({ provider: "anthropic" });
379
324
 
380
- expect(raw.slack).toBeDefined();
381
- expect((raw.slack as Record<string, unknown>).deliverAuthBypass).toBe(
382
- false,
383
- );
325
+ const config = getConfig();
326
+
327
+ expect(config.memory.v2.bm25_b).toBe(0.4);
384
328
  });
385
329
 
386
- test("preserves existing user-defined values during backfill", () => {
330
+ test("still strips deprecated fields and rewrites", () => {
331
+ // `warnAndStripDeprecatedFields` is a legitimate self-healing path:
332
+ // it removes fields the schema no longer recognizes and persists the
333
+ // cleaned config so the deprecation warning fires only once.
387
334
  writeConfig({
388
- services: {
389
- inference: { provider: "openai", model: "gpt-4" },
390
- },
391
- telegram: { botUsername: "mybot", timeoutMs: 30_000 },
392
- whatsapp: { phoneNumber: "+1234567890" },
335
+ provider: "anthropic",
336
+ rateLimit: { maxTokensPerSession: 100_000 },
393
337
  });
394
338
 
395
339
  loadConfig();
396
340
 
397
- const raw = readConfig();
398
- // User values preserved
399
- const services = raw.services as Record<string, Record<string, unknown>>;
400
- expect(services.inference.provider).toBe("openai");
401
- expect(services.inference.model).toBe("gpt-4");
402
- expect((raw.telegram as Record<string, unknown>).botUsername).toBe("mybot");
403
- expect((raw.telegram as Record<string, unknown>).timeoutMs).toBe(30_000);
404
- expect((raw.whatsapp as Record<string, unknown>).phoneNumber).toBe(
405
- "+1234567890",
406
- );
407
-
408
- // Missing fields backfilled
409
- expect((raw.telegram as Record<string, unknown>).apiBaseUrl).toBe(
410
- "https://api.telegram.org",
411
- );
412
- expect((raw.telegram as Record<string, unknown>).deliverAuthBypass).toBe(
413
- false,
414
- );
415
- expect((raw.whatsapp as Record<string, unknown>).deliverAuthBypass).toBe(
416
- false,
417
- );
341
+ const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
342
+ expect(raw.rateLimit?.maxTokensPerSession).toBeUndefined();
343
+ // Other rateLimit keys are not affected — only the deprecated entry is stripped
344
+ expect(raw.provider).toBe("anthropic");
418
345
  });
419
346
 
420
- test("does not rewrite config.json when no effective change exists", () => {
421
- // First load: creates config from scratch with all defaults
422
- loadConfig();
423
- invalidateConfigCache();
424
-
425
- // Read file and record its content
426
- const contentBefore = readFileSync(CONFIG_PATH, "utf-8");
347
+ test("strips memory.jobs.batchSize from existing user configs", () => {
348
+ // Pre-PR-#29364, the memory job worker read `memory.jobs.batchSize` to
349
+ // size its single claim batch. The per-lane scheduler no longer reads
350
+ // it, so the field is deprecated. Existing configs that have it
351
+ // written to disk should load cleanly with the field silently stripped.
352
+ writeConfig({
353
+ provider: "anthropic",
354
+ memory: { jobs: { batchSize: 25, workerConcurrency: 4 } },
355
+ });
427
356
 
428
- // Second load: file already has all keys — no write expected
429
357
  loadConfig();
430
358
 
431
- const contentAfter = readFileSync(CONFIG_PATH, "utf-8");
432
- expect(contentAfter).toBe(contentBefore);
359
+ const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
360
+ expect(raw.memory?.jobs?.batchSize).toBeUndefined();
361
+ // Sibling fields under memory.jobs are preserved
362
+ expect(raw.memory?.jobs?.workerConcurrency).toBe(4);
433
363
  });
434
364
 
435
- test("does not write dataDir during backfill", () => {
436
- writeConfig({ provider: "anthropic" });
365
+ test("still writes a default config on first launch when file is absent", () => {
366
+ // Discoverability: when no config.json exists, write one populated with
367
+ // all schema defaults so users can see and edit available options.
368
+ expect(existsSync(CONFIG_PATH)).toBe(false);
437
369
 
438
370
  loadConfig();
439
371
 
440
- const raw = readConfig();
372
+ expect(existsSync(CONFIG_PATH)).toBe(true);
373
+ const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
374
+ // Sanity: schema-defaulted nested fields are materialized
375
+ expect(raw.memory?.v2?.bm25_b).toBe(0.4);
441
376
  expect(raw.dataDir).toBeUndefined();
442
377
  });
443
378
 
444
- test("backfills new nested fields into existing sections", () => {
445
- // Config with only the old telegram.botUsername field
446
- writeConfig({
447
- telegram: { botUsername: "oldbot" },
448
- });
379
+ test("still quarantines corrupt JSON", () => {
380
+ // Corrupt-config quarantine is a recovery path: the broken file is
381
+ // renamed to `config.json.corrupt-<ts>.json` and the daemon proceeds
382
+ // with defaults. This must keep working.
383
+ writeFileSync(CONFIG_PATH, "{not valid json");
449
384
 
450
385
  loadConfig();
451
386
 
452
- const raw = readConfig();
453
- const telegram = raw.telegram as Record<string, unknown>;
454
- // Old field preserved
455
- expect(telegram.botUsername).toBe("oldbot");
456
- // New fields backfilled
457
- expect(telegram.apiBaseUrl).toBe("https://api.telegram.org");
458
- expect(telegram.deliverAuthBypass).toBe(false);
459
- expect(telegram.timeoutMs).toBe(15_000);
460
- expect(telegram.maxRetries).toBe(3);
461
- expect(telegram.initialBackoffMs).toBe(1_000);
387
+ // A new defaults-populated config.json is written in place
388
+ expect(existsSync(CONFIG_PATH)).toBe(true);
389
+ const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
390
+ expect(raw.memory?.v2?.bm25_b).toBe(0.4);
391
+
392
+ // The corrupt original is preserved as a `*.corrupt-*.json` sibling
393
+ const quarantined = readdirSync(WORKSPACE_DIR).filter((n) =>
394
+ n.startsWith("config.json.corrupt-"),
395
+ );
396
+ expect(quarantined.length).toBeGreaterThan(0);
462
397
  });
463
398
  });
@@ -0,0 +1,196 @@
1
+ /**
2
+ * When IS_PLATFORM=true and no config.json exists yet, loadConfig() must
3
+ * write all eight managed-capable service modes as "managed" instead of the
4
+ * schema default "your-own". When IS_PLATFORM is absent/false, or when
5
+ * config.json already exists, the schema defaults and existing values are
6
+ * preserved unchanged.
7
+ */
8
+
9
+ import {
10
+ existsSync,
11
+ mkdirSync,
12
+ readdirSync,
13
+ readFileSync,
14
+ rmSync,
15
+ writeFileSync,
16
+ } from "node:fs";
17
+ import { join } from "node:path";
18
+ import {
19
+ afterAll,
20
+ afterEach,
21
+ beforeEach,
22
+ describe,
23
+ expect,
24
+ mock,
25
+ test,
26
+ } from "bun:test";
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Mocks — declared before imports that depend on platform/logger
30
+ // ---------------------------------------------------------------------------
31
+
32
+ const WORKSPACE_DIR = process.env.VELLUM_WORKSPACE_DIR!;
33
+ const CONFIG_PATH = join(WORKSPACE_DIR, "config.json");
34
+
35
+ function makeLoggerStub(): Record<string, unknown> {
36
+ const stub: Record<string, unknown> = {};
37
+ for (const m of [
38
+ "info",
39
+ "warn",
40
+ "error",
41
+ "debug",
42
+ "trace",
43
+ "fatal",
44
+ "silent",
45
+ "child",
46
+ ]) {
47
+ stub[m] = m === "child" ? () => makeLoggerStub() : () => {};
48
+ }
49
+ return stub;
50
+ }
51
+
52
+ mock.module("../util/logger.js", () => ({
53
+ getLogger: () => makeLoggerStub(),
54
+ }));
55
+
56
+ afterAll(() => {
57
+ mock.restore();
58
+ });
59
+
60
+ import { invalidateConfigCache, loadConfig } from "../config/loader.js";
61
+ import { _setStorePath } from "../security/encrypted-store.js";
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Helpers
65
+ // ---------------------------------------------------------------------------
66
+
67
+ function ensureTestDir(): void {
68
+ const dirs = [
69
+ WORKSPACE_DIR,
70
+ join(WORKSPACE_DIR, "data"),
71
+ join(WORKSPACE_DIR, "data", "memory"),
72
+ join(WORKSPACE_DIR, "data", "memory", "knowledge"),
73
+ join(WORKSPACE_DIR, "data", "logs"),
74
+ ];
75
+ for (const dir of dirs) {
76
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
77
+ }
78
+ }
79
+
80
+ function resetWorkspace(): void {
81
+ if (existsSync(WORKSPACE_DIR)) {
82
+ for (const name of readdirSync(WORKSPACE_DIR)) {
83
+ rmSync(join(WORKSPACE_DIR, name), { recursive: true, force: true });
84
+ }
85
+ }
86
+ ensureTestDir();
87
+ }
88
+
89
+ function readConfig(): Record<string, unknown> {
90
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
91
+ }
92
+
93
+ const MANAGED_SERVICES = [
94
+ "inference",
95
+ "image-generation",
96
+ "web-search",
97
+ "google-oauth",
98
+ "outlook-oauth",
99
+ "linear-oauth",
100
+ "github-oauth",
101
+ "notion-oauth",
102
+ ] as const;
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Tests
106
+ // ---------------------------------------------------------------------------
107
+
108
+ describe("platform-managed config defaults", () => {
109
+ const originalIsPlatform = process.env.IS_PLATFORM;
110
+
111
+ beforeEach(() => {
112
+ resetWorkspace();
113
+ _setStorePath(join(WORKSPACE_DIR, "keys.enc"));
114
+ invalidateConfigCache();
115
+ });
116
+
117
+ afterEach(() => {
118
+ _setStorePath(null);
119
+ invalidateConfigCache();
120
+ // Restore env to its original value
121
+ if (originalIsPlatform === undefined) {
122
+ delete process.env.IS_PLATFORM;
123
+ } else {
124
+ process.env.IS_PLATFORM = originalIsPlatform;
125
+ }
126
+ });
127
+
128
+ test("IS_PLATFORM=true, no config file → all 8 service modes written as 'managed'", () => {
129
+ process.env.IS_PLATFORM = "true";
130
+
131
+ loadConfig();
132
+
133
+ expect(existsSync(CONFIG_PATH)).toBe(true);
134
+ const written = readConfig() as { services?: Record<string, unknown> };
135
+ expect(written.services).toBeDefined();
136
+ const services = written.services!;
137
+ for (const svc of MANAGED_SERVICES) {
138
+ expect((services[svc] as { mode?: string })?.mode).toBe("managed");
139
+ }
140
+ });
141
+
142
+ test("IS_PLATFORM=false, no config file → service modes default to 'your-own'", () => {
143
+ process.env.IS_PLATFORM = "false";
144
+
145
+ loadConfig();
146
+
147
+ expect(existsSync(CONFIG_PATH)).toBe(true);
148
+ const written = readConfig() as { services?: Record<string, unknown> };
149
+ expect(written.services).toBeDefined();
150
+ const services = written.services!;
151
+ for (const svc of MANAGED_SERVICES) {
152
+ expect((services[svc] as { mode?: string })?.mode).toBe("your-own");
153
+ }
154
+ });
155
+
156
+ test("IS_PLATFORM unset, no config file → service modes default to 'your-own'", () => {
157
+ delete process.env.IS_PLATFORM;
158
+
159
+ loadConfig();
160
+
161
+ expect(existsSync(CONFIG_PATH)).toBe(true);
162
+ const written = readConfig() as { services?: Record<string, unknown> };
163
+ expect(written.services).toBeDefined();
164
+ const services = written.services!;
165
+ for (const svc of MANAGED_SERVICES) {
166
+ expect((services[svc] as { mode?: string })?.mode).toBe("your-own");
167
+ }
168
+ });
169
+
170
+ test("IS_PLATFORM=true, config file already exists → existing service mode values are preserved", () => {
171
+ process.env.IS_PLATFORM = "true";
172
+
173
+ // Write an existing config with inference mode explicitly set to "your-own"
174
+ writeFileSync(
175
+ CONFIG_PATH,
176
+ JSON.stringify(
177
+ {
178
+ services: {
179
+ inference: { mode: "your-own" },
180
+ },
181
+ },
182
+ null,
183
+ 2,
184
+ ) + "\n",
185
+ );
186
+
187
+ loadConfig();
188
+
189
+ const written = readConfig() as { services?: Record<string, unknown> };
190
+ expect(written.services).toBeDefined();
191
+ // The existing value must be preserved — backfill path, not fresh-write path
192
+ expect(
193
+ (written.services!["inference"] as { mode?: string })?.mode,
194
+ ).toBe("your-own");
195
+ });
196
+ });
@@ -63,7 +63,6 @@ mock.module("../config/loader.js", () => ({
63
63
  }),
64
64
  loadConfig: () => ({}),
65
65
  loadRawConfig: () => ({}),
66
- saveConfig: () => {},
67
66
  saveRawConfig: () => {},
68
67
  invalidateConfigCache: () => {},
69
68
  getNestedValue: () => undefined,
@@ -40,20 +40,37 @@ mock.module("../platform/client.js", () => ({
40
40
  mock.module("../config/loader.js", () => ({
41
41
  getConfig: () => ({ services: {} }),
42
42
  loadConfig: () => ({ services: {} }),
43
- saveConfig: () => {},
44
43
  invalidateConfigCache: () => {},
45
44
  loadRawConfig: () => mockLoadRawConfig(),
46
45
  saveRawConfig: (raw: Record<string, unknown>) => {
47
46
  mockSaveRawConfigCalls.push(raw);
48
47
  },
49
48
  applyNestedDefaults: (c: unknown) => c,
50
- deepMergeMissing: (a: unknown) => a,
51
49
  deepMergeOverwrite: (a: unknown) => a,
52
50
  mergeDefaultWorkspaceConfig: () => {},
53
51
  getNestedValue: (obj: Record<string, unknown>, key: string) =>
54
52
  mockGetNestedValue(obj, key),
55
- setNestedValue: (obj: Record<string, unknown>, key: string, value: unknown) =>
56
- mockSetNestedValueCalls.push({ obj, key, value }),
53
+ setNestedValue: (
54
+ obj: Record<string, unknown>,
55
+ key: string,
56
+ value: unknown,
57
+ ) => {
58
+ mockSetNestedValueCalls.push({ obj, key, value });
59
+ const keys = key.split(".");
60
+ let current = obj;
61
+ for (let i = 0; i < keys.length - 1; i++) {
62
+ const segment = keys[i]!;
63
+ if (
64
+ current[segment] == null ||
65
+ typeof current[segment] !== "object" ||
66
+ Array.isArray(current[segment])
67
+ ) {
68
+ current[segment] = {};
69
+ }
70
+ current = current[segment] as Record<string, unknown>;
71
+ }
72
+ current[keys[keys.length - 1]!] = value;
73
+ },
57
74
  API_KEY_PROVIDERS: [
58
75
  "anthropic",
59
76
  "openai",
@@ -240,6 +257,33 @@ describe("config set — platform connection guard for service mode paths", () =
240
257
  expect(mockSaveRawConfigCalls).toHaveLength(1);
241
258
  });
242
259
 
260
+ test("config set ingress.publicBaseUrl overwrites existing value", async () => {
261
+ mockLoadRawConfig = () => ({
262
+ ingress: {
263
+ publicBaseUrl: "https://stale-velay.example.test",
264
+ publicBaseUrlManagedBy: "velay",
265
+ },
266
+ });
267
+
268
+ const { exitCode } = await runCli([
269
+ "node",
270
+ "assistant",
271
+ "config",
272
+ "set",
273
+ "ingress.publicBaseUrl",
274
+ "https://manual.example.test",
275
+ ]);
276
+
277
+ expect(exitCode).toBe(0);
278
+ expect(mockSaveRawConfigCalls).toHaveLength(1);
279
+ expect(mockSaveRawConfigCalls[0]).toEqual({
280
+ ingress: {
281
+ publicBaseUrl: "https://manual.example.test",
282
+ publicBaseUrlManagedBy: "velay",
283
+ },
284
+ });
285
+ });
286
+
243
287
  test("config get services.inference.mode — works without platform connection", async () => {
244
288
  mockGetNestedValue = (_obj, key) => {
245
289
  if (key === "services.inference.mode") return "your-own";
@@ -215,8 +215,8 @@ mock.module("../signals/emit-event.js", () => ({
215
215
  handleEmitEventSignal: () => {},
216
216
  }));
217
217
 
218
- mock.module("../signals/mcp-reload.js", () => ({
219
- handleMcpReloadSignal: () => {},
218
+ mock.module("../daemon/mcp-reload-service.js", () => ({
219
+ reloadMcpServers: async () => {},
220
220
  }));
221
221
 
222
222
  mock.module("../signals/user-message.js", () => ({
@@ -92,8 +92,8 @@ mock.module("../providers/registry.js", () => ({
92
92
  initializeProviders: () => {},
93
93
  }));
94
94
 
95
- mock.module("../signals/mcp-reload.js", () => ({
96
- handleMcpReloadSignal: () => {},
95
+ mock.module("../daemon/mcp-reload-service.js", () => ({
96
+ reloadMcpServers: async () => {},
97
97
  }));
98
98
 
99
99
  mock.module("../signals/conversation-undo.js", () => ({