@vellumai/assistant 0.6.4 → 0.6.5

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 (717) hide show
  1. package/.prettierignore +5 -0
  2. package/ARCHITECTURE.md +32 -36
  3. package/Dockerfile +12 -0
  4. package/README.md +3 -4
  5. package/bun.lock +8 -3
  6. package/docs/architecture/integrations.md +1 -20
  7. package/docs/architecture/security.md +16 -16
  8. package/docs/error-handling.md +111 -0
  9. package/docs/skills.md +10 -10
  10. package/docs/stt-provider-onboarding.md +2 -1
  11. package/knip.json +9 -2
  12. package/node_modules/@vellumai/ces-contracts/package.json +2 -1
  13. package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
  14. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
  15. package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
  16. package/node_modules/@vellumai/credential-storage/package.json +2 -2
  17. package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
  18. package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
  19. package/node_modules/@vellumai/egress-proxy/package.json +2 -2
  20. package/openapi.yaml +123 -11
  21. package/package.json +6 -3
  22. package/scripts/generate-openapi.ts +50 -11
  23. package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
  24. package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
  25. package/src/__tests__/agent-loop.test.ts +112 -1
  26. package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
  27. package/src/__tests__/anthropic-provider.test.ts +171 -2
  28. package/src/__tests__/approval-cascade.test.ts +31 -10
  29. package/src/__tests__/approval-routes-http.test.ts +134 -10
  30. package/src/__tests__/assistant-attachments.test.ts +44 -0
  31. package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
  32. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  33. package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
  34. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
  35. package/src/__tests__/browser-skill-endstate.test.ts +51 -182
  36. package/src/__tests__/btw-routes.test.ts +47 -1
  37. package/src/__tests__/call-controller.test.ts +1 -2
  38. package/src/__tests__/call-site-routing-provider.test.ts +214 -0
  39. package/src/__tests__/catalog-cache.test.ts +27 -4
  40. package/src/__tests__/channel-approval-routes.test.ts +4 -4
  41. package/src/__tests__/channel-reply-delivery.test.ts +300 -2
  42. package/src/__tests__/checker.test.ts +428 -501
  43. package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
  44. package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
  45. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  46. package/src/__tests__/config-analysis.test.ts +11 -28
  47. package/src/__tests__/config-loader-backfill.test.ts +174 -0
  48. package/src/__tests__/config-loader-corrupt.test.ts +183 -0
  49. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
  50. package/src/__tests__/config-schema-cmd.test.ts +11 -5
  51. package/src/__tests__/config-schema.test.ts +427 -114
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/contact-store-user-file.test.ts +72 -73
  54. package/src/__tests__/contacts-write.test.ts +4 -4
  55. package/src/__tests__/context-token-estimator.test.ts +191 -1
  56. package/src/__tests__/context-window-manager.test.ts +530 -2
  57. package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
  58. package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
  59. package/src/__tests__/conversation-agent-loop.test.ts +412 -82
  60. package/src/__tests__/conversation-attachments.test.ts +1 -1
  61. package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
  62. package/src/__tests__/conversation-error.test.ts +37 -6
  63. package/src/__tests__/conversation-history-web-search.test.ts +6 -0
  64. package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
  65. package/src/__tests__/conversation-lifecycle.test.ts +336 -0
  66. package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
  67. package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
  68. package/src/__tests__/conversation-process-callsite.test.ts +306 -0
  69. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
  70. package/src/__tests__/conversation-queue.test.ts +41 -26
  71. package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
  75. package/src/__tests__/conversation-skill-tools.test.ts +12 -146
  76. package/src/__tests__/conversation-slash-queue.test.ts +34 -19
  77. package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
  78. package/src/__tests__/conversation-speed-override.test.ts +30 -11
  79. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
  80. package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
  81. package/src/__tests__/conversation-title-service.test.ts +2 -2
  82. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
  83. package/src/__tests__/conversation-unread-route.test.ts +2 -2
  84. package/src/__tests__/conversation-usage.test.ts +3 -1
  85. package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
  86. package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
  87. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
  88. package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
  89. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  90. package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
  91. package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
  92. package/src/__tests__/credential-vault-unit.test.ts +135 -19
  93. package/src/__tests__/credentials-cli.test.ts +1 -9
  94. package/src/__tests__/cross-provider-web-search.test.ts +84 -0
  95. package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
  96. package/src/__tests__/delete-propagation.test.ts +437 -0
  97. package/src/__tests__/dm-backfill.test.ts +417 -0
  98. package/src/__tests__/dm-persistence.test.ts +227 -0
  99. package/src/__tests__/edit-propagation.test.ts +280 -0
  100. package/src/__tests__/ephemeral-permissions.test.ts +93 -3
  101. package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
  102. package/src/__tests__/estimator-calibration.test.ts +213 -0
  103. package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
  104. package/src/__tests__/file-write-tool.test.ts +151 -1
  105. package/src/__tests__/filing-service.test.ts +255 -0
  106. package/src/__tests__/gemini-provider.test.ts +0 -3
  107. package/src/__tests__/guardian-grant-minting.test.ts +8 -0
  108. package/src/__tests__/headless-browser-interactions.test.ts +1 -1
  109. package/src/__tests__/heartbeat-service.test.ts +96 -15
  110. package/src/__tests__/host-shell-tool.test.ts +124 -18
  111. package/src/__tests__/http-user-message-parity.test.ts +29 -1
  112. package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
  113. package/src/__tests__/intent-routing.test.ts +1 -40
  114. package/src/__tests__/llm-catalog-parity.test.ts +174 -0
  115. package/src/__tests__/llm-context-normalization.test.ts +121 -0
  116. package/src/__tests__/llm-resolver.test.ts +214 -0
  117. package/src/__tests__/llm-schema.test.ts +223 -0
  118. package/src/__tests__/managed-proxy-context.test.ts +6 -2
  119. package/src/__tests__/messaging-skill-split.test.ts +3 -34
  120. package/src/__tests__/migration-import-from-url.test.ts +684 -0
  121. package/src/__tests__/model-intents.test.ts +9 -83
  122. package/src/__tests__/notification-decision-fallback.test.ts +0 -10
  123. package/src/__tests__/notification-decision-identity.test.ts +0 -9
  124. package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
  125. package/src/__tests__/oauth-store.test.ts +10 -7
  126. package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
  127. package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
  128. package/src/__tests__/openai-provider.test.ts +7 -0
  129. package/src/__tests__/openai-responses-provider.test.ts +396 -0
  130. package/src/__tests__/openrouter-provider-only.test.ts +135 -0
  131. package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
  132. package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
  133. package/src/__tests__/permission-mode.test.ts +16 -0
  134. package/src/__tests__/permission-types.test.ts +0 -1
  135. package/src/__tests__/persona-resolver.test.ts +13 -13
  136. package/src/__tests__/pkb-autoinject.test.ts +37 -1
  137. package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
  138. package/src/__tests__/pricing.test.ts +50 -3
  139. package/src/__tests__/profiler-routes.test.ts +1 -1
  140. package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
  141. package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +135 -6
  143. package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
  144. package/src/__tests__/provider-registry-ollama.test.ts +1 -2
  145. package/src/__tests__/proxy-approval-callback.test.ts +0 -1
  146. package/src/__tests__/reaction-persistence.test.ts +560 -0
  147. package/src/__tests__/relay-server.test.ts +1 -1
  148. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  149. package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
  150. package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
  151. package/src/__tests__/risk-classifier-parity.test.ts +230 -0
  152. package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
  153. package/src/__tests__/secret-ingress-http.test.ts +28 -0
  154. package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
  155. package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
  156. package/src/__tests__/secret-scanner-executor.test.ts +1 -1
  157. package/src/__tests__/send-endpoint-busy.test.ts +29 -1
  158. package/src/__tests__/server-history-render.test.ts +31 -0
  159. package/src/__tests__/shell-parser-property.test.ts +13 -13
  160. package/src/__tests__/skill-cache-store.test.ts +182 -0
  161. package/src/__tests__/skills.test.ts +19 -33
  162. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  163. package/src/__tests__/slack-skill.test.ts +3 -8
  164. package/src/__tests__/starter-bundle.test.ts +35 -0
  165. package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
  166. package/src/__tests__/suggestion-routes.test.ts +160 -3
  167. package/src/__tests__/system-prompt.test.ts +22 -35
  168. package/src/__tests__/task-runner.test.ts +3 -1
  169. package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
  170. package/src/__tests__/terminal-tools.test.ts +8 -0
  171. package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
  172. package/src/__tests__/thread-backfill.test.ts +941 -0
  173. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
  174. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
  175. package/src/__tests__/tool-executor.test.ts +60 -94
  176. package/src/__tests__/trust-store.test.ts +442 -109
  177. package/src/__tests__/update-bulletin-job.test.ts +389 -0
  178. package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
  179. package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
  180. package/src/__tests__/voice-session-bridge.test.ts +39 -0
  181. package/src/__tests__/volume-security-guard.test.ts +3 -2
  182. package/src/__tests__/web-search-history.test.ts +337 -0
  183. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
  184. package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
  185. package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
  186. package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
  187. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
  188. package/src/__tests__/workspace-policy.test.ts +1 -13
  189. package/src/acp/client-handler.ts +1 -2
  190. package/src/agent/loop.ts +209 -17
  191. package/src/avatar/resvg-lazy.test.ts +136 -0
  192. package/src/avatar/resvg-lazy.ts +82 -9
  193. package/src/avatar/traits-png-sync.ts +21 -1
  194. package/src/browser/__tests__/operations.test.ts +163 -0
  195. package/src/browser/identifiers.ts +51 -0
  196. package/src/browser/operations.ts +660 -0
  197. package/src/browser/types.ts +81 -0
  198. package/src/calls/guardian-question-copy.ts +2 -2
  199. package/src/calls/telephony-stt-routing.ts +1 -1
  200. package/src/calls/voice-session-bridge.ts +1 -0
  201. package/src/cli/AGENTS.md +1 -1
  202. package/src/cli/commands/__tests__/attachment.test.ts +438 -0
  203. package/src/cli/commands/__tests__/browser.test.ts +554 -0
  204. package/src/cli/commands/__tests__/cache.test.ts +623 -0
  205. package/src/cli/commands/__tests__/email-list.test.ts +6 -0
  206. package/src/cli/commands/__tests__/email-send.test.ts +93 -1
  207. package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
  208. package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
  209. package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
  210. package/src/cli/commands/__tests__/task.test.ts +913 -0
  211. package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
  212. package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
  213. package/src/cli/commands/__tests__/ui.test.ts +1215 -0
  214. package/src/cli/commands/__tests__/watchers.test.ts +716 -0
  215. package/src/cli/commands/attachment.ts +182 -0
  216. package/src/cli/commands/browser.ts +350 -0
  217. package/src/cli/commands/cache.ts +341 -0
  218. package/src/cli/commands/completions.ts +0 -3
  219. package/src/cli/commands/config.ts +6 -6
  220. package/src/cli/commands/conversations-import.ts +347 -0
  221. package/src/cli/commands/conversations.ts +14 -1
  222. package/src/cli/commands/email.ts +234 -194
  223. package/src/cli/commands/image-generation.ts +300 -0
  224. package/src/cli/commands/inference.ts +200 -0
  225. package/src/cli/commands/memory.ts +127 -17
  226. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  227. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
  228. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
  229. package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
  230. package/src/cli/commands/stt.ts +339 -0
  231. package/src/cli/commands/task.ts +795 -0
  232. package/src/cli/commands/trust.ts +50 -19
  233. package/src/cli/commands/tts.ts +273 -0
  234. package/src/cli/commands/ui.ts +670 -0
  235. package/src/cli/commands/watchers.ts +509 -0
  236. package/src/cli/lib/daemon-credential-client.ts +0 -19
  237. package/src/cli/program.ts +23 -4
  238. package/src/cli.ts +0 -37
  239. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
  240. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  241. package/src/config/bundled-skills/messaging/SKILL.md +2 -2
  242. package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
  243. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
  244. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
  245. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
  246. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
  247. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
  248. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  249. package/src/config/bundled-tool-registry.ts +0 -175
  250. package/src/config/env.ts +7 -2
  251. package/src/config/feature-flag-registry.json +25 -9
  252. package/src/config/llm-resolver.ts +128 -0
  253. package/src/config/loader.ts +194 -10
  254. package/src/config/raw-config-utils.ts +30 -2
  255. package/src/config/sanitize-for-transfer.ts +35 -0
  256. package/src/config/schema.ts +30 -41
  257. package/src/config/schemas/analysis.ts +3 -22
  258. package/src/config/schemas/calls.ts +0 -4
  259. package/src/config/schemas/filing.ts +2 -7
  260. package/src/config/schemas/heartbeat.ts +0 -5
  261. package/src/config/schemas/inference.ts +3 -23
  262. package/src/config/schemas/llm.ts +318 -0
  263. package/src/config/schemas/memory-processing.ts +1 -9
  264. package/src/config/schemas/notifications.ts +4 -11
  265. package/src/config/schemas/platform.ts +3 -9
  266. package/src/config/schemas/security.ts +33 -0
  267. package/src/config/schemas/services.ts +9 -4
  268. package/src/config/schemas/stt.ts +1 -0
  269. package/src/config/schemas/tts.ts +53 -0
  270. package/src/config/schemas/updates.ts +1 -1
  271. package/src/config/schemas/workspace-git.ts +3 -40
  272. package/src/config/skills.ts +2 -2
  273. package/src/context/__tests__/compact-prompt.test.ts +45 -0
  274. package/src/context/__tests__/microcompact.test.ts +805 -0
  275. package/src/context/estimator-calibration.ts +136 -0
  276. package/src/context/microcompact.ts +443 -0
  277. package/src/context/prompts/compact.md +12 -0
  278. package/src/context/token-estimator.ts +61 -3
  279. package/src/context/window-manager.ts +229 -25
  280. package/src/credential-execution/approval-bridge.ts +0 -1
  281. package/src/credential-execution/executable-discovery.ts +19 -8
  282. package/src/credential-execution/process-manager.test.ts +109 -0
  283. package/src/credential-execution/process-manager.ts +65 -2
  284. package/src/daemon/approval-generators.ts +29 -4
  285. package/src/daemon/assistant-attachments.ts +24 -13
  286. package/src/daemon/classifier.ts +2 -2
  287. package/src/daemon/config-watcher.ts +0 -1
  288. package/src/daemon/context-overflow-reducer.ts +4 -1
  289. package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
  290. package/src/daemon/conversation-agent-loop.ts +462 -80
  291. package/src/daemon/conversation-attachments.ts +2 -6
  292. package/src/daemon/conversation-error.ts +36 -1
  293. package/src/daemon/conversation-lifecycle.ts +30 -6
  294. package/src/daemon/conversation-messaging.ts +73 -4
  295. package/src/daemon/conversation-process.ts +10 -4
  296. package/src/daemon/conversation-queue-manager.ts +3 -0
  297. package/src/daemon/conversation-runtime-assembly.ts +760 -29
  298. package/src/daemon/conversation-slash.ts +2 -2
  299. package/src/daemon/conversation-surfaces.ts +389 -1
  300. package/src/daemon/conversation-tool-setup.ts +10 -5
  301. package/src/daemon/conversation-usage.ts +1 -1
  302. package/src/daemon/conversation.ts +118 -30
  303. package/src/daemon/external-skills-bootstrap.ts +41 -0
  304. package/src/daemon/guardian-action-generators.ts +34 -14
  305. package/src/daemon/handlers/config-model.test.ts +86 -0
  306. package/src/daemon/handlers/config-model.ts +54 -12
  307. package/src/daemon/handlers/conversations.ts +9 -2
  308. package/src/daemon/handlers/shared.ts +39 -11
  309. package/src/daemon/handlers/skills.ts +2 -2
  310. package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
  311. package/src/daemon/lifecycle.ts +76 -14
  312. package/src/daemon/message-types/conversations.ts +14 -0
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/trust.ts +0 -2
  315. package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
  316. package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
  317. package/src/daemon/pkb-context-tracker.test.ts +169 -0
  318. package/src/daemon/pkb-context-tracker.ts +125 -0
  319. package/src/daemon/pkb-reminder-builder.test.ts +70 -0
  320. package/src/daemon/pkb-reminder-builder.ts +31 -0
  321. package/src/daemon/providers-setup.ts +6 -0
  322. package/src/daemon/server.ts +117 -9
  323. package/src/daemon/tool-side-effects.ts +0 -9
  324. package/src/daemon/watch-handler.ts +4 -4
  325. package/src/daemon/web-search-history.ts +126 -0
  326. package/src/events/domain-events.ts +0 -1
  327. package/src/filing/filing-service.ts +9 -10
  328. package/src/heartbeat/heartbeat-service.ts +76 -28
  329. package/src/home/__tests__/feed-scheduler.test.ts +39 -11
  330. package/src/home/__tests__/rollup-producer.test.ts +44 -0
  331. package/src/home/assistant-feed-authoring.ts +4 -0
  332. package/src/home/emit-feed-event.ts +4 -0
  333. package/src/home/feed-scheduler.ts +20 -4
  334. package/src/home/feed-types.ts +56 -2
  335. package/src/home/relationship-state-writer.ts +2 -2
  336. package/src/home/rollup-producer.ts +34 -5
  337. package/src/home/suggested-prompts.ts +101 -0
  338. package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
  339. package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
  340. package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
  341. package/src/ipc/__tests__/socket-path.test.ts +73 -0
  342. package/src/ipc/__tests__/task-ipc.test.ts +577 -0
  343. package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
  344. package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
  345. package/src/ipc/cli-client.ts +2 -1
  346. package/src/ipc/cli-server.ts +26 -8
  347. package/src/ipc/gateway-client.ts +4 -4
  348. package/src/ipc/routes/attachment.ts +114 -0
  349. package/src/ipc/routes/browser-context.ts +61 -0
  350. package/src/ipc/routes/browser.ts +96 -0
  351. package/src/ipc/routes/cache.ts +96 -0
  352. package/src/ipc/routes/index.ts +17 -1
  353. package/src/ipc/routes/task-queue.ts +226 -0
  354. package/src/ipc/routes/task.ts +173 -0
  355. package/src/ipc/routes/ui-request.ts +50 -0
  356. package/src/ipc/routes/watcher.ts +203 -0
  357. package/src/ipc/socket-path.ts +100 -0
  358. package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
  359. package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
  360. package/src/memory/admin.ts +18 -0
  361. package/src/memory/conversation-analyze-job.ts +14 -13
  362. package/src/memory/conversation-attention-store.ts +13 -6
  363. package/src/memory/conversation-crud.ts +103 -3
  364. package/src/memory/conversation-group-migration.ts +38 -6
  365. package/src/memory/conversation-title-service.ts +7 -4
  366. package/src/memory/db-init.ts +2 -0
  367. package/src/memory/embedding-backend.ts +1 -1
  368. package/src/memory/graph/compaction.ts +299 -0
  369. package/src/memory/graph/consolidation.ts +4 -4
  370. package/src/memory/graph/conversation-graph-memory.ts +89 -29
  371. package/src/memory/graph/extraction.test.ts +272 -2
  372. package/src/memory/graph/extraction.ts +173 -51
  373. package/src/memory/graph/graph-search.test.ts +92 -0
  374. package/src/memory/graph/graph-search.ts +4 -1
  375. package/src/memory/graph/narrative.ts +2 -2
  376. package/src/memory/graph/pattern-scan.ts +2 -2
  377. package/src/memory/graph/retriever.test.ts +459 -0
  378. package/src/memory/graph/retriever.ts +230 -48
  379. package/src/memory/graph/store.ts +41 -0
  380. package/src/memory/graph/tool-handlers.ts +27 -0
  381. package/src/memory/graph/tools.ts +6 -1
  382. package/src/memory/indexer.ts +5 -5
  383. package/src/memory/job-handlers/conversation-starters.ts +23 -20
  384. package/src/memory/job-handlers/summarization.ts +2 -2
  385. package/src/memory/job-utils.ts +7 -1
  386. package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
  387. package/src/memory/jobs/embed-pkb-file.ts +54 -0
  388. package/src/memory/jobs-store.ts +44 -3
  389. package/src/memory/jobs-worker.ts +4 -0
  390. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
  391. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
  392. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
  393. package/src/memory/migrations/index.ts +1 -0
  394. package/src/memory/pkb/pkb-index.test.ts +368 -0
  395. package/src/memory/pkb/pkb-index.ts +255 -0
  396. package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
  397. package/src/memory/pkb/pkb-reconcile.ts +148 -0
  398. package/src/memory/pkb/pkb-search.test.ts +438 -0
  399. package/src/memory/pkb/pkb-search.ts +137 -0
  400. package/src/memory/pkb/types.ts +53 -0
  401. package/src/memory/qdrant-client.ts +122 -1
  402. package/src/memory/slack-thread-store.ts +37 -0
  403. package/src/messaging/providers/gmail/adapter.ts +6 -16
  404. package/src/messaging/providers/gmail/client.ts +22 -0
  405. package/src/messaging/providers/gmail/types.ts +7 -0
  406. package/src/messaging/providers/slack/adapter.ts +14 -2
  407. package/src/messaging/providers/slack/backfill.test.ts +257 -0
  408. package/src/messaging/providers/slack/backfill.ts +101 -0
  409. package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
  410. package/src/messaging/providers/slack/message-metadata.ts +123 -0
  411. package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
  412. package/src/messaging/providers/slack/render-transcript.ts +443 -0
  413. package/src/messaging/style-analyzer.ts +5 -2
  414. package/src/notifications/README.md +9 -5
  415. package/src/notifications/decision-engine.ts +3 -9
  416. package/src/notifications/preference-extractor.ts +2 -6
  417. package/src/oauth/oauth-store.ts +1 -0
  418. package/src/oauth/platform-connection.test.ts +47 -0
  419. package/src/oauth/platform-connection.ts +15 -5
  420. package/src/oauth/seed-providers.ts +4 -2
  421. package/src/permissions/approval-policy.test.ts +948 -0
  422. package/src/permissions/approval-policy.ts +257 -0
  423. package/src/permissions/bash-risk-classifier.test.ts +1208 -0
  424. package/src/permissions/bash-risk-classifier.ts +707 -0
  425. package/src/permissions/checker.ts +217 -708
  426. package/src/permissions/command-registry.test.ts +535 -0
  427. package/src/permissions/command-registry.ts +825 -0
  428. package/src/permissions/defaults.ts +26 -78
  429. package/src/permissions/file-risk-classifier.test.ts +535 -0
  430. package/src/permissions/file-risk-classifier.ts +274 -0
  431. package/src/permissions/risk-types.ts +205 -0
  432. package/src/permissions/secret-prompter.ts +53 -2
  433. package/src/permissions/skill-risk-classifier.test.ts +311 -0
  434. package/src/permissions/skill-risk-classifier.ts +214 -0
  435. package/src/permissions/trust-client.ts +52 -25
  436. package/src/permissions/trust-store-interface.ts +1 -6
  437. package/src/permissions/trust-store.ts +161 -62
  438. package/src/permissions/types.ts +23 -14
  439. package/src/permissions/web-risk-classifier.test.ts +170 -0
  440. package/src/permissions/web-risk-classifier.ts +89 -0
  441. package/src/permissions/workspace-policy.ts +1 -16
  442. package/src/platform/client.ts +19 -1
  443. package/src/prompts/persona-resolver.ts +3 -3
  444. package/src/prompts/system-prompt.ts +19 -20
  445. package/src/prompts/templates/SOUL.md +2 -2
  446. package/src/prompts/update-bulletin-job.ts +190 -0
  447. package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
  448. package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
  449. package/src/providers/__tests__/retry-callsite.test.ts +424 -0
  450. package/src/providers/anthropic/client.ts +183 -14
  451. package/src/providers/call-site-routing.ts +71 -0
  452. package/src/providers/gemini/client.ts +65 -2
  453. package/src/providers/managed-proxy/constants.ts +2 -1
  454. package/src/providers/model-catalog.ts +501 -33
  455. package/src/providers/model-intents.ts +4 -4
  456. package/src/providers/openai/chat-completions-provider.ts +57 -1
  457. package/src/providers/openai/responses-provider.ts +86 -9
  458. package/src/providers/openrouter/client.ts +76 -9
  459. package/src/providers/provider-env-vars.ts +56 -0
  460. package/src/providers/provider-send-message.ts +22 -5
  461. package/src/providers/ratelimit.ts +4 -0
  462. package/src/providers/registry.ts +19 -8
  463. package/src/providers/retry.ts +174 -39
  464. package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
  465. package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
  466. package/src/providers/speech-to-text/provider-catalog.ts +17 -0
  467. package/src/providers/speech-to-text/resolve.ts +7 -0
  468. package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
  469. package/src/providers/speech-to-text/xai-realtime.ts +796 -0
  470. package/src/providers/speech-to-text/xai.test.ts +155 -0
  471. package/src/providers/speech-to-text/xai.ts +97 -0
  472. package/src/providers/types.ts +93 -3
  473. package/src/runtime/AGENTS.md +2 -2
  474. package/src/runtime/__tests__/agent-wake.test.ts +43 -2
  475. package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
  476. package/src/runtime/agent-wake.ts +63 -22
  477. package/src/runtime/auth/route-policy.ts +4 -0
  478. package/src/runtime/btw-sidechain.ts +13 -3
  479. package/src/runtime/channel-reply-delivery.ts +106 -2
  480. package/src/runtime/decision-token.ts +116 -0
  481. package/src/runtime/gateway-client.ts +2 -2
  482. package/src/runtime/http-router.ts +32 -0
  483. package/src/runtime/http-server.ts +52 -1
  484. package/src/runtime/http-types.ts +23 -1
  485. package/src/runtime/interactive-ui.ts +362 -0
  486. package/src/runtime/invite-instruction-generator.ts +2 -2
  487. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
  488. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
  489. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
  490. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
  491. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
  492. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
  493. package/src/runtime/migrations/gcs-signed-url.ts +162 -0
  494. package/src/runtime/migrations/vbundle-importer.ts +154 -9
  495. package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
  496. package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
  497. package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
  498. package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
  499. package/src/runtime/migrations/vbundle-validator.ts +15 -6
  500. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
  501. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
  502. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
  503. package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
  504. package/src/runtime/routes/approval-routes.ts +12 -17
  505. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
  506. package/src/runtime/routes/avatar-routes.ts +20 -4
  507. package/src/runtime/routes/btw-routes.ts +1 -4
  508. package/src/runtime/routes/conversation-management-routes.ts +20 -2
  509. package/src/runtime/routes/conversation-routes.ts +133 -27
  510. package/src/runtime/routes/debug-routes.ts +1 -1
  511. package/src/runtime/routes/diagnostics-routes.ts +6 -4
  512. package/src/runtime/routes/events-routes.ts +16 -0
  513. package/src/runtime/routes/guardian-approval-interception.ts +33 -3
  514. package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
  515. package/src/runtime/routes/home-feed-routes.ts +120 -2
  516. package/src/runtime/routes/inbound-message-handler.ts +912 -2
  517. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
  518. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
  519. package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
  520. package/src/runtime/routes/integrations/slack/channel.ts +25 -3
  521. package/src/runtime/routes/llm-context-normalization.ts +23 -1
  522. package/src/runtime/routes/migration-routes.ts +720 -124
  523. package/src/runtime/routes/settings-routes.ts +4 -2
  524. package/src/runtime/routes/trust-rules-routes.ts +30 -14
  525. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  526. package/src/runtime/routes/work-items-routes.ts +3 -2
  527. package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
  528. package/src/runtime/services/analyze-conversation.ts +12 -16
  529. package/src/runtime/skill-route-registry.ts +28 -6
  530. package/src/schedule/scheduler.ts +8 -0
  531. package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
  532. package/src/security/__tests__/untrusted-content.test.ts +109 -0
  533. package/src/security/oauth2.ts +98 -35
  534. package/src/security/secure-keys.ts +7 -8
  535. package/src/security/token-manager.ts +27 -13
  536. package/src/security/untrusted-content.ts +102 -0
  537. package/src/skills/catalog-cache.ts +26 -7
  538. package/src/skills/catalog-install.ts +31 -3
  539. package/src/skills/skill-cache-store.ts +97 -0
  540. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
  541. package/src/stt/daemon-batch-transcriber.ts +33 -0
  542. package/src/stt/stt-stream-session.ts +8 -1
  543. package/src/stt/types.ts +5 -1
  544. package/src/subagent/manager.ts +41 -13
  545. package/src/tasks/ephemeral-permissions.ts +9 -4
  546. package/src/telemetry/usage-telemetry-reporter.ts +27 -5
  547. package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
  548. package/src/tools/browser/browser-execution.ts +65 -38
  549. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
  550. package/src/tools/credentials/tool-policy.ts +39 -5
  551. package/src/tools/credentials/vault.ts +9 -4
  552. package/src/tools/executor.ts +4 -0
  553. package/src/tools/filesystem/write.ts +52 -0
  554. package/src/tools/host-terminal/host-shell.ts +45 -5
  555. package/src/tools/memory/register.test.ts +185 -0
  556. package/src/tools/memory/register.ts +3 -1
  557. package/src/tools/network/web-fetch.ts +20 -10
  558. package/src/tools/network/web-search.ts +19 -4
  559. package/src/tools/permission-checker.ts +36 -15
  560. package/src/tools/policy-context.ts +25 -8
  561. package/src/tools/registry.ts +55 -3
  562. package/src/tools/side-effects.ts +0 -11
  563. package/src/tools/skills/execute.ts +2 -2
  564. package/src/tools/skills/sandbox-runner.ts +5 -2
  565. package/src/tools/terminal/backends/native.ts +51 -2
  566. package/src/tools/terminal/safe-env.ts +3 -2
  567. package/src/tools/terminal/shell.ts +1 -0
  568. package/src/tools/tool-manifest.ts +6 -21
  569. package/src/tools/types.ts +12 -3
  570. package/src/tools/verification-control-plane-policy.ts +1 -1
  571. package/src/tts/__tests__/provider-adapters.test.ts +240 -13
  572. package/src/tts/provider-catalog.ts +18 -0
  573. package/src/tts/providers/index.ts +2 -0
  574. package/src/tts/providers/xai-provider.ts +224 -0
  575. package/src/tts/types.ts +46 -0
  576. package/src/types/tar-stream.d.ts +66 -0
  577. package/src/util/json.ts +17 -0
  578. package/src/util/platform.ts +2 -2
  579. package/src/util/pricing.ts +15 -5
  580. package/src/watcher/engine.ts +1 -1
  581. package/src/watcher/providers/google-calendar.ts +134 -8
  582. package/src/watcher/providers/outlook-calendar.ts +42 -2
  583. package/src/workspace/git-service.ts +23 -4
  584. package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
  585. package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
  586. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
  587. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
  588. package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
  589. package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
  590. package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
  591. package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
  592. package/src/workspace/migrations/AGENTS.md +1 -1
  593. package/src/workspace/migrations/registry.ts +16 -0
  594. package/src/workspace/provider-commit-message-generator.ts +19 -38
  595. package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
  596. package/src/__tests__/gmail-archive-gate.test.ts +0 -246
  597. package/src/__tests__/gmail-preferences.test.ts +0 -117
  598. package/src/__tests__/outlook-attachments.test.ts +0 -301
  599. package/src/__tests__/outlook-automation-tools.test.ts +0 -425
  600. package/src/__tests__/outlook-categories.test.ts +0 -212
  601. package/src/__tests__/outlook-compose-tools.test.ts +0 -325
  602. package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
  603. package/src/__tests__/outlook-follow-up.test.ts +0 -196
  604. package/src/__tests__/outlook-trash.test.ts +0 -77
  605. package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
  606. package/src/__tests__/update-bulletin-format.test.ts +0 -181
  607. package/src/__tests__/update-bulletin-state.test.ts +0 -135
  608. package/src/__tests__/update-bulletin.test.ts +0 -478
  609. package/src/__tests__/update-template-contract.test.ts +0 -29
  610. package/src/cli/commands/doctor.ts +0 -341
  611. package/src/config/bundled-skills/browser/SKILL.md +0 -88
  612. package/src/config/bundled-skills/browser/TOOLS.json +0 -516
  613. package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
  614. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
  615. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
  616. package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
  617. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
  618. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
  619. package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
  620. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
  621. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
  622. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
  623. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
  624. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
  625. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
  626. package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
  627. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
  628. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
  629. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
  630. package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
  631. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
  632. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
  633. package/src/config/bundled-skills/gmail/SKILL.md +0 -221
  634. package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
  635. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
  636. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
  637. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
  638. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
  639. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
  640. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
  641. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
  642. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
  643. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
  644. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
  645. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
  646. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
  647. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
  648. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
  649. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
  650. package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
  651. package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
  652. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  653. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
  654. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
  655. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
  656. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
  657. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
  658. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
  659. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
  660. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
  661. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  662. package/src/config/bundled-skills/outlook/SKILL.md +0 -196
  663. package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
  664. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
  665. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
  666. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
  667. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
  668. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
  669. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
  670. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
  671. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
  672. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
  673. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
  674. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
  675. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
  676. package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
  677. package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
  678. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
  679. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
  680. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
  681. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
  682. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
  683. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
  684. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
  685. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
  686. package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
  687. package/src/config/bundled-skills/slack/SKILL.md +0 -108
  688. package/src/config/bundled-skills/tasks/SKILL.md +0 -37
  689. package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
  690. package/src/config/bundled-skills/tasks/icon.svg +0 -34
  691. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
  692. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
  693. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
  694. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
  695. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
  696. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
  697. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
  698. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
  699. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
  700. package/src/config/bundled-skills/watcher/SKILL.md +0 -31
  701. package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
  702. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
  703. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
  704. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
  705. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
  706. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
  707. package/src/prompts/templates/UPDATES.md +0 -50
  708. package/src/prompts/update-bulletin-format.ts +0 -85
  709. package/src/prompts/update-bulletin-state.ts +0 -58
  710. package/src/prompts/update-bulletin-template-path.ts +0 -13
  711. package/src/prompts/update-bulletin.ts +0 -139
  712. package/src/shared/provider-env-vars.ts +0 -19
  713. package/src/tools/watcher/create.ts +0 -86
  714. package/src/tools/watcher/delete.ts +0 -36
  715. package/src/tools/watcher/digest.ts +0 -54
  716. package/src/tools/watcher/list.ts +0 -83
  717. package/src/tools/watcher/update.ts +0 -71
@@ -6,13 +6,16 @@
6
6
  * POST /v1/migrations/import-preflight — dry-run import analysis of a .vbundle archive.
7
7
  * POST /v1/migrations/import — commit a .vbundle archive import to disk.
8
8
  *
9
- * Accepts raw binary body (Content-Type: application/octet-stream) or
10
- * multipart form data with a "file" field. Returns structured validation
11
- * results with is_valid flag and detailed error descriptions.
9
+ * Accepts raw binary body (Content-Type: application/octet-stream),
10
+ * multipart form data with a "file" field, or on /import only — a JSON
11
+ * body of shape `{ "url": "<signed-gcs-url>" }` that causes the daemon to
12
+ * fetch the bundle from GCS and stream it through `streamCommitImport`.
13
+ * Returns structured validation results with is_valid flag and detailed
14
+ * error descriptions.
12
15
  */
13
16
 
14
17
  import { createReadStream } from "node:fs";
15
- import { Readable } from "node:stream";
18
+ import { PassThrough, Readable } from "node:stream";
16
19
  import { Database } from "bun:sqlite";
17
20
 
18
21
  import { z } from "zod";
@@ -21,11 +24,17 @@ import { invalidateConfigCache } from "../../config/loader.js";
21
24
  import { getDb, resetDb } from "../../memory/db-connection.js";
22
25
  import { validateMigrationState } from "../../memory/migrations/validate-migration-state.js";
23
26
  import { clearCache as clearTrustCache } from "../../permissions/trust-store.js";
27
+ import { credentialKey } from "../../security/credential-key.js";
24
28
  import {
25
29
  bulkSetSecureKeysAsync,
30
+ getSecureKeyAsync,
26
31
  getSecureKeyResultAsync,
27
32
  listSecureKeysAsync,
28
33
  } from "../../security/secure-keys.js";
34
+ import {
35
+ getCredentialMetadata,
36
+ upsertCredentialMetadata,
37
+ } from "../../tools/credentials/metadata-store.js";
29
38
  import { getLogger } from "../../util/logger.js";
30
39
  import {
31
40
  getDbPath,
@@ -34,6 +43,10 @@ import {
34
43
  } from "../../util/platform.js";
35
44
  import { httpError } from "../http-errors.js";
36
45
  import type { RouteDefinition } from "../http-router.js";
46
+ import {
47
+ validateGcsSignedUrl,
48
+ type ValidateGcsSignedUrlOptions,
49
+ } from "../migrations/gcs-signed-url.js";
37
50
  import { streamExportVBundle } from "../migrations/vbundle-builder.js";
38
51
  import {
39
52
  analyzeImport,
@@ -42,11 +55,74 @@ import {
42
55
  import {
43
56
  commitImport,
44
57
  extractCredentialsFromBundle,
58
+ type ImportCommitReport,
59
+ type ImportCommitResult,
45
60
  } from "../migrations/vbundle-importer.js";
61
+ import { streamCommitImport } from "../migrations/vbundle-streaming-importer.js";
46
62
  import { validateVBundle } from "../migrations/vbundle-validator.js";
47
63
 
48
- /** Credentials with this prefix are platform-identity keys and must not be imported. */
49
- const PLATFORM_CREDENTIAL_PREFIX = "vellum:";
64
+ /**
65
+ * CES account prefix for platform-identity (`vellum:*`) credentials. Entries
66
+ * with an account that starts with this string are filtered out of any
67
+ * imported bundle so they don't overwrite the target's own Django-provisioned
68
+ * platform identity (most notably `assistant_api_key`).
69
+ *
70
+ * Derived from `credentialKey("vellum", "")` so the prefix automatically
71
+ * tracks the real CES account format — the literal string `"credential/vellum/"`.
72
+ */
73
+ const PLATFORM_CREDENTIAL_PREFIX = credentialKey("vellum", "");
74
+
75
+ /**
76
+ * Platform-identity fields that the managed runtime expects to see in CES.
77
+ * Django's post-hatch provisioning populates the first four via
78
+ * `POST /v1/secrets`; `platform_organization_id` and `platform_user_id` are
79
+ * populated by the signed-in client after hatch (onboarding, teleport,
80
+ * local→managed transfer) because Django has no signed-in user session to
81
+ * resolve them. Either set of writes can race with the import — the CES
82
+ * write survives (separate volume), but the metadata upsert may be
83
+ * clobbered by the in-place clear / atomic swap. After every import we
84
+ * reconcile metadata.json against CES so any field CES already holds a
85
+ * value for gets a matching metadata entry.
86
+ */
87
+ const VELLUM_PLATFORM_IDENTITY_FIELDS = [
88
+ "platform_base_url",
89
+ "assistant_api_key",
90
+ "platform_assistant_id",
91
+ "platform_organization_id",
92
+ "platform_user_id",
93
+ "webhook_secret",
94
+ ] as const;
95
+
96
+ /**
97
+ * Idempotent post-import reconciliation: for each vellum:* field, if CES
98
+ * has a value but metadata.json doesn't list it, upsert the entry. Pure
99
+ * add-only — never deletes anything. Safe to run whether or not Django's
100
+ * post-hatch provisioning has completed (missing CES values are skipped).
101
+ *
102
+ * Exported for direct unit-testing.
103
+ */
104
+ export async function reconcileVellumMetadataFromCes(warningSink: {
105
+ warnings: string[];
106
+ }): Promise<void> {
107
+ for (const field of VELLUM_PLATFORM_IDENTITY_FIELDS) {
108
+ try {
109
+ const value = await getSecureKeyAsync(credentialKey("vellum", field));
110
+ if (!value) continue;
111
+ if (getCredentialMetadata("vellum", field)) continue;
112
+ upsertCredentialMetadata("vellum", field, {});
113
+ log.info(
114
+ { field },
115
+ "Reconciled vellum:* metadata entry from CES after import",
116
+ );
117
+ } catch (err) {
118
+ warningSink.warnings.push(
119
+ `Failed to reconcile vellum:${field} metadata: ${
120
+ err instanceof Error ? err.message : String(err)
121
+ }`,
122
+ );
123
+ }
124
+ }
125
+ }
50
126
 
51
127
  const log = getLogger("migration-routes");
52
128
 
@@ -380,11 +456,15 @@ export async function handleMigrationImportPreflight(
380
456
  * 5. Verifies post-write integrity (SHA-256 check)
381
457
  * 6. Returns a detailed report of what was imported
382
458
  *
383
- * The file can be sent as:
459
+ * The bundle can be supplied in any of three ways:
384
460
  * - Raw binary body with Content-Type: application/octet-stream
385
461
  * - Multipart form data with a "file" field
462
+ * - JSON body `{ "url": "<signed-gcs-url>" }` (Content-Type:
463
+ * application/json). The daemon fetches and streams the archive
464
+ * through `streamCommitImport`, so peak memory stays bounded by a
465
+ * single tar entry rather than bundle size.
386
466
  *
387
- * Returns:
467
+ * Returns (all three paths):
388
468
  * 200: {
389
469
  * success: true,
390
470
  * summary: { total_files, files_created, files_overwritten, files_skipped, backups_created },
@@ -393,12 +473,25 @@ export async function handleMigrationImportPreflight(
393
473
  * warnings: [...]
394
474
  * }
395
475
  * 200: { success: false, reason: "validation_failed", errors: [...] }
396
- * 400: Standard error envelope for missing/empty body
476
+ * 400: Standard error envelope for missing/empty body or malformed URL
397
477
  * 500: Standard error envelope for unexpected failures
478
+ * 502: { success: false, reason: "fetch_failed", upstream_status?: number }
479
+ * (URL path only — upstream GCS fetch failed)
398
480
  *
399
481
  * Auth: Requires settings.write scope. Allowed for actor, svc_gateway, svc_daemon, local.
400
482
  */
401
483
  export async function handleMigrationImport(req: Request): Promise<Response> {
484
+ // JSON body means the caller is asking us to fetch the bundle from a
485
+ // signed URL and stream it through the importer. This keeps the daemon's
486
+ // peak memory bounded by one tar entry instead of bundle size, which is
487
+ // the whole point of supporting URL-based imports for large bundles.
488
+ //
489
+ // Raw-bytes path (octet-stream / multipart) is untouched below.
490
+ const contentType = req.headers.get("content-type") ?? "";
491
+ if (contentType.includes("application/json")) {
492
+ return handleMigrationImportFromUrl(req);
493
+ }
494
+
402
495
  const extracted = await extractFileData(req);
403
496
  if ("error" in extracted) {
404
497
  return extracted.error;
@@ -445,122 +538,29 @@ export async function handleMigrationImport(req: Request): Promise<Response> {
445
538
  });
446
539
 
447
540
  if (!result.ok) {
448
- if (result.reason === "validation_failed") {
449
- return Response.json({
450
- success: false,
451
- reason: "validation_failed",
452
- errors: result.errors,
453
- });
454
- }
455
-
456
- if (result.reason === "extraction_failed") {
457
- return Response.json(
458
- {
459
- success: false,
460
- reason: "extraction_failed",
461
- message: result.message,
462
- },
463
- { status: 500 },
464
- );
465
- }
466
-
467
- // write_failed
468
- return Response.json(
469
- {
470
- success: false,
471
- reason: "write_failed",
472
- message: result.message,
473
- ...(result.partial_report
474
- ? { partial_report: result.partial_report }
475
- : {}),
476
- },
477
- { status: 500 },
478
- );
541
+ return importCommitFailureResponse(result);
479
542
  }
480
543
 
481
544
  // Import credentials from the bundle into CES (non-blocking — failures
482
545
  // are logged as warnings but do not fail the overall import).
483
- let credentialsImported:
484
- | {
485
- total: number;
486
- succeeded: number;
487
- failed: number;
488
- failedAccounts: string[];
489
- skippedPlatform: number;
490
- }
491
- | undefined;
546
+ let credentialsImported: CredentialImportSummary | undefined;
492
547
 
493
548
  if (validation.entries) {
494
549
  const bundleCredentials = extractCredentialsFromBundle(
495
550
  validation.entries,
496
551
  validation.manifest!,
497
552
  );
498
-
499
- // Filter out platform-identity credentials (vellum:*) — these are
500
- // environment-specific and must not overwrite the target's own identity.
501
- const userCredentials = bundleCredentials.filter(
502
- (c) => !c.account.startsWith(PLATFORM_CREDENTIAL_PREFIX),
553
+ credentialsImported = await importBundleCredentialsIntoCes(
554
+ bundleCredentials,
555
+ result.report,
503
556
  );
504
- const skippedPlatform = bundleCredentials.length - userCredentials.length;
505
- if (skippedPlatform > 0) {
506
- log.info(
507
- `Skipped ${skippedPlatform} platform credential(s) from import`,
508
- );
509
- }
510
-
511
- if (userCredentials.length > 0) {
512
- try {
513
- const credResults = await bulkSetSecureKeysAsync(userCredentials);
514
- const failedResults = credResults.filter((r) => !r.ok);
515
- if (failedResults.length > 0) {
516
- log.warn(
517
- { failed: failedResults.map((f) => f.account) },
518
- "Some credentials failed to import",
519
- );
520
- }
521
- log.info(
522
- { total: userCredentials.length, failed: failedResults.length },
523
- "Credential import complete",
524
- );
525
- const succeeded = userCredentials.length - failedResults.length;
526
- credentialsImported = {
527
- total: userCredentials.length,
528
- succeeded,
529
- failed: failedResults.length,
530
- failedAccounts: failedResults.map((f) => f.account),
531
- skippedPlatform,
532
- };
533
- if (failedResults.length > 0) {
534
- result.report.warnings.push(
535
- `Imported ${succeeded} credential(s), ${failedResults.length} failed`,
536
- );
537
- }
538
- } catch (err) {
539
- log.warn({ err }, "Credential import failed entirely");
540
- result.report.warnings.push(
541
- `Credential import failed: ${err instanceof Error ? err.message : String(err)}`,
542
- );
543
- credentialsImported = {
544
- total: userCredentials.length,
545
- succeeded: 0,
546
- failed: userCredentials.length,
547
- failedAccounts: userCredentials.map((c) => c.account),
548
- skippedPlatform,
549
- };
550
- }
551
- } else if (skippedPlatform > 0) {
552
- // All credentials in the bundle were platform credentials — report
553
- // the skip count even though nothing was sent to CES.
554
- credentialsImported = {
555
- total: userCredentials.length,
556
- succeeded: 0,
557
- failed: 0,
558
- failedAccounts: [],
559
- skippedPlatform,
560
- };
561
- }
562
557
  }
563
558
 
559
+ // Reconcile vellum:* metadata against CES so the gateway's
560
+ // readServiceCredentials can still find platform identity values even
561
+ // if Django's post-hatch provisioning raced with the import.
562
+ await reconcileVellumMetadataFromCes(result.report);
563
+
564
564
  // Invalidate in-process caches so imported settings.json and trust.json take effect
565
565
  invalidateConfigCache();
566
566
  clearTrustCache();
@@ -569,29 +569,571 @@ export async function handleMigrationImport(req: Request): Promise<Response> {
569
569
  // a newer version. This is non-blocking — the import has already
570
570
  // succeeded — but we surface a warning so the caller knows some data may
571
571
  // not be fully compatible with this daemon's schema.
572
+ appendNewerMigrationWarningsIfAny(result.report);
573
+
574
+ return importCommitSuccessResponse(result.report, credentialsImported);
575
+ } catch (err) {
576
+ log.error({ err }, "Unexpected error during import commit");
577
+ return httpError(
578
+ "INTERNAL_ERROR",
579
+ err instanceof Error ? err.message : "Unexpected import error",
580
+ 500,
581
+ );
582
+ }
583
+ }
584
+
585
+ // ---------------------------------------------------------------------------
586
+ // URL-body variant of POST /v1/migrations/import
587
+ // ---------------------------------------------------------------------------
588
+
589
+ /** 60 minutes — matches the gateway's upstream fetch deadline. */
590
+ const URL_FETCH_TIMEOUT_MS = 60 * 60 * 1000;
591
+
592
+ const MigrationImportUrlBody = z.object({ url: z.string().min(1) });
593
+
594
+ /**
595
+ * Marker attached to errors that originate from the upstream HTTP body
596
+ * stream (peer reset, abort mid-stream, DNS/transport failure after
597
+ * headers were received). The handler's catch/result-mapping path looks
598
+ * for this tag to return 502 `fetch_failed` instead of 500
599
+ * `extraction_failed` for truncated bodies, matching the OpenAPI
600
+ * contract.
601
+ */
602
+ const kFetchBodyError = Symbol.for("vellum.migrationImport.fetchBodyError");
603
+
604
+ /**
605
+ * Sidecar flag on the wrapper PassThrough indicating that its upstream
606
+ * was torn down by a tagged fetch-body error. Checked after
607
+ * streamCommitImport returns — the importer preserves the error message
608
+ * in `result.reason = "extraction_failed"` but strips the tag.
609
+ */
610
+ const kFetchBodyTornDown = Symbol.for(
611
+ "vellum.migrationImport.fetchBodyTornDown",
612
+ );
613
+
614
+ function tagFetchBodyError(err: NodeJS.ErrnoException): void {
615
+ (err as unknown as Record<symbol, boolean>)[kFetchBodyError] = true;
616
+ }
617
+
618
+ function isFetchBodyError(err: unknown): boolean {
619
+ if (!err || typeof err !== "object") return false;
620
+ return (err as unknown as Record<symbol, boolean>)[kFetchBodyError] === true;
621
+ }
622
+
623
+ function wasFetchBodyTornDown(stream: PassThrough): boolean {
624
+ return (
625
+ (stream as unknown as Record<symbol, boolean>)[kFetchBodyTornDown] === true
626
+ );
627
+ }
628
+
629
+ /**
630
+ * Test seam: the integration test needs to point the validator at a local
631
+ * HTTP server fixture. Production callers never pass this — the default
632
+ * keeps the validator strict (GCS host, HTTPS only, no explicit port).
633
+ */
634
+ let urlValidatorOptions: ValidateGcsSignedUrlOptions | undefined;
635
+
636
+ /**
637
+ * Test-only: override the allowed-host list used by the URL-body import
638
+ * handler. Call with `undefined` (or no arguments) to reset to production
639
+ * defaults. This is intentionally not exported from the module's public
640
+ * surface — tests import it directly from this file.
641
+ */
642
+ export function _setUrlImportValidatorOptionsForTests(
643
+ options: ValidateGcsSignedUrlOptions | undefined,
644
+ ): void {
645
+ urlValidatorOptions = options;
646
+ }
647
+
648
+ /**
649
+ * Handle a JSON `{ "url": "..." }` body on POST /v1/migrations/import.
650
+ *
651
+ * Fetches the signed URL, pipes the response body through the streaming
652
+ * importer, and returns the same response shapes as the raw-bytes path.
653
+ * The signed URL is never logged or included in error responses — only the
654
+ * extracted host and path make it into logs.
655
+ */
656
+ async function handleMigrationImportFromUrl(req: Request): Promise<Response> {
657
+ // ── 1. Parse JSON body ────────────────────────────────────────────────
658
+ let rawBody: unknown;
659
+ try {
660
+ rawBody = await req.json();
661
+ } catch (err) {
662
+ log.warn(
663
+ { err: err instanceof Error ? err.message : String(err) },
664
+ "Failed to parse JSON body on migration import URL request",
665
+ );
666
+ return httpError("BAD_REQUEST", "Invalid JSON body", 400);
667
+ }
668
+
669
+ const parsed = MigrationImportUrlBody.safeParse(rawBody);
670
+ if (!parsed.success) {
671
+ return httpError(
672
+ "BAD_REQUEST",
673
+ "Request body must be { url: string } with a non-empty url",
674
+ 400,
675
+ );
676
+ }
677
+
678
+ // ── 2. Validate the URL (defense-in-depth; never log `parsed.data.url`).
679
+ const validated = validateGcsSignedUrl(parsed.data.url, urlValidatorOptions);
680
+ if (!validated.ok) {
681
+ // `reason` is a stable enum string and safe to include. The raw URL is
682
+ // not — it may contain a live signature. Callers get the reason so they
683
+ // can correct the URL without leaking anything into observability.
684
+ log.warn({ reason: validated.reason }, "Rejected migration import URL");
685
+ return httpError("BAD_REQUEST", `Invalid URL: ${validated.reason}`, 400);
686
+ }
687
+
688
+ log.info(
689
+ { host: validated.host, path: validated.path },
690
+ "migration import from URL",
691
+ );
692
+
693
+ const startedAt = Date.now();
694
+
695
+ // ── 3. Fetch the URL ──────────────────────────────────────────────────
696
+ let upstream: Response;
697
+ try {
698
+ upstream = await fetch(parsed.data.url, {
699
+ signal: AbortSignal.timeout(URL_FETCH_TIMEOUT_MS),
700
+ // SSRF guard: `validateGcsSignedUrl` only vetted the initial URL.
701
+ // Default fetch behavior follows 3xx responses, which would let a
702
+ // validated `storage.googleapis.com` URL redirect to an arbitrary
703
+ // host and bypass the allowlist. Reject redirects so we only ever
704
+ // read bytes from the URL the caller handed us.
705
+ redirect: "error",
706
+ });
707
+ } catch (err) {
708
+ log.error(
709
+ {
710
+ host: validated.host,
711
+ path: validated.path,
712
+ err: err instanceof Error ? err.message : String(err),
713
+ },
714
+ "Failed to fetch migration import URL",
715
+ );
716
+ return Response.json(
717
+ { success: false, reason: "fetch_failed" },
718
+ { status: 502 },
719
+ );
720
+ }
721
+
722
+ if (!upstream.ok) {
723
+ log.error(
724
+ {
725
+ host: validated.host,
726
+ path: validated.path,
727
+ upstream_status: upstream.status,
728
+ },
729
+ "Migration import URL fetch returned non-2xx",
730
+ );
731
+ // Drain the body so the underlying socket can be released promptly.
572
732
  try {
573
- const migrationValidation = validateMigrationState(getDb());
574
- if (migrationValidation.unknownCheckpoints.length > 0) {
575
- result.report.warnings.push(
576
- `Imported data contains ${migrationValidation.unknownCheckpoints.length} migration(s) from a newer version. Some data may not be fully compatible.`,
577
- );
578
- }
733
+ await upstream.body?.cancel();
579
734
  } catch {
580
- // Don't fail the import if validation itself errors
735
+ /* best effort */
736
+ }
737
+ return Response.json(
738
+ {
739
+ success: false,
740
+ reason: "fetch_failed",
741
+ upstream_status: upstream.status,
742
+ },
743
+ { status: 502 },
744
+ );
745
+ }
746
+
747
+ if (!upstream.body) {
748
+ log.error(
749
+ { host: validated.host, path: validated.path },
750
+ "Migration import URL fetch returned no body",
751
+ );
752
+ return Response.json(
753
+ { success: false, reason: "fetch_failed" },
754
+ { status: 502 },
755
+ );
756
+ }
757
+
758
+ // ── 4. Stream the response through the importer ──────────────────────
759
+ // Convert the WHATWG ReadableStream from fetch() into a Node Readable so
760
+ // the tar-stream / gunzip / hash-verifier pipeline inside
761
+ // streamCommitImport can consume it via `.pipe()`.
762
+ const upstreamNodeStream = Readable.fromWeb(
763
+ upstream.body as unknown as import("node:stream/web").ReadableStream<Uint8Array>,
764
+ );
765
+
766
+ // Wrap the upstream stream in a PassThrough that tags any error bubbling
767
+ // from the upstream HTTP body (peer reset, abort mid-stream, etc.) with a
768
+ // known symbol. When that tagged error surfaces out of
769
+ // streamCommitImport's gunzip/tar pipeline, we can distinguish it from a
770
+ // legitimate bundle-format failure and map it to 502 fetch_failed instead
771
+ // of 500 extraction_failed — matching the OpenAPI contract for the URL
772
+ // body shape. We also propagate errors from the wrapper back to the
773
+ // upstream stream so its underlying connection is torn down cleanly.
774
+ //
775
+ // Bun's `Readable.fromWeb(fetchBody)` does NOT emit `'error'` when the
776
+ // TCP socket is torn down mid-response — it just emits `'close'` with
777
+ // no final `'end'`. We therefore track BOTH signals:
778
+ // • explicit `'error'` → tag the error, destroy the wrapper.
779
+ // • premature `'close'` → synthesize an error, tag it, destroy the
780
+ // wrapper. "Premature" = close fired without end first.
781
+ const taggedSource = new PassThrough();
782
+ let upstreamEnded = false;
783
+ // True once the importer (or any local consumer) initiates a teardown of
784
+ // `taggedSource`. The subsequent `close` on `upstreamNodeStream` is then a
785
+ // cascaded effect of our own teardown, NOT a real upstream failure — so
786
+ // we must NOT tag it as a fetch-body error, or local validation /
787
+ // extraction errors would be masked as 502 fetch_failed.
788
+ let localTeardownInitiated = false;
789
+ upstreamNodeStream.on("end", () => {
790
+ upstreamEnded = true;
791
+ });
792
+ upstreamNodeStream.on("error", (err: NodeJS.ErrnoException) => {
793
+ tagFetchBodyError(err);
794
+ (taggedSource as unknown as Record<symbol, boolean>)[kFetchBodyTornDown] =
795
+ true;
796
+ taggedSource.destroy(err);
797
+ });
798
+ upstreamNodeStream.on("close", () => {
799
+ if (upstreamEnded) return;
800
+ // A local teardown path closed us; don't treat this as an upstream
801
+ // failure. The real error (validation / extraction / hash mismatch) is
802
+ // already propagating through `streamCommitImport`'s result.
803
+ if (localTeardownInitiated) return;
804
+ const err = new Error(
805
+ "Upstream body stream closed before end",
806
+ ) as NodeJS.ErrnoException;
807
+ err.code = "ERR_UPSTREAM_BODY_CLOSED";
808
+ tagFetchBodyError(err);
809
+ (taggedSource as unknown as Record<symbol, boolean>)[kFetchBodyTornDown] =
810
+ true;
811
+ taggedSource.destroy(err);
812
+ });
813
+ upstreamNodeStream.pipe(taggedSource);
814
+ // Propagate wrapper teardown back to the upstream fetch body. When the
815
+ // streaming importer hits a validation/extraction error, it destroys
816
+ // `source` (which is `taggedSource`). Without this listener the
817
+ // `Readable.fromWeb(fetchBody)` stream would stay alive and continue
818
+ // buffering the remote response in the background until GC or the
819
+ // 60-minute timeout — a socket/bandwidth leak for any non-upstream error
820
+ // (malformed bundle, hash mismatch, size cap, etc.). We set
821
+ // `localTeardownInitiated` BEFORE destroying upstream so the resulting
822
+ // cascaded `close` on `upstreamNodeStream` isn't misclassified as a real
823
+ // upstream failure (which would return 502 fetch_failed and mask the
824
+ // actual validation error).
825
+ taggedSource.on("close", () => {
826
+ if (!upstreamNodeStream.destroyed) {
827
+ localTeardownInitiated = true;
828
+ upstreamNodeStream.destroy();
581
829
  }
830
+ });
582
831
 
583
- return Response.json({
584
- ...result.report,
585
- ...(credentialsImported ? { credentialsImported } : {}),
832
+ const pathResolver = new DefaultPathResolver(
833
+ getWorkspaceDir(),
834
+ getWorkspaceHooksDir(),
835
+ );
836
+
837
+ // streamCommitImport does its own resetDb() internally before the atomic
838
+ // swap, so we don't need to call it here.
839
+ let result: ImportCommitResult;
840
+ // Track credential-import outcome for inclusion in the success response.
841
+ // The streaming importer invokes our callback only after the atomic swap,
842
+ // so filling this in here is safe.
843
+ let credentialsImported: CredentialImportSummary | undefined;
844
+ // Per-invocation warning collector — scoped to this request so concurrent
845
+ // URL imports can't trample each other's warnings.
846
+ const credentialImportWarningSink: CredentialWarningSink = { warnings: [] };
847
+
848
+ try {
849
+ result = await streamCommitImport({
850
+ source: taggedSource,
851
+ pathResolver,
852
+ workspaceDir: getWorkspaceDir(),
853
+ importCredentials: async (bundleCredentials) => {
854
+ // We can't mutate `result.report.warnings` in place here — the
855
+ // streaming importer hasn't returned its report yet. Accumulate
856
+ // into a sidecar and merge into the final report below.
857
+ credentialsImported = await importBundleCredentialsIntoCes(
858
+ bundleCredentials,
859
+ credentialImportWarningSink,
860
+ );
861
+ },
586
862
  });
587
863
  } catch (err) {
588
- log.error({ err }, "Unexpected error during import commit");
864
+ if (isFetchBodyError(err)) {
865
+ log.error(
866
+ {
867
+ host: validated.host,
868
+ path: validated.path,
869
+ err: err instanceof Error ? err.message : String(err),
870
+ },
871
+ "Upstream body stream failed mid-import",
872
+ );
873
+ return Response.json(
874
+ { success: false, reason: "fetch_failed" },
875
+ { status: 502 },
876
+ );
877
+ }
878
+ log.error(
879
+ {
880
+ host: validated.host,
881
+ path: validated.path,
882
+ err: err instanceof Error ? err.message : String(err),
883
+ },
884
+ "streamCommitImport threw during URL-body import",
885
+ );
589
886
  return httpError(
590
887
  "INTERNAL_ERROR",
591
888
  err instanceof Error ? err.message : "Unexpected import error",
592
889
  500,
593
890
  );
594
891
  }
892
+
893
+ if (!result.ok) {
894
+ // streamCommitImport swallows the raw cause and maps any
895
+ // non-validation throw to `extraction_failed`. If the cause was an
896
+ // upstream body failure that we tagged at the source, surface the
897
+ // tag through the result (the importer preserves the message) by
898
+ // detecting the latched flag on the wrapper stream.
899
+ if (wasFetchBodyTornDown(taggedSource)) {
900
+ log.error(
901
+ {
902
+ host: validated.host,
903
+ path: validated.path,
904
+ reason: result.reason,
905
+ },
906
+ "Upstream body stream failed mid-import (detected via result)",
907
+ );
908
+ return Response.json(
909
+ { success: false, reason: "fetch_failed" },
910
+ { status: 502 },
911
+ );
912
+ }
913
+ log.warn(
914
+ {
915
+ host: validated.host,
916
+ path: validated.path,
917
+ reason: result.reason,
918
+ },
919
+ "streamCommitImport returned failure during URL-body import",
920
+ );
921
+ return importCommitFailureResponse(result);
922
+ }
923
+
924
+ // Merge any warnings accumulated by the credential-import callback into
925
+ // the final report.
926
+ if (credentialImportWarningSink.warnings.length > 0) {
927
+ result.report.warnings.push(...credentialImportWarningSink.warnings);
928
+ }
929
+
930
+ // Reconcile vellum:* metadata against CES so the gateway's
931
+ // readServiceCredentials can still find platform identity values even
932
+ // if Django's post-hatch provisioning raced with the streaming import
933
+ // (its metadata upsert may have landed in the backup-dir copy that the
934
+ // swap pushed aside, while its CES write survived on the separate
935
+ // volume).
936
+ await reconcileVellumMetadataFromCes(result.report);
937
+
938
+ // streamCommitImport already invalidated config + trust caches inside its
939
+ // post-swap cleanup. We only need to check whether the newly-imported DB
940
+ // carries migration checkpoints from a newer daemon version.
941
+ appendNewerMigrationWarningsIfAny(result.report);
942
+
943
+ const elapsedMs = Date.now() - startedAt;
944
+ log.info(
945
+ {
946
+ host: validated.host,
947
+ path: validated.path,
948
+ files_written: result.report.summary.files_created,
949
+ bytes_written: result.report.files.reduce((n, f) => n + f.size, 0),
950
+ elapsed_ms: elapsedMs,
951
+ },
952
+ "Migration import from URL complete",
953
+ );
954
+
955
+ return importCommitSuccessResponse(result.report, credentialsImported);
956
+ }
957
+
958
+ // ---------------------------------------------------------------------------
959
+ // Shared helpers for raw-bytes and URL paths
960
+ // ---------------------------------------------------------------------------
961
+
962
+ interface CredentialImportSummary {
963
+ total: number;
964
+ succeeded: number;
965
+ failed: number;
966
+ failedAccounts: string[];
967
+ skippedPlatform: number;
968
+ }
969
+
970
+ /**
971
+ * Minimal surface the credential-import helper needs to stash warnings —
972
+ * either a full `ImportCommitReport` (raw-bytes path, after commitImport
973
+ * returns) or an ephemeral per-request collector (streaming path, where the
974
+ * report doesn't exist yet when the callback fires).
975
+ */
976
+ interface CredentialWarningSink {
977
+ warnings: string[];
978
+ }
979
+
980
+ /**
981
+ * Filter platform-identity (vellum:*) credentials out of the bundle, push
982
+ * user credentials into CES via `bulkSetSecureKeysAsync`, and return a
983
+ * structured summary. Never throws — CES failures become report warnings.
984
+ */
985
+ async function importBundleCredentialsIntoCes(
986
+ bundleCredentials: Array<{ account: string; value: string }>,
987
+ warningSink: CredentialWarningSink,
988
+ ): Promise<CredentialImportSummary | undefined> {
989
+ // Filter out platform-identity credentials (vellum:*) — these are
990
+ // environment-specific and must not overwrite the target's own identity.
991
+ const userCredentials = bundleCredentials.filter(
992
+ (c) => !c.account.startsWith(PLATFORM_CREDENTIAL_PREFIX),
993
+ );
994
+ const skippedPlatform = bundleCredentials.length - userCredentials.length;
995
+ if (skippedPlatform > 0) {
996
+ log.info(`Skipped ${skippedPlatform} platform credential(s) from import`);
997
+ }
998
+
999
+ if (userCredentials.length === 0) {
1000
+ if (skippedPlatform > 0) {
1001
+ // All credentials in the bundle were platform credentials — report
1002
+ // the skip count even though nothing was sent to CES.
1003
+ return {
1004
+ total: 0,
1005
+ succeeded: 0,
1006
+ failed: 0,
1007
+ failedAccounts: [],
1008
+ skippedPlatform,
1009
+ };
1010
+ }
1011
+ return undefined;
1012
+ }
1013
+
1014
+ try {
1015
+ const credResults = await bulkSetSecureKeysAsync(userCredentials);
1016
+ const failedResults = credResults.filter((r) => !r.ok);
1017
+ if (failedResults.length > 0) {
1018
+ log.warn(
1019
+ { failed: failedResults.map((f) => f.account) },
1020
+ "Some credentials failed to import",
1021
+ );
1022
+ }
1023
+ log.info(
1024
+ { total: userCredentials.length, failed: failedResults.length },
1025
+ "Credential import complete",
1026
+ );
1027
+ const succeeded = userCredentials.length - failedResults.length;
1028
+ if (failedResults.length > 0) {
1029
+ warningSink.warnings.push(
1030
+ `Imported ${succeeded} credential(s), ${failedResults.length} failed`,
1031
+ );
1032
+ }
1033
+ return {
1034
+ total: userCredentials.length,
1035
+ succeeded,
1036
+ failed: failedResults.length,
1037
+ failedAccounts: failedResults.map((f) => f.account),
1038
+ skippedPlatform,
1039
+ };
1040
+ } catch (err) {
1041
+ log.warn({ err }, "Credential import failed entirely");
1042
+ warningSink.warnings.push(
1043
+ `Credential import failed: ${err instanceof Error ? err.message : String(err)}`,
1044
+ );
1045
+ return {
1046
+ total: userCredentials.length,
1047
+ succeeded: 0,
1048
+ failed: userCredentials.length,
1049
+ failedAccounts: userCredentials.map((c) => c.account),
1050
+ skippedPlatform,
1051
+ };
1052
+ }
1053
+ }
1054
+
1055
+ /**
1056
+ * Append a warning to `report` when the newly-imported database contains
1057
+ * migration checkpoints from a daemon version newer than this one. Silent
1058
+ * on any validation error — the import has already succeeded.
1059
+ *
1060
+ * Gated on the report's own file counts: if the import didn't create or
1061
+ * overwrite any workspace files (no-swap success — e.g. credentials-only
1062
+ * bundle, all-skipped legacy bundle), the live DB is unchanged and any
1063
+ * "newer migrations" detected there came from the existing workspace,
1064
+ * NOT from the imported bundle. Attributing them to the bundle would be a
1065
+ * false positive, so skip the check entirely in that case.
1066
+ */
1067
+ function appendNewerMigrationWarningsIfAny(report: ImportCommitReport): void {
1068
+ if (report.summary.files_created + report.summary.files_overwritten === 0) {
1069
+ return;
1070
+ }
1071
+ try {
1072
+ const migrationValidation = validateMigrationState(getDb());
1073
+ if (migrationValidation.unknownCheckpoints.length > 0) {
1074
+ report.warnings.push(
1075
+ `Imported data contains ${migrationValidation.unknownCheckpoints.length} migration(s) from a newer version. Some data may not be fully compatible.`,
1076
+ );
1077
+ }
1078
+ } catch {
1079
+ // Don't fail the import if validation itself errors
1080
+ }
1081
+ }
1082
+
1083
+ /**
1084
+ * Build a success Response from an ImportCommitReport. The report fields
1085
+ * are spread at the top level, with an optional `credentialsImported`
1086
+ * summary alongside.
1087
+ */
1088
+ function importCommitSuccessResponse(
1089
+ report: ImportCommitReport,
1090
+ credentialsImported: CredentialImportSummary | undefined,
1091
+ ): Response {
1092
+ return Response.json({
1093
+ ...report,
1094
+ ...(credentialsImported ? { credentialsImported } : {}),
1095
+ });
1096
+ }
1097
+
1098
+ /**
1099
+ * Map an `ImportCommitResult` failure to the Response shape callers of
1100
+ * `POST /v1/migrations/import` depend on. Status codes and body shapes
1101
+ * are part of the public contract and must remain stable.
1102
+ */
1103
+ function importCommitFailureResponse(
1104
+ result: Extract<ImportCommitResult, { ok: false }>,
1105
+ ): Response {
1106
+ if (result.reason === "validation_failed") {
1107
+ return Response.json({
1108
+ success: false,
1109
+ reason: "validation_failed",
1110
+ errors: result.errors,
1111
+ });
1112
+ }
1113
+
1114
+ if (result.reason === "extraction_failed") {
1115
+ return Response.json(
1116
+ {
1117
+ success: false,
1118
+ reason: "extraction_failed",
1119
+ message: result.message,
1120
+ },
1121
+ { status: 500 },
1122
+ );
1123
+ }
1124
+
1125
+ // write_failed
1126
+ return Response.json(
1127
+ {
1128
+ success: false,
1129
+ reason: "write_failed",
1130
+ message: result.message,
1131
+ ...(result.partial_report
1132
+ ? { partial_report: result.partial_report }
1133
+ : {}),
1134
+ },
1135
+ { status: 500 },
1136
+ );
595
1137
  }
596
1138
 
597
1139
  // ---------------------------------------------------------------------------
@@ -647,8 +1189,62 @@ export function migrationRouteDefinitions(): RouteDefinition[] {
647
1189
  method: "POST",
648
1190
  summary: "Import a .vbundle archive",
649
1191
  description:
650
- "Commit a .vbundle archive import to disk — destructive. Backs up existing files before overwriting.",
1192
+ "Commit a .vbundle archive import to disk — destructive. Accepts the bundle as raw bytes (application/octet-stream), multipart/form-data, or a JSON body carrying a signed URL the daemon fetches and streams through the importer.",
651
1193
  tags: ["migrations"],
1194
+ requestBodies: [
1195
+ {
1196
+ contentType: "application/octet-stream",
1197
+ schema: {
1198
+ type: "string",
1199
+ format: "binary",
1200
+ description: "Raw .vbundle archive bytes.",
1201
+ },
1202
+ },
1203
+ {
1204
+ contentType: "multipart/form-data",
1205
+ schema: {
1206
+ type: "object",
1207
+ properties: {
1208
+ file: {
1209
+ type: "string",
1210
+ format: "binary",
1211
+ description: "The .vbundle archive uploaded as a file field.",
1212
+ },
1213
+ },
1214
+ required: ["file"],
1215
+ },
1216
+ },
1217
+ {
1218
+ contentType: "application/json",
1219
+ schema: {
1220
+ type: "object",
1221
+ properties: {
1222
+ url: {
1223
+ type: "string",
1224
+ format: "uri",
1225
+ description:
1226
+ "A signed GCS URL pointing to the .vbundle archive. The daemon fetches the URL and streams the body through the importer.",
1227
+ },
1228
+ },
1229
+ required: ["url"],
1230
+ },
1231
+ },
1232
+ ],
1233
+ additionalResponses: {
1234
+ "502": {
1235
+ description:
1236
+ "Upstream fetch failed (URL body only). Body shape: { success: false, reason: 'fetch_failed', upstream_status?: number }.",
1237
+ schema: {
1238
+ type: "object",
1239
+ properties: {
1240
+ success: { type: "boolean" },
1241
+ reason: { type: "string", enum: ["fetch_failed"] },
1242
+ upstream_status: { type: "integer" },
1243
+ },
1244
+ required: ["success", "reason"],
1245
+ },
1246
+ },
1247
+ },
652
1248
  responseBody: z.object({
653
1249
  success: z.boolean(),
654
1250
  summary: z.object({}).passthrough(),