@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
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Head-of-line repro for the per-lane scheduler in `runMemoryJobsOnce`.
3
+ *
4
+ * Before this fix, all non-embed jobs ran through a single bounded worker
5
+ * pool, so a long-running `graph_consolidate` LLM call would pin every slot
6
+ * and starve fast-lane jobs (e.g. `embed_concept_page` consolidation pages)
7
+ * for the duration of that call.
8
+ *
9
+ * The new scheduler runs slow / fast / embed lanes in parallel pools, each
10
+ * with its own concurrency budget. This test enqueues a wave of slow LLM
11
+ * jobs alongside fast jobs and asserts that every fast job completes before
12
+ * any slow job's promise resolves — proving the lanes are truly independent.
13
+ */
14
+ import { beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
15
+
16
+ import { eq } from "drizzle-orm";
17
+
18
+ import { DEFAULT_CONFIG } from "../config/defaults.js";
19
+ import type { AssistantConfig } from "../config/types.js";
20
+
21
+ // ── Mocks (must precede imports of tested module) ──────────────────
22
+
23
+ mock.module("../util/logger.js", () => ({
24
+ getLogger: () =>
25
+ new Proxy({} as Record<string, unknown>, {
26
+ get: () => () => {},
27
+ }),
28
+ }));
29
+
30
+ // Per-lane caps: 1 slow slot (so only 1 of the 5 enqueued slow jobs runs in
31
+ // this tick) and a generous fast cap so every fast job both gets claimed
32
+ // and gets a slot in the lane pool. The OLD shared-pool scheduler — which
33
+ // claimed jobs without lane awareness and ran them through a single
34
+ // workerConcurrency-sized pool — would pin its slots on the first claimed
35
+ // slow jobs and force the fast jobs to queue behind a 200ms LLM call.
36
+ const TEST_CONFIG: AssistantConfig = {
37
+ ...DEFAULT_CONFIG,
38
+ memory: {
39
+ ...DEFAULT_CONFIG.memory,
40
+ enabled: true,
41
+ jobs: {
42
+ ...DEFAULT_CONFIG.memory.jobs,
43
+ slowLlmConcurrency: 1,
44
+ fastConcurrency: 5,
45
+ embedConcurrency: 1,
46
+ workerConcurrency: 2,
47
+ },
48
+ },
49
+ };
50
+
51
+ mock.module("../config/loader.js", () => ({
52
+ getConfig: () => TEST_CONFIG,
53
+ loadConfig: () => TEST_CONFIG,
54
+ invalidateConfigCache: () => {},
55
+ }));
56
+
57
+ // ── Track timestamps so we can assert ordering ─────────────────────
58
+
59
+ type CompletionRecord = {
60
+ type: string;
61
+ conversationId: string;
62
+ completedAt: number;
63
+ };
64
+ const completions: CompletionRecord[] = [];
65
+
66
+ // Slow-lane handler: blocks for SLOW_DELAY_MS. The test asserts every fast
67
+ // job completes before any slow job — a single 200ms window is plenty.
68
+ const SLOW_DELAY_MS = 200;
69
+
70
+ mock.module("../memory/graph/consolidation.js", () => ({
71
+ runConsolidation: async (
72
+ scopeId: string,
73
+ ): Promise<{
74
+ totalUpdated: number;
75
+ totalDeleted: number;
76
+ totalMergeEdges: number;
77
+ }> => {
78
+ await new Promise((resolve) => setTimeout(resolve, SLOW_DELAY_MS));
79
+ completions.push({
80
+ type: "graph_consolidate",
81
+ conversationId: scopeId,
82
+ completedAt: Date.now(),
83
+ });
84
+ return { totalUpdated: 0, totalDeleted: 0, totalMergeEdges: 0 };
85
+ },
86
+ }));
87
+
88
+ // Fast-lane handler: resolves on the next microtask. The test fires this
89
+ // many times in parallel; nothing should block.
90
+ mock.module("../memory/jobs/embed-concept-page.js", () => ({
91
+ embedConceptPageJob: async (job: {
92
+ payload: { slug?: string };
93
+ }): Promise<void> => {
94
+ completions.push({
95
+ type: "embed_concept_page",
96
+ conversationId: job.payload.slug ?? "",
97
+ completedAt: Date.now(),
98
+ });
99
+ },
100
+ }));
101
+
102
+ // Stub remaining heavy boundaries that we never exercise but that get pulled
103
+ // in transitively through jobs-worker's eager imports. These aren't strictly
104
+ // required if the host machine can resolve them, but mocking them keeps the
105
+ // test hermetic and fast under `bun test`.
106
+ mock.module("../memory/db-maintenance.js", () => ({
107
+ maybeRunDbMaintenance: () => {},
108
+ }));
109
+
110
+ import { getDb } from "../memory/db-connection.js";
111
+ import { initializeDb } from "../memory/db-init.js";
112
+ import { enqueueMemoryJob } from "../memory/jobs-store.js";
113
+ import { runMemoryJobsOnce } from "../memory/jobs-worker.js";
114
+ import { _resetQdrantBreaker } from "../memory/qdrant-circuit-breaker.js";
115
+ import { memoryJobs } from "../memory/schema.js";
116
+
117
+ describe("memory jobs worker lane scheduling", () => {
118
+ beforeAll(() => {
119
+ initializeDb();
120
+ });
121
+
122
+ beforeEach(() => {
123
+ const db = getDb();
124
+ db.run("DELETE FROM memory_jobs");
125
+ completions.length = 0;
126
+ _resetQdrantBreaker();
127
+ });
128
+
129
+ test("fast lane completes before slow lane releases its slot", async () => {
130
+ // 5 slow `graph_consolidate` jobs across distinct scopes (so they would
131
+ // serialize behind a single shared pool) plus 5 fast `embed_concept_page`
132
+ // jobs across distinct slugs.
133
+ for (let i = 0; i < 5; i++) {
134
+ enqueueMemoryJob("graph_consolidate", { scopeId: `slow-${i}` });
135
+ }
136
+ for (let i = 0; i < 5; i++) {
137
+ enqueueMemoryJob("embed_concept_page", { slug: `fast-${i}` });
138
+ }
139
+
140
+ await runMemoryJobsOnce();
141
+
142
+ const fastDone = completions.filter((c) => c.type === "embed_concept_page");
143
+ const slowDone = completions.filter((c) => c.type === "graph_consolidate");
144
+
145
+ // Slow lane is capped at 1 in this test, so only 1 slow job ran in this
146
+ // tick. Fast lane has cap 2, but with 5 fast jobs it runs all 5 because
147
+ // each handler resolves on the next microtask.
148
+ expect(fastDone).toHaveLength(5);
149
+ expect(slowDone).toHaveLength(1);
150
+
151
+ // Head-of-line guarantee: every fast completion timestamp must precede
152
+ // the (single) slow completion timestamp. Under the old shared pool with
153
+ // workerConcurrency=2 and 1 slow slot occupied, this still held only if
154
+ // a fast slot freed up first — but with 2 slow jobs in flight (the old
155
+ // claim path would have claimed multiple slow jobs into the shared pool)
156
+ // both slots would be pinned for SLOW_DELAY_MS and fast work would queue
157
+ // behind. With the lane scheduler, fast work has its own pool.
158
+ const earliestSlow = Math.min(...slowDone.map((c) => c.completedAt));
159
+ for (const fast of fastDone) {
160
+ expect(fast.completedAt).toBeLessThan(earliestSlow);
161
+ }
162
+
163
+ // Sanity: exactly 1 of the 5 enqueued slow jobs reached `completed` (the
164
+ // slow lane's per-tick budget). The other 4 are still pending and will be
165
+ // picked up on subsequent ticks. (FIFO ordering inside the slow lane is
166
+ // covered separately in jobs-store-qdrant-breaker.test.ts.)
167
+ //
168
+ // Note: a sixth `graph_consolidate` row may also appear here — the
169
+ // `maybeEnqueueGraphMaintenanceJobs` tail of `runMemoryJobsOnce` enqueues
170
+ // its own maintenance job whose checkpoint is missing in this fresh DB.
171
+ // That row is irrelevant; we only care about the completed-vs-pending
172
+ // counts of jobs we explicitly seeded.
173
+ const completedSlow = countSlowByStatus("completed");
174
+ expect(completedSlow).toBe(1);
175
+ });
176
+ });
177
+
178
+ function countSlowByStatus(
179
+ status: "pending" | "running" | "completed",
180
+ ): number {
181
+ const db = getDb();
182
+ return db
183
+ .select()
184
+ .from(memoryJobs)
185
+ .where(eq(memoryJobs.type, "graph_consolidate"))
186
+ .all()
187
+ .filter((row) => row.status === status).length;
188
+ }
@@ -256,6 +256,8 @@ function createValidVBundle(
256
256
  bundle_id: string;
257
257
  origin_mode: "managed" | "self-hosted-remote" | "self-hosted-local";
258
258
  secrets_redacted: boolean;
259
+ min_runtime_version: string;
260
+ max_runtime_version: string | null;
259
261
  }>,
260
262
  ): Uint8Array {
261
263
  const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
@@ -296,8 +298,11 @@ function createValidVBundle(
296
298
  mode: overrides?.origin_mode ?? "self-hosted-local",
297
299
  },
298
300
  compatibility: {
299
- min_runtime_version: "0.0.0-test",
300
- max_runtime_version: null,
301
+ min_runtime_version: overrides?.min_runtime_version ?? "0.0.0-test",
302
+ max_runtime_version:
303
+ overrides?.max_runtime_version === undefined
304
+ ? null
305
+ : overrides.max_runtime_version,
301
306
  },
302
307
  contents,
303
308
  checksum: "",
@@ -652,6 +657,107 @@ describe("handleMigrationImport — validation failures", () => {
652
657
  });
653
658
  });
654
659
 
660
+ // ---------------------------------------------------------------------------
661
+ // HTTP handler tests: version_incompatible (Codex P2 regression)
662
+ //
663
+ // Pin: a bundle whose min_runtime_version exceeds APP_VERSION must surface as
664
+ // a 4xx user-actionable response (422 Unprocessable Entity), not a 500
665
+ // InternalError. Body mirrors the platform's PR #5470 response shape so
666
+ // clients can render the same UX regardless of which gate (platform or
667
+ // runtime) rejected the bundle.
668
+ // ---------------------------------------------------------------------------
669
+
670
+ describe("handleMigrationImport — version_incompatible", () => {
671
+ test("incompatible bundle returns 422 with structured body, not 500", async () => {
672
+ const vbundle = createValidVBundle(undefined, {
673
+ min_runtime_version: "99.0.0",
674
+ });
675
+ const req = new Request("http://localhost/v1/migrations/import", {
676
+ method: "POST",
677
+ headers: { "Content-Type": "application/octet-stream" },
678
+ body: toArrayBuffer(vbundle),
679
+ });
680
+
681
+ const res = await callHandler(handleMigrationImport, req);
682
+ const body = (await res.json()) as {
683
+ error: {
684
+ code: string;
685
+ message: string;
686
+ details?: {
687
+ reason: string;
688
+ bundle_compat: {
689
+ min_runtime_version: string;
690
+ max_runtime_version: string | null;
691
+ };
692
+ runtime_version: string;
693
+ };
694
+ };
695
+ };
696
+
697
+ expect(res.status).toBe(422);
698
+ expect(body.error.code).toBe("UNPROCESSABLE_ENTITY");
699
+ expect(body.error.message).toContain("99.0.0");
700
+ expect(body.error.details).toBeDefined();
701
+ expect(body.error.details!.reason).toBe("version_incompatible");
702
+ expect(body.error.details!.bundle_compat.min_runtime_version).toBe(
703
+ "99.0.0",
704
+ );
705
+ expect(body.error.details!.bundle_compat.max_runtime_version).toBeNull();
706
+ expect(typeof body.error.details!.runtime_version).toBe("string");
707
+ });
708
+
709
+ test("incompatible bundle does not modify disk", async () => {
710
+ const originalDb = new Uint8Array(readFileSync(testDbPath));
711
+ const originalConfig = readFileSync(testConfigPath, "utf8");
712
+
713
+ const vbundle = createValidVBundle(undefined, {
714
+ min_runtime_version: "99.0.0",
715
+ });
716
+ const req = new Request("http://localhost/v1/migrations/import", {
717
+ method: "POST",
718
+ headers: { "Content-Type": "application/octet-stream" },
719
+ body: toArrayBuffer(vbundle),
720
+ });
721
+
722
+ await callHandler(handleMigrationImport, req);
723
+
724
+ const currentDb = new Uint8Array(readFileSync(testDbPath));
725
+ const currentConfig = readFileSync(testConfigPath, "utf8");
726
+
727
+ expect(currentDb).toEqual(originalDb);
728
+ expect(currentConfig).toBe(originalConfig);
729
+ });
730
+
731
+ // Regression: the route handler pre-checks runtime-version compat using
732
+ // `validation.manifest.compatibility` before calling `resetDb()` and
733
+ // `commitImport()`. If a future refactor reorders the close to come
734
+ // before the gate, an incompatible bundle would still 422 (commitImport
735
+ // has its own defense-in-depth gate) but it would unnecessarily close
736
+ // and reopen the live SQLite singleton on every rejected import.
737
+ //
738
+ // We can't easily spy on `resetDb` here without mocking `db-connection.js`
739
+ // module-wide (which other tests in this file rely on for real). The
740
+ // semantic regression — disk unchanged + 422 — is covered by the two
741
+ // tests above. This test is a marker so future readers know the pre-
742
+ // check intent; if it ever starts failing, recheck `handleMigrationImport`
743
+ // ordering against the comment at line 861-862 ("Validate the bundle
744
+ // before closing the DB to avoid an unnecessary close/reopen cycle when
745
+ // the bundle is invalid").
746
+ test("incompatible bundle short-circuits before resetDb (intent marker)", async () => {
747
+ const vbundle = createValidVBundle(undefined, {
748
+ min_runtime_version: "99.0.0",
749
+ });
750
+ const req = new Request("http://localhost/v1/migrations/import", {
751
+ method: "POST",
752
+ headers: { "Content-Type": "application/octet-stream" },
753
+ body: toArrayBuffer(vbundle),
754
+ });
755
+
756
+ const res = await callHandler(handleMigrationImport, req);
757
+ expect(res.status).toBe(422);
758
+ });
759
+ });
760
+
655
761
  // ---------------------------------------------------------------------------
656
762
  // commitImport unit tests
657
763
  // ---------------------------------------------------------------------------
@@ -38,6 +38,7 @@ import { mock } from "bun:test";
38
38
  // ---------------------------------------------------------------------------
39
39
 
40
40
  /** IPC result the fake gateway will return (keyed by method name). */
41
+
41
42
  let ipcResults: Record<string, unknown> = {};
42
43
 
43
44
  /** Whether the fake ipcCall should simulate a connection error. */
@@ -380,12 +380,10 @@ mock.module("../config/loader.js", () => ({
380
380
  getConfig: () => mockGetConfig(),
381
381
  getConfigReadOnly: () => mockGetConfig(),
382
382
  loadConfig: () => mockGetConfig(),
383
- saveConfig: () => {},
384
383
  invalidateConfigCache: () => {},
385
384
  loadRawConfig: () => ({}),
386
385
  saveRawConfig: () => {},
387
386
  applyNestedDefaults: (c: unknown) => c,
388
- deepMergeMissing: (a: unknown) => a,
389
387
  deepMergeOverwrite: (a: unknown) => a,
390
388
  mergeDefaultWorkspaceConfig: () => {},
391
389
  getNestedValue: () => undefined,
@@ -16,7 +16,6 @@ mock.module("../config/loader.js", () => ({
16
16
  ingress: { publicBaseUrl: mockPublicBaseUrl },
17
17
  }),
18
18
  loadRawConfig: () => ({}),
19
- saveConfig: () => {},
20
19
  invalidateConfigCache: () => {},
21
20
  }));
22
21
 
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Verifies that known-pattern secrets are redacted before being written to
3
+ * durable conversation storage while the live model history (pendingToolResults,
4
+ * raw message content) is left untouched.
5
+ *
6
+ * Touch points under test:
7
+ * - Tool result content blocks persisted by handleMessageComplete
8
+ * - Assistant message text blocks persisted by handleMessageComplete
9
+ */
10
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
11
+
12
+ // ── Shared mock plumbing (must precede module-under-test imports) ──────────
13
+
14
+ mock.module("../util/logger.js", () => ({
15
+ getLogger: () =>
16
+ new Proxy({} as Record<string, unknown>, {
17
+ get: () => () => {},
18
+ }),
19
+ }));
20
+
21
+ mock.module("../config/loader.js", () => ({
22
+ getConfig: () => ({
23
+ skills: {
24
+ entries: {},
25
+ load: { extraDirs: [], watch: true, watchDebounceMs: 250 },
26
+ install: { nodeManager: "npm" },
27
+ allowBundled: null,
28
+ remoteProviders: {
29
+ skillssh: { enabled: true },
30
+ clawhub: { enabled: true },
31
+ },
32
+ remotePolicy: {
33
+ blockSuspicious: true,
34
+ blockMalware: true,
35
+ maxSkillsShRisk: "medium",
36
+ },
37
+ },
38
+ }),
39
+ loadConfig: () => ({}),
40
+ }));
41
+
42
+ interface AddMessageCall {
43
+ conversationId: string;
44
+ role: string;
45
+ content: string;
46
+ metadata?: Record<string, unknown>;
47
+ }
48
+ const addMessageCalls: AddMessageCall[] = [];
49
+ mock.module("../memory/conversation-crud.js", () => ({
50
+ addMessage: (
51
+ conversationId: string,
52
+ role: string,
53
+ content: string,
54
+ metadata?: Record<string, unknown>,
55
+ ) => {
56
+ addMessageCalls.push({ conversationId, role, content, metadata });
57
+ return { id: `mock-msg-${addMessageCalls.length}` };
58
+ },
59
+ getConversation: () => null,
60
+ getMessageById: () => null,
61
+ updateMessageContent: () => {},
62
+ provenanceFromTrustContext: () => ({}),
63
+ }));
64
+
65
+ mock.module("../memory/llm-request-log-store.js", () => ({
66
+ recordRequestLog: () => {},
67
+ backfillMessageIdOnLogs: () => {},
68
+ }));
69
+
70
+ mock.module("../memory/memory-recall-log-store.js", () => ({
71
+ backfillMemoryRecallLogMessageId: () => {},
72
+ }));
73
+
74
+ mock.module("../memory/conversation-disk-view.js", () => ({
75
+ syncMessageToDisk: () => {},
76
+ }));
77
+
78
+ // ── Imports (after mocks) ──────────────────────────────────────────────────
79
+
80
+ import type { AgentEvent } from "../agent/loop.js";
81
+ import type {
82
+ EventHandlerDeps,
83
+ EventHandlerState,
84
+ } from "../daemon/conversation-agent-loop-handlers.js";
85
+ import {
86
+ createEventHandlerState,
87
+ handleMessageComplete,
88
+ } from "../daemon/conversation-agent-loop-handlers.js";
89
+
90
+ // ── Helpers ────────────────────────────────────────────────────────────────
91
+
92
+ const CONV = "conv-redact-test";
93
+
94
+ function makeDeps(): EventHandlerDeps {
95
+ return {
96
+ ctx: {
97
+ conversationId: CONV,
98
+ provider: { name: "anthropic" },
99
+ traceEmitter: { emit: () => {} },
100
+ currentTurnSurfaces: [],
101
+ trustContext: {
102
+ sourceChannel: "vellum",
103
+ trustClass: "guardian",
104
+ },
105
+ } as unknown as EventHandlerDeps["ctx"],
106
+ onEvent: () => {},
107
+ reqId: "test-req",
108
+ isFirstMessage: false,
109
+ shouldGenerateTitle: false,
110
+ rlog: new Proxy({} as Record<string, unknown>, {
111
+ get: () => () => {},
112
+ }) as unknown as EventHandlerDeps["rlog"],
113
+ turnChannelContext: {
114
+ userMessageChannel: "vellum",
115
+ assistantMessageChannel: "vellum",
116
+ } as EventHandlerDeps["turnChannelContext"],
117
+ turnInterfaceContext: {
118
+ userMessageInterface: "macos",
119
+ assistantMessageInterface: "macos",
120
+ } as EventHandlerDeps["turnInterfaceContext"],
121
+ } as EventHandlerDeps;
122
+ }
123
+
124
+ function makeMessageCompleteEvent(
125
+ text: string,
126
+ ): Extract<AgentEvent, { type: "message_complete" }> {
127
+ return {
128
+ type: "message_complete",
129
+ message: { role: "assistant", content: [{ type: "text", text }] },
130
+ };
131
+ }
132
+
133
+ function lastPersisted(role: "assistant" | "user"): AddMessageCall {
134
+ for (let i = addMessageCalls.length - 1; i >= 0; i--) {
135
+ if (addMessageCalls[i].role === role) return addMessageCalls[i];
136
+ }
137
+ throw new Error(`No ${role} message was persisted`);
138
+ }
139
+
140
+ // ── Tests ──────────────────────────────────────────────────────────────────
141
+
142
+ describe("persistence-layer secret redaction", () => {
143
+ let state: EventHandlerState;
144
+
145
+ beforeEach(() => {
146
+ addMessageCalls.length = 0;
147
+ state = createEventHandlerState();
148
+ state.turnStartedAt = 1_700_000_000_000;
149
+ });
150
+
151
+ afterEach(() => {
152
+ addMessageCalls.length = 0;
153
+ });
154
+
155
+ // ── Tool result content ──────────────────────────────────────────────────
156
+
157
+ test("redacts Anthropic API key in tool result content before persistence", async () => {
158
+ // Pattern requires 80+ chars after "sk-ant-"
159
+ const secret =
160
+ "sk-ant-api03-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
161
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
162
+ state.pendingToolResults.set("tool-use-1", {
163
+ content: `Here is the key: ${secret}`,
164
+ isError: false,
165
+ });
166
+
167
+ await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent("done"));
168
+
169
+ const persisted = lastPersisted("user");
170
+ const blocks = JSON.parse(persisted.content) as Array<{
171
+ type: string;
172
+ content: string;
173
+ }>;
174
+ expect(blocks[0].content).not.toContain("sk-ant-api03-");
175
+ expect(blocks[0].content).toContain("<redacted");
176
+ });
177
+
178
+ test("redacts GitHub PAT in tool result content before persistence", async () => {
179
+ // Pattern requires 36+ chars after "ghp_"
180
+ const secret = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn";
181
+ state.pendingToolResults.set("tool-use-2", {
182
+ content: `token=${secret}`,
183
+ isError: false,
184
+ });
185
+
186
+ await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent("done"));
187
+
188
+ const persisted = lastPersisted("user");
189
+ const blocks = JSON.parse(persisted.content) as Array<{
190
+ type: string;
191
+ content: string;
192
+ }>;
193
+ expect(blocks[0].content).not.toContain("ghp_");
194
+ expect(blocks[0].content).toContain("<redacted");
195
+ });
196
+
197
+ test("does not redact non-secret content (UUID, hex hash) in tool result", async () => {
198
+ const safe = "id=550e8400-e29b-41d4-a716-446655440000 sha=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2";
199
+ state.pendingToolResults.set("tool-use-3", {
200
+ content: safe,
201
+ isError: false,
202
+ });
203
+
204
+ await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent("done"));
205
+
206
+ const persisted = lastPersisted("user");
207
+ const blocks = JSON.parse(persisted.content) as Array<{
208
+ type: string;
209
+ content: string;
210
+ }>;
211
+ expect(blocks[0].content).toBe(safe);
212
+ });
213
+
214
+ test("live model state (pendingToolResults) is not modified by persistence redaction", async () => {
215
+ const secret =
216
+ "sk-ant-api03-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
217
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
218
+ const originalContent = `key=${secret}`;
219
+ state.pendingToolResults.set("tool-use-4", {
220
+ content: originalContent,
221
+ isError: false,
222
+ });
223
+
224
+ // Capture the content before handleMessageComplete clears pendingToolResults
225
+ const contentSnapshot = state.pendingToolResults.get("tool-use-4")!.content;
226
+
227
+ await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent("done"));
228
+
229
+ // The snapshot taken from live state before the call must be unmodified
230
+ expect(contentSnapshot).toBe(originalContent);
231
+ expect(contentSnapshot).toContain("sk-ant-api03-");
232
+ });
233
+
234
+ // ── Assistant message text ───────────────────────────────────────────────
235
+
236
+ test("redacts known-pattern secret quoted in assistant text before persistence", async () => {
237
+ const secret =
238
+ "sk-ant-api03-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
239
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
240
+ const text = `Your API key is \`${secret}\`. Keep it safe.`;
241
+
242
+ await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent(text));
243
+
244
+ const persisted = lastPersisted("assistant");
245
+ const blocks = JSON.parse(persisted.content) as Array<{
246
+ type: string;
247
+ text?: string;
248
+ }>;
249
+ const textBlock = blocks.find((b) => b.type === "text");
250
+ expect(textBlock?.text).not.toContain("sk-ant-api03-");
251
+ expect(textBlock?.text).toContain("<redacted");
252
+ });
253
+
254
+ test("redacts OpenAI Project Key quoted in assistant text before persistence", async () => {
255
+ // Pattern requires 40+ chars after "sk-proj-"
256
+ const secret = "sk-proj-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrst";
257
+ const text = `I found this key in the config: ${secret}`;
258
+
259
+ await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent(text));
260
+
261
+ const persisted = lastPersisted("assistant");
262
+ const blocks = JSON.parse(persisted.content) as Array<{
263
+ type: string;
264
+ text?: string;
265
+ }>;
266
+ const textBlock = blocks.find((b) => b.type === "text");
267
+ expect(textBlock?.text).not.toContain("sk-proj-");
268
+ expect(textBlock?.text).toContain("<redacted");
269
+ });
270
+
271
+ test("does not redact non-secret text in assistant message", async () => {
272
+ const safe = "Here is the file list: index.ts, util.ts, main.ts";
273
+
274
+ await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent(safe));
275
+
276
+ const persisted = lastPersisted("assistant");
277
+ const blocks = JSON.parse(persisted.content) as Array<{
278
+ type: string;
279
+ text?: string;
280
+ }>;
281
+ const textBlock = blocks.find((b) => b.type === "text");
282
+ expect(textBlock?.text).toBe(safe);
283
+ });
284
+
285
+ test("does not redact random-looking strings that lack known prefixes", async () => {
286
+ // High-entropy but no known credential prefix — should NOT be redacted
287
+ const text = "checksum: 8f14e45fceea167a5a36dedd4bea2543";
288
+
289
+ await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent(text));
290
+
291
+ const persisted = lastPersisted("assistant");
292
+ const blocks = JSON.parse(persisted.content) as Array<{
293
+ type: string;
294
+ text?: string;
295
+ }>;
296
+ const textBlock = blocks.find((b) => b.type === "text");
297
+ expect(textBlock?.text).toBe(text);
298
+ });
299
+ });
@@ -67,7 +67,6 @@ mock.module("../config/loader.js", () => ({
67
67
  getConfig: () => mockConfig,
68
68
  loadConfig: () => mockConfig,
69
69
  invalidateConfigCache: () => {},
70
- saveConfig: () => {},
71
70
  loadRawConfig: () => ({}),
72
71
  saveRawConfig: () => {},
73
72
  getNestedValue: () => undefined,
@@ -245,13 +244,11 @@ describe("platform-hosted bash auto-approval", () => {
245
244
  expect(promptCalled).toBe(true);
246
245
  });
247
246
 
248
- test("bash NOT auto-approved for non-guardian actors via platform path (sandbox bash is allowed)", async () => {
247
+ test("bash NOT auto-approved for non-guardian actors via platform path (trusted_contact requires grant)", async () => {
249
248
  checkResultOverride = { decision: "prompt", reason: "Needs approval" };
250
249
 
251
- const platformAutoApproveCalled = false;
252
250
  const trackingPrompter = {
253
251
  prompt: async () => {
254
- // If this is called, we know the platform auto-approve did NOT fire
255
252
  return { decision: "allow" as const };
256
253
  },
257
254
  resolveConfirmation: () => {},
@@ -270,11 +267,10 @@ describe("platform-hosted bash auto-approval", () => {
270
267
  }),
271
268
  );
272
269
 
273
- // With requireFreshApproval, trusted_contact+bash goes through check()
274
- // which returns "prompt" and then the interactive prompter is called.
275
- // The platform auto-approve path (guardian-only) is NOT taken.
276
- expect(result.isError).toBe(false);
277
- void platformAutoApproveCalled; // suppress unused warning
270
+ // trusted_contact now requires a guardian-scoped grant for side-effect
271
+ // tools. Without a grant, the pre-execution gate denies the invocation
272
+ // before the permission checker or prompter is reached.
273
+ expect(result.isError).toBe(true);
278
274
  });
279
275
 
280
276
  test("bash NOT auto-approved when requireFreshApproval is set", async () => {