@vellumai/assistant 0.7.1 → 0.7.3

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 (739) hide show
  1. package/ARCHITECTURE.md +48 -50
  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/memory.md +5 -2
  7. package/docs/architecture/security.md +20 -0
  8. package/docs/plugins.md +7 -9
  9. package/knip.json +1 -0
  10. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
  11. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +52 -5
  12. package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
  13. package/node_modules/@vellumai/service-contracts/package.json +2 -0
  14. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
  15. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
  16. package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
  17. package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
  18. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
  19. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  20. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  21. package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
  22. package/node_modules/@vellumai/twilio-client/package.json +18 -0
  23. package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
  24. package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
  25. package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
  26. package/openapi.yaml +1020 -40
  27. package/package.json +6 -3
  28. package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
  29. package/src/__tests__/app-bundler.test.ts +170 -1
  30. package/src/__tests__/app-control-flow.test.ts +384 -0
  31. package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
  32. package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
  33. package/src/__tests__/app-executors.test.ts +30 -43
  34. package/src/__tests__/approval-routes-http.test.ts +23 -6
  35. package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
  36. package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
  37. package/src/__tests__/assistant-event-hub.test.ts +157 -2
  38. package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -7
  39. package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
  40. package/src/__tests__/background-shell-host-bash.test.ts +14 -15
  41. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  42. package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
  43. package/src/__tests__/btw-routes.test.ts +13 -4
  44. package/src/__tests__/call-controller.test.ts +49 -1
  45. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  46. package/src/__tests__/call-domain.test.ts +0 -2
  47. package/src/__tests__/call-routes-http.test.ts +0 -2
  48. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  49. package/src/__tests__/channel-readiness-service.test.ts +62 -2
  50. package/src/__tests__/checker.test.ts +3 -4
  51. package/src/__tests__/config-loader-backfill.test.ts +461 -147
  52. package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
  53. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  54. package/src/__tests__/config-schema.test.ts +1 -0
  55. package/src/__tests__/config-set-platform-guard.test.ts +48 -4
  56. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +20 -11
  57. package/src/__tests__/config-watcher.test.ts +142 -71
  58. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  59. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  60. package/src/__tests__/context-search-fanout.test.ts +0 -1
  61. package/src/__tests__/context-search-memory-source.test.ts +3 -7
  62. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  63. package/src/__tests__/context-search-pkb-source.test.ts +0 -1
  64. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  65. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  66. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  67. package/src/__tests__/conversation-agent-loop.test.ts +454 -5
  68. package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
  69. package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
  70. package/src/__tests__/conversation-error.test.ts +150 -3
  71. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  72. package/src/__tests__/conversation-lifecycle.test.ts +36 -0
  73. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
  74. package/src/__tests__/conversation-process-callsite.test.ts +43 -0
  75. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  76. package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
  77. package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
  78. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  79. package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
  80. package/src/__tests__/conversation-slash-commands.test.ts +0 -4
  81. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  82. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  83. package/src/__tests__/conversation-store.test.ts +0 -18
  84. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
  85. package/src/__tests__/conversation-surfaces-app-control.test.ts +328 -0
  86. package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
  87. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
  88. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  89. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  90. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
  91. package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
  92. package/src/__tests__/credentials-cli.test.ts +12 -12
  93. package/src/__tests__/cu-unified-flow.test.ts +351 -23
  94. package/src/__tests__/daemon-credential-client.test.ts +101 -19
  95. package/src/__tests__/date-context.test.ts +164 -2
  96. package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
  97. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  98. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  99. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  100. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  101. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  102. package/src/__tests__/disk-usage.test.ts +150 -0
  103. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  104. package/src/__tests__/events-client-registration.test.ts +52 -0
  105. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  106. package/src/__tests__/file-write-tool.test.ts +4 -10
  107. package/src/__tests__/filing-service.test.ts +3 -4
  108. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  109. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
  110. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
  111. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
  112. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  113. package/src/__tests__/heartbeat-service.test.ts +968 -2
  114. package/src/__tests__/helpers/call-route-handler.ts +7 -1
  115. package/src/__tests__/host-app-control-proxy.test.ts +772 -0
  116. package/src/__tests__/host-app-control-routes.test.ts +263 -0
  117. package/src/__tests__/host-bash-proxy.test.ts +439 -47
  118. package/src/__tests__/host-bash-routes.test.ts +459 -0
  119. package/src/__tests__/host-browser-proxy.test.ts +24 -22
  120. package/src/__tests__/host-browser-routes.test.ts +39 -13
  121. package/src/__tests__/host-cu-proxy.test.ts +248 -52
  122. package/src/__tests__/host-cu-routes-targeted.test.ts +429 -0
  123. package/src/__tests__/host-file-edit-tool.test.ts +47 -1
  124. package/src/__tests__/host-file-proxy-targeted.test.ts +378 -0
  125. package/src/__tests__/host-file-proxy.test.ts +301 -45
  126. package/src/__tests__/host-file-read-tool.test.ts +17 -0
  127. package/src/__tests__/host-file-routes-targeted.test.ts +420 -0
  128. package/src/__tests__/host-file-write-tool.test.ts +42 -1
  129. package/src/__tests__/host-proxy-base.test.ts +312 -0
  130. package/src/__tests__/host-shell-tool.test.ts +22 -4
  131. package/src/__tests__/host-transfer-proxy-targeted.test.ts +932 -0
  132. package/src/__tests__/host-transfer-proxy.test.ts +121 -22
  133. package/src/__tests__/host-transfer-routes-targeted.test.ts +662 -0
  134. package/src/__tests__/http-user-message-parity.test.ts +108 -1
  135. package/src/__tests__/identity-intro-cache.test.ts +29 -0
  136. package/src/__tests__/identity-routes.test.ts +103 -1
  137. package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
  138. package/src/__tests__/injector-chain.test.ts +18 -6
  139. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  140. package/src/__tests__/inline-command-runner.test.ts +0 -1
  141. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
  142. package/src/__tests__/integration-status.test.ts +85 -5
  143. package/src/__tests__/intent-routing.test.ts +0 -1
  144. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
  145. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
  146. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  147. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  148. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  149. package/src/__tests__/mcp-auth-routes.test.ts +197 -0
  150. package/src/__tests__/mcp-cli.test.ts +338 -2
  151. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  152. package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
  153. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  154. package/src/__tests__/migration-import-commit-http.test.ts +108 -2
  155. package/src/__tests__/mock-gateway-ipc.ts +1 -0
  156. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  157. package/src/__tests__/oauth-cli.test.ts +0 -2
  158. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  159. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  160. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
  161. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  162. package/src/__tests__/openai-provider.test.ts +45 -8
  163. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  164. package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
  165. package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
  166. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  167. package/src/__tests__/platform.test.ts +2 -1
  168. package/src/__tests__/playbook-execution.test.ts +0 -43
  169. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  170. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -25
  171. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  172. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  173. package/src/__tests__/provider-tool-name.test.ts +23 -0
  174. package/src/__tests__/public-ingress-urls.test.ts +97 -0
  175. package/src/__tests__/relay-server.test.ts +15 -4
  176. package/src/__tests__/require-fresh-approval.test.ts +0 -1
  177. package/src/__tests__/retry-backoff.test.ts +87 -0
  178. package/src/__tests__/runtime-events-sse.test.ts +2 -2
  179. package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
  180. package/src/__tests__/schedule-retry.test.ts +715 -0
  181. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  182. package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
  183. package/src/__tests__/secret-ingress-http.test.ts +1 -1
  184. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  185. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  186. package/src/__tests__/skill-feature-flags.test.ts +43 -41
  187. package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
  188. package/src/__tests__/skill-load-inline-command.test.ts +0 -51
  189. package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
  190. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  191. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  192. package/src/__tests__/slack-channel-config.test.ts +9 -14
  193. package/src/__tests__/suggestion-routes.test.ts +46 -0
  194. package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
  195. package/src/__tests__/system-prompt.test.ts +0 -1
  196. package/src/__tests__/telegram-config.test.ts +0 -1
  197. package/src/__tests__/test-preload.ts +8 -0
  198. package/src/__tests__/tool-approval-handler.test.ts +3 -4
  199. package/src/__tests__/tool-audit-listener.test.ts +48 -0
  200. package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
  201. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  202. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  203. package/src/__tests__/tool-executor.test.ts +0 -1
  204. package/src/__tests__/twilio-config.test.ts +3 -16
  205. package/src/__tests__/twilio-routes.test.ts +3 -5
  206. package/src/__tests__/twilio-validation.test.ts +93 -0
  207. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
  208. package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
  209. package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
  210. package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
  211. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  212. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  213. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  214. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
  215. package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
  216. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
  217. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
  218. package/src/approvals/guardian-decision-primitive.ts +13 -0
  219. package/src/approvals/guardian-request-resolvers.ts +16 -17
  220. package/src/backup/__tests__/paths.test.ts +0 -22
  221. package/src/backup/__tests__/restore.test.ts +51 -151
  222. package/src/backup/paths.ts +2 -18
  223. package/src/backup/restore.ts +107 -231
  224. package/src/backup/snapshot-lock.ts +2 -27
  225. package/src/bundler/app-bundler.ts +51 -3
  226. package/src/bundler/compiler-tools.ts +3 -2
  227. package/src/calls/call-conversation-messages.ts +46 -10
  228. package/src/calls/relay-server.ts +4 -44
  229. package/src/calls/twilio-config.ts +2 -17
  230. package/src/calls/twilio-rest.ts +33 -105
  231. package/src/calls/twilio-routes.ts +11 -12
  232. package/src/channels/types.ts +8 -7
  233. package/src/cli/commands/__tests__/backup.test.ts +6 -277
  234. package/src/cli/commands/__tests__/gateway.test.ts +288 -0
  235. package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
  236. package/src/cli/commands/__tests__/webhooks.test.ts +0 -5
  237. package/src/cli/commands/backup.ts +6 -331
  238. package/src/cli/commands/bash.ts +35 -108
  239. package/src/cli/commands/clients.ts +36 -37
  240. package/src/cli/commands/contacts.ts +137 -25
  241. package/src/cli/commands/conversations.ts +2 -5
  242. package/src/cli/commands/credentials.ts +71 -7
  243. package/src/cli/commands/domain.ts +66 -15
  244. package/src/cli/commands/gateway.ts +183 -0
  245. package/src/cli/commands/keys.ts +9 -6
  246. package/src/cli/commands/mcp.ts +116 -156
  247. package/src/cli/commands/memory-v2.ts +303 -7
  248. package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
  249. package/src/cli/commands/oauth/connect.ts +127 -1
  250. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -4
  251. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -3
  252. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -3
  253. package/src/cli/commands/platform/__tests__/status.test.ts +116 -21
  254. package/src/cli/commands/platform/disconnect.ts +5 -4
  255. package/src/cli/commands/platform/index.ts +16 -25
  256. package/src/cli/commands/status.ts +57 -0
  257. package/src/cli/lib/daemon-credential-client.ts +110 -28
  258. package/src/cli/program.ts +6 -2
  259. package/src/config/assistant-feature-flags.ts +79 -12
  260. package/src/config/bundled-skills/acp/SKILL.md +6 -0
  261. package/src/config/bundled-skills/acp/TOOLS.json +1 -22
  262. package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
  263. package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
  264. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
  265. package/src/config/bundled-skills/app-control/SKILL.md +75 -0
  266. package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
  267. package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
  268. package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
  269. package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
  270. package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
  271. package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
  272. package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
  273. package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
  274. package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
  275. package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
  276. package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
  277. package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
  278. package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
  279. package/src/config/bundled-skills/document/TOOLS.json +0 -8
  280. package/src/config/bundled-skills/followups/TOOLS.json +0 -12
  281. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  282. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
  283. package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
  284. package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
  285. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  286. package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
  287. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +25 -4
  288. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
  289. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  290. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  291. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  292. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  293. package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
  294. package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
  295. package/src/config/bundled-skills/settings/SKILL.md +4 -0
  296. package/src/config/bundled-skills/settings/TOOLS.json +0 -12
  297. package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
  298. package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
  299. package/src/config/bundled-skills/subagent/SKILL.md +6 -2
  300. package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
  301. package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
  302. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
  303. package/src/config/bundled-tool-registry.ts +21 -0
  304. package/src/config/env-registry.ts +0 -2
  305. package/src/config/env.ts +19 -20
  306. package/src/config/feature-flag-registry.json +47 -135
  307. package/src/config/loader.ts +197 -104
  308. package/src/config/sanitize-for-transfer.ts +2 -0
  309. package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
  310. package/src/config/schemas/__tests__/memory-v2.test.ts +17 -9
  311. package/src/config/schemas/call-site-catalog.ts +14 -0
  312. package/src/config/schemas/calls.ts +0 -9
  313. package/src/config/schemas/channels.ts +0 -5
  314. package/src/config/schemas/heartbeat.ts +64 -1
  315. package/src/config/schemas/ingress.ts +10 -6
  316. package/src/config/schemas/llm.ts +7 -10
  317. package/src/config/schemas/memory-lifecycle.ts +90 -24
  318. package/src/config/schemas/memory-v2.ts +121 -13
  319. package/src/config/schemas/platform.ts +49 -3
  320. package/src/config/schemas/services.ts +29 -15
  321. package/src/config/schemas/skills.ts +0 -6
  322. package/src/config/seed-inference-profiles.ts +230 -33
  323. package/src/contacts/contact-store.ts +0 -55
  324. package/src/contacts/contacts-write.ts +0 -27
  325. package/src/context/window-manager.ts +1 -2
  326. package/src/credential-execution/feature-gates.ts +10 -10
  327. package/src/credential-execution/process-manager.ts +12 -41
  328. package/src/daemon/__tests__/conversation-tool-setup.test.ts +187 -5
  329. package/src/daemon/assistant-attachments.ts +4 -4
  330. package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
  331. package/src/daemon/config-watcher.ts +89 -60
  332. package/src/daemon/conversation-agent-loop-handlers.ts +27 -3
  333. package/src/daemon/conversation-agent-loop.ts +202 -61
  334. package/src/daemon/conversation-error.ts +87 -15
  335. package/src/daemon/conversation-lifecycle.ts +9 -4
  336. package/src/daemon/conversation-process.ts +24 -11
  337. package/src/daemon/conversation-runtime-assembly.ts +28 -2
  338. package/src/daemon/conversation-store.ts +2 -2
  339. package/src/daemon/conversation-surfaces.ts +305 -4
  340. package/src/daemon/conversation-tool-setup.ts +66 -62
  341. package/src/daemon/conversation.ts +38 -24
  342. package/src/daemon/date-context.ts +71 -22
  343. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  344. package/src/daemon/disk-pressure-guard.ts +343 -0
  345. package/src/daemon/disk-pressure-policy.ts +163 -0
  346. package/src/daemon/doordash-steps.ts +1 -1
  347. package/src/daemon/handlers/shared.ts +4 -2
  348. package/src/daemon/handlers/skills.ts +3 -4
  349. package/src/daemon/host-app-control-proxy.ts +389 -0
  350. package/src/daemon/host-bash-proxy.ts +117 -82
  351. package/src/daemon/host-browser-proxy.ts +67 -82
  352. package/src/daemon/host-cu-proxy.ts +127 -86
  353. package/src/daemon/host-file-proxy.ts +129 -69
  354. package/src/daemon/host-proxy-base.ts +294 -0
  355. package/src/daemon/host-proxy-preactivation.ts +82 -0
  356. package/src/daemon/host-transfer-proxy.ts +338 -129
  357. package/src/daemon/lifecycle.ts +194 -145
  358. package/src/daemon/meet-host-supervisor.ts +4 -4
  359. package/src/daemon/meet-manifest-loader.ts +0 -1
  360. package/src/daemon/memory-v2-startup.ts +14 -4
  361. package/src/daemon/message-protocol.ts +6 -8
  362. package/src/daemon/message-types/contacts.ts +23 -1
  363. package/src/daemon/message-types/conversations.ts +15 -8
  364. package/src/daemon/message-types/disk-pressure.ts +9 -0
  365. package/src/daemon/message-types/host-app-control.ts +150 -0
  366. package/src/daemon/message-types/host-bash.ts +4 -0
  367. package/src/daemon/message-types/host-cu.ts +2 -0
  368. package/src/daemon/message-types/host-file.ts +4 -0
  369. package/src/daemon/message-types/host-transfer.ts +3 -0
  370. package/src/daemon/message-types/messages.ts +3 -0
  371. package/src/daemon/message-types/schedules.ts +8 -3
  372. package/src/daemon/message-types/skills.ts +2 -2
  373. package/src/daemon/process-message.ts +18 -1
  374. package/src/daemon/profiler-run-store.ts +5 -5
  375. package/src/daemon/shutdown-handlers.ts +0 -3
  376. package/src/daemon/tool-setup-types.ts +51 -0
  377. package/src/daemon/tool-side-effects.ts +1 -1
  378. package/src/documents/document-store.ts +85 -0
  379. package/src/events/tool-audit-listener.ts +2 -1
  380. package/src/filing/filing-service.ts +30 -5
  381. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +24 -23
  382. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +252 -0
  383. package/src/heartbeat/heartbeat-run-store.ts +249 -0
  384. package/src/heartbeat/heartbeat-service.ts +459 -54
  385. package/src/home/__tests__/post-connect-feed.test.ts +99 -0
  386. package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
  387. package/src/home/__tests__/suggested-prompts.test.ts +89 -0
  388. package/src/home/feed-scheduler.ts +18 -0
  389. package/src/home/post-connect-feed.ts +68 -0
  390. package/src/home/relationship-state-writer.ts +17 -92
  391. package/src/home/suggested-prompts.ts +46 -10
  392. package/src/inbound/platform-callback-registration.ts +8 -15
  393. package/src/inbound/public-ingress-urls.ts +32 -34
  394. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  395. package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
  396. package/src/ipc/assistant-server.ts +70 -3
  397. package/src/ipc/cli-client.ts +32 -1
  398. package/src/ipc/gateway-client.ts +37 -3
  399. package/src/live-voice/live-voice-archive.ts +4 -4
  400. package/src/live-voice/live-voice-metrics.ts +10 -10
  401. package/src/live-voice/protocol.ts +5 -7
  402. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
  403. package/src/mcp/mcp-auth-orchestrator.ts +213 -0
  404. package/src/mcp/mcp-auth-state.ts +133 -0
  405. package/src/mcp/mcp-oauth-provider.ts +19 -0
  406. package/src/media/image-service.ts +1 -7
  407. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  408. package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
  409. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
  410. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  411. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  412. package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
  413. package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
  414. package/src/memory/admin.ts +5 -9
  415. package/src/memory/anisotropy.test.ts +247 -0
  416. package/src/memory/anisotropy.ts +443 -0
  417. package/src/memory/auto-analysis-constants.ts +17 -0
  418. package/src/memory/auto-analysis-guard.ts +5 -15
  419. package/src/memory/canonical-guardian-store.ts +7 -7
  420. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
  421. package/src/memory/context-search/agent-protocol.ts +6 -6
  422. package/src/memory/context-search/agent-runner.ts +51 -9
  423. package/src/memory/context-search/sources/conversations.ts +2 -11
  424. package/src/memory/context-search/sources/memory-v2.ts +22 -9
  425. package/src/memory/context-search/sources/memory.ts +0 -1
  426. package/src/memory/context-search/types.ts +0 -1
  427. package/src/memory/conversation-crud.ts +5 -13
  428. package/src/memory/conversation-key-store.ts +2 -15
  429. package/src/memory/db-init.ts +6 -0
  430. package/src/memory/embedding-backend.ts +9 -21
  431. package/src/memory/embedding-runtime-manager.ts +119 -5
  432. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +81 -25
  433. package/src/memory/graph/conversation-graph-memory.ts +43 -78
  434. package/src/memory/graph/extraction.ts +1 -3
  435. package/src/memory/graph/graph-search.test.ts +10 -67
  436. package/src/memory/graph/graph-search.ts +9 -20
  437. package/src/memory/graph/retriever.test.ts +6 -0
  438. package/src/memory/graph/retriever.ts +34 -10
  439. package/src/memory/graph/tools.ts +1 -1
  440. package/src/memory/indexer.ts +54 -45
  441. package/src/memory/job-handlers/backfill.ts +2 -11
  442. package/src/memory/job-handlers/cleanup.ts +43 -0
  443. package/src/memory/job-handlers/embedding.ts +6 -8
  444. package/src/memory/job-handlers/summarization.ts +2 -7
  445. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
  446. package/src/memory/jobs/embed-concept-page.ts +28 -2
  447. package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
  448. package/src/memory/jobs-store.ts +114 -22
  449. package/src/memory/jobs-worker.ts +193 -106
  450. package/src/memory/memory-v2-activation-log-store.ts +33 -15
  451. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  452. package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
  453. package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
  454. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  455. package/src/memory/migrations/index.ts +6 -0
  456. package/src/memory/migrations/registry.ts +8 -0
  457. package/src/memory/pkb/pkb-search.test.ts +6 -0
  458. package/src/memory/pkb/pkb-search.ts +7 -0
  459. package/src/memory/qdrant-client.ts +49 -32
  460. package/src/memory/rerank-local.ts +374 -0
  461. package/src/memory/schema/infrastructure.ts +15 -0
  462. package/src/memory/search/semantic.ts +13 -67
  463. package/src/memory/sparse-tokenize.ts +49 -0
  464. package/src/memory/trace-event-store.ts +1 -17
  465. package/src/memory/v2/__tests__/activation.test.ts +387 -344
  466. package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
  467. package/src/memory/v2/__tests__/injection.test.ts +181 -169
  468. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  469. package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
  470. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  471. package/src/memory/v2/__tests__/sim.test.ts +154 -188
  472. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  473. package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
  474. package/src/memory/v2/__tests__/static-context.test.ts +76 -2
  475. package/src/memory/v2/activation.ts +213 -239
  476. package/src/memory/v2/consolidation-job.ts +65 -17
  477. package/src/memory/v2/constants.ts +7 -0
  478. package/src/memory/v2/injection.ts +123 -103
  479. package/src/memory/v2/prompts/consolidation.ts +348 -92
  480. package/src/memory/v2/qdrant.ts +198 -1
  481. package/src/memory/v2/reranker.ts +177 -0
  482. package/src/memory/v2/sim.ts +113 -77
  483. package/src/memory/v2/skill-content.ts +4 -3
  484. package/src/memory/v2/skill-store.ts +91 -53
  485. package/src/memory/v2/sparse-bm25.ts +245 -0
  486. package/src/memory/v2/static-context.ts +28 -5
  487. package/src/memory/v2/types.ts +10 -10
  488. package/src/messaging/providers/gmail/types.ts +0 -49
  489. package/src/messaging/providers/slack/adapter.ts +1 -31
  490. package/src/messaging/providers/slack/types.ts +0 -32
  491. package/src/notifications/README.md +10 -10
  492. package/src/notifications/broadcaster.ts +1 -1
  493. package/src/notifications/copy-composer.ts +13 -0
  494. package/src/notifications/guardian-question-mode.ts +5 -5
  495. package/src/notifications/signal.ts +4 -0
  496. package/src/oauth/AGENTS.md +3 -1
  497. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  498. package/src/oauth/connect-orchestrator.ts +6 -0
  499. package/src/oauth/connection-resolver.test.ts +66 -1
  500. package/src/oauth/connection-resolver.ts +55 -1
  501. package/src/oauth/credential-token-resolver.ts +1 -3
  502. package/src/oauth/manual-token-connection.ts +0 -4
  503. package/src/oauth/oauth-connect-state.ts +77 -0
  504. package/src/oauth/seed-providers.ts +58 -1
  505. package/src/outbound-proxy/index.ts +1 -37
  506. package/src/outbound-proxy/logging.ts +1 -1
  507. package/src/outbound-proxy/policy.ts +6 -5
  508. package/src/outbound-proxy/router.ts +2 -1
  509. package/src/permissions/approval-policy.test.ts +6 -275
  510. package/src/permissions/approval-policy.ts +0 -51
  511. package/src/permissions/checker.test.ts +0 -1
  512. package/src/permissions/checker.ts +3 -17
  513. package/src/permissions/gateway-threshold-reader.ts +2 -0
  514. package/src/permissions/prompter.ts +34 -1
  515. package/src/permissions/secret-prompter.ts +6 -2
  516. package/src/plugins/defaults/injectors.ts +35 -2
  517. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  518. package/src/plugins/types.ts +7 -0
  519. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  520. package/src/proactive-artifact/decision.test.ts +226 -0
  521. package/src/proactive-artifact/decision.ts +165 -0
  522. package/src/proactive-artifact/index.ts +7 -0
  523. package/src/proactive-artifact/job.test.ts +867 -0
  524. package/src/proactive-artifact/job.ts +352 -0
  525. package/src/proactive-artifact/message-copy.ts +41 -0
  526. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  527. package/src/proactive-artifact/trigger-state.ts +119 -0
  528. package/src/prompts/bootstrap-cleanup.ts +27 -0
  529. package/src/prompts/normalize-onboarding.ts +80 -0
  530. package/src/prompts/persona-resolver.ts +101 -9
  531. package/src/prompts/system-prompt.ts +23 -24
  532. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  533. package/src/prompts/templates/SOUL.md +13 -1
  534. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  535. package/src/providers/model-intents.ts +7 -0
  536. package/src/providers/openrouter/client.ts +8 -0
  537. package/src/providers/retry.ts +50 -0
  538. package/src/providers/speech-to-text/provider-catalog.ts +7 -8
  539. package/src/providers/types.ts +1 -0
  540. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  541. package/src/runtime/agent-wake.ts +238 -100
  542. package/src/runtime/assistant-event-hub.ts +151 -99
  543. package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
  544. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  545. package/src/runtime/auth/middleware.ts +0 -96
  546. package/src/runtime/auth/route-policy.ts +32 -0
  547. package/src/runtime/auth/same-actor.ts +216 -0
  548. package/src/runtime/btw-sidechain.ts +2 -3
  549. package/src/runtime/channel-invite-transport.ts +2 -48
  550. package/src/runtime/channel-invite-transports/email.ts +1 -1
  551. package/src/runtime/channel-invite-transports/slack.ts +1 -1
  552. package/src/runtime/channel-invite-transports/telegram.ts +1 -1
  553. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  554. package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
  555. package/src/runtime/channel-invite-types.ts +54 -0
  556. package/src/runtime/channel-readiness-service.ts +32 -13
  557. package/src/runtime/channel-retry-sweep.ts +65 -1
  558. package/src/runtime/guardian-reply-router.ts +10 -0
  559. package/src/runtime/http-server.ts +3 -329
  560. package/src/runtime/http-types.ts +0 -5
  561. package/src/runtime/local-actor-identity.ts +52 -11
  562. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
  563. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
  564. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
  565. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
  566. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
  567. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  568. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
  569. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
  570. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
  571. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
  572. package/src/runtime/migrations/migration-transport.ts +7 -7
  573. package/src/runtime/migrations/vbundle-builder.ts +327 -60
  574. package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
  575. package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
  576. package/src/runtime/migrations/vbundle-importer.ts +245 -68
  577. package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
  578. package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
  579. package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
  580. package/src/runtime/migrations/vbundle-validator.ts +114 -0
  581. package/src/runtime/pending-interactions.ts +43 -9
  582. package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
  583. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  584. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -5
  585. package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
  586. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
  587. package/src/runtime/routes/approval-interception-types.ts +13 -0
  588. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
  589. package/src/runtime/routes/backup-routes.ts +15 -38
  590. package/src/runtime/routes/btw-routes.ts +14 -37
  591. package/src/runtime/routes/client-routes.ts +21 -2
  592. package/src/runtime/routes/contact-prompt-routes.ts +183 -0
  593. package/src/runtime/routes/contact-routes.ts +0 -25
  594. package/src/runtime/routes/conversation-query-routes.ts +36 -1
  595. package/src/runtime/routes/conversation-routes.ts +65 -39
  596. package/src/runtime/routes/debug-bash-routes.ts +163 -0
  597. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  598. package/src/runtime/routes/document-pdf-renderer.ts +169 -0
  599. package/src/runtime/routes/documents-routes.ts +32 -75
  600. package/src/runtime/routes/errors.ts +19 -4
  601. package/src/runtime/routes/events-routes.ts +38 -0
  602. package/src/runtime/routes/gateway-log-routes.ts +79 -0
  603. package/src/runtime/routes/guardian-approval-interception.ts +2 -8
  604. package/src/runtime/routes/heartbeat-routes.ts +103 -38
  605. package/src/runtime/routes/host-app-control-routes.ts +134 -0
  606. package/src/runtime/routes/host-bash-routes.ts +56 -6
  607. package/src/runtime/routes/host-browser-routes.ts +108 -13
  608. package/src/runtime/routes/host-cu-routes.ts +66 -9
  609. package/src/runtime/routes/host-file-routes.ts +54 -5
  610. package/src/runtime/routes/host-transfer-routes.ts +122 -19
  611. package/src/runtime/routes/http-adapter.ts +1 -0
  612. package/src/runtime/routes/identity-intro-cache.ts +30 -0
  613. package/src/runtime/routes/identity-routes.ts +21 -180
  614. package/src/runtime/routes/inbound-message-handler.ts +78 -21
  615. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
  616. package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
  617. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
  618. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
  619. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
  620. package/src/runtime/routes/index.ts +14 -0
  621. package/src/runtime/routes/mcp-auth-routes.ts +132 -0
  622. package/src/runtime/routes/memory-item-routes.test.ts +41 -15
  623. package/src/runtime/routes/memory-item-routes.ts +10 -12
  624. package/src/runtime/routes/memory-v2-routes.ts +474 -1
  625. package/src/runtime/routes/migration-routes.ts +96 -0
  626. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  627. package/src/runtime/routes/schedule-routes.ts +7 -0
  628. package/src/runtime/verification-outbound-actions.ts +4 -4
  629. package/src/runtime/verification-templates.ts +4 -7
  630. package/src/schedule/integration-status.ts +66 -2
  631. package/src/schedule/recurrence-engine.ts +4 -1
  632. package/src/schedule/retry-backoff.ts +18 -0
  633. package/src/schedule/retry-policy.ts +82 -0
  634. package/src/schedule/run-script.ts +37 -5
  635. package/src/schedule/schedule-recovery.ts +64 -0
  636. package/src/schedule/schedule-store.ts +106 -2
  637. package/src/schedule/scheduler-types.ts +25 -0
  638. package/src/schedule/scheduler.ts +83 -39
  639. package/src/security/encrypted-store.ts +2 -0
  640. package/src/security/oauth-callback-registry.ts +8 -0
  641. package/src/security/secure-keys.ts +55 -0
  642. package/src/sequence/analytics.ts +5 -5
  643. package/src/sequence/engine.ts +1 -1
  644. package/src/skills/catalog-files.ts +2 -8
  645. package/src/skills/include-graph.ts +5 -5
  646. package/src/skills/remote-skill-policy.ts +10 -16
  647. package/src/skills/skill-file-provider.ts +1 -1
  648. package/src/skills/skill-file-types.ts +13 -0
  649. package/src/skills/skillssh-audit-types.ts +28 -0
  650. package/src/skills/skillssh-registry.ts +8 -21
  651. package/src/subagent/index.ts +1 -7
  652. package/src/subagent/manager.ts +1 -15
  653. package/src/tasks/task-runner.ts +0 -1
  654. package/src/tasks/task-store.ts +0 -3
  655. package/src/telemetry/types.ts +2 -0
  656. package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
  657. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  658. package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
  659. package/src/tools/apps/executors.ts +56 -69
  660. package/src/tools/background-tool-registry.ts +17 -3
  661. package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
  662. package/src/tools/browser/browser-execution.ts +2 -2
  663. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
  664. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
  665. package/src/tools/browser/cdp-client/factory.ts +23 -24
  666. package/src/tools/browser/cdp-client/index.ts +1 -14
  667. package/src/tools/computer-use/definitions.ts +42 -20
  668. package/src/tools/executor.ts +2 -0
  669. package/src/tools/host-filesystem/edit.test.ts +151 -0
  670. package/src/tools/host-filesystem/edit.ts +68 -0
  671. package/src/tools/host-filesystem/read.test.ts +129 -0
  672. package/src/tools/host-filesystem/read.ts +68 -0
  673. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  674. package/src/tools/host-filesystem/transfer.ts +78 -3
  675. package/src/tools/host-filesystem/write.test.ts +134 -0
  676. package/src/tools/host-filesystem/write.ts +68 -0
  677. package/src/tools/host-terminal/host-shell.ts +66 -1
  678. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  679. package/src/tools/memory/register.test.ts +12 -9
  680. package/src/tools/memory/register.ts +1 -2
  681. package/src/tools/provider-tool-name.ts +28 -0
  682. package/src/tools/registry.ts +30 -9
  683. package/src/tools/schedule/create.ts +6 -0
  684. package/src/tools/schedule/list.ts +2 -0
  685. package/src/tools/schedule/update.ts +10 -0
  686. package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
  687. package/src/tools/shared/filesystem/path-policy.ts +25 -1
  688. package/src/tools/skills/load.ts +0 -32
  689. package/src/tools/terminal/shell.ts +9 -1
  690. package/src/tools/tool-approval-handler.ts +32 -11
  691. package/src/tools/types.ts +28 -2
  692. package/src/tts/provider-catalog.ts +3 -5
  693. package/src/usage/pricing.ts +1 -1
  694. package/src/util/disk-usage.ts +138 -0
  695. package/src/util/platform.ts +21 -11
  696. package/src/util/process-liveness.ts +26 -0
  697. package/src/workspace/hatched-date.ts +86 -0
  698. package/src/workspace/heartbeat-service.ts +19 -0
  699. package/src/workspace/migrations/003-seed-device-id.ts +1 -1
  700. package/src/workspace/migrations/006-services-config.ts +8 -5
  701. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
  702. package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
  703. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
  704. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
  705. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
  706. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
  707. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
  708. package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
  709. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
  710. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  711. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  712. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
  713. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  714. package/src/workspace/migrations/AGENTS.md +1 -1
  715. package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
  716. package/src/workspace/migrations/registry.ts +8 -0
  717. package/src/workspace/migrations/utils.ts +21 -0
  718. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  719. package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
  720. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
  721. package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
  722. package/src/__tests__/twilio-rest.test.ts +0 -34
  723. package/src/backup/__tests__/backup-key.test.ts +0 -152
  724. package/src/backup/__tests__/backup-worker.test.ts +0 -782
  725. package/src/backup/__tests__/offsite-writer.test.ts +0 -641
  726. package/src/backup/__tests__/stream-crypt.test.ts +0 -228
  727. package/src/backup/backup-key.ts +0 -137
  728. package/src/backup/backup-worker.ts +0 -472
  729. package/src/backup/offsite-writer.ts +0 -222
  730. package/src/backup/stream-crypt.ts +0 -263
  731. package/src/daemon/message-types/pairing.ts +0 -58
  732. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  733. package/src/memory/v2/skill-qdrant.ts +0 -395
  734. package/src/outbound-proxy/config.ts +0 -20
  735. package/src/outbound-proxy/health.ts +0 -18
  736. package/src/outbound-proxy/types.ts +0 -150
  737. package/src/runtime/capability-tokens.ts +0 -190
  738. package/src/signals/bash.ts +0 -198
  739. package/src/signals/mcp-reload.ts +0 -18
@@ -52,21 +52,21 @@ import { sanitizeConfigForTransfer } from "../../config/sanitize-for-transfer.js
52
52
  import { resetDb } from "../../memory/db-connection.js";
53
53
  import { isGuardianPersonaCustomized } from "../../prompts/persona-resolver.js";
54
54
  import { getLogger } from "../../util/logger.js";
55
+ import { APP_VERSION } from "../../version.js";
55
56
  import type { PathResolver } from "./vbundle-import-analyzer.js";
56
- import {
57
- CONFIG_ARCHIVE_PATHS,
58
- type ImportCommitReport,
59
- type ImportCommitResult,
60
- type ImportedFileReport,
61
- type ImportFileAction,
62
- LEGACY_USER_MD_ARCHIVE_PATH,
63
- WORKSPACE_PRESERVE_PATHS,
57
+ import * as policy from "./vbundle-import-policy.js";
58
+ import type {
59
+ ImportCommitReport,
60
+ ImportCommitResult,
61
+ ImportedFileReport,
62
+ ImportFileAction,
64
63
  } from "./vbundle-importer.js";
65
64
  import { mergeMetadataPreservingVellum } from "./vbundle-metadata-merge.js";
66
65
  import {
67
66
  createHashVerifier,
68
67
  readAndValidateManifest,
69
68
  StreamingValidationError,
69
+ verifySymlinkEntry,
70
70
  } from "./vbundle-streaming-validator.js";
71
71
  import { parseVBundleStream } from "./vbundle-tar-stream.js";
72
72
  import type { ManifestType } from "./vbundle-validator.js";
@@ -104,9 +104,13 @@ const DEFAULT_MAX_BUNDLE_ENTRIES = 100_000;
104
104
  * this run (via a `Set<string>` built from the backupDir/tempWorkspaceDir
105
105
  * basenames), so a user entry that happens to start with one of these
106
106
  * prefixes is still swept into the swap.
107
+ *
108
+ * Exported so tests asserting "no orphan temp/backup dirs" stay in sync with
109
+ * the actual layout. Both dirs are created at `${workspaceDir}/<prefix><uuid>`
110
+ * (i.e. INSIDE workspaceDir, not as a sibling).
107
111
  */
108
- const IMPORT_TEMP_PREFIX = ".import-";
109
- const IMPORT_BACKUP_PREFIX = ".pre-import-";
112
+ export const IMPORT_TEMP_PREFIX = ".import-";
113
+ export const IMPORT_BACKUP_PREFIX = ".pre-import-";
110
114
 
111
115
  // ---------------------------------------------------------------------------
112
116
  // Public API
@@ -274,18 +278,16 @@ export async function streamCommitImport(
274
278
  // Compared against `bundleEntryCap`.
275
279
  let entryCount = 0;
276
280
 
277
- // Create the temp workspace dir up front so any failure between here and
278
- // the atomic swap can be cleaned up by the catch block below.
279
- try {
280
- await mkdir(tempWorkspaceDir, { recursive: true });
281
- } catch (err) {
282
- return {
283
- ok: false,
284
- reason: "write_failed",
285
- message: `Failed to create temp workspace dir "${tempWorkspaceDir}": ${errMessage(err)}`,
286
- };
287
- }
288
-
281
+ // The temp workspace dir is created lazily inside the parse loop AFTER
282
+ // the manifest's version gate passes (see the `entryIndex === 0` block
283
+ // below). Creating it up front would materialize `${workspaceDir}` (and
284
+ // the `.import-<uuid>` subdir) on a fresh filesystem before we knew the
285
+ // bundle was compatible — violating the plan invariant that importers
286
+ // gate on runtime-version compat BEFORE any state mutation.
287
+ //
288
+ // `cleanupTempDir` is safe whether or not the dir was ever created:
289
+ // `rm(..., { recursive: true, force: true })` is a no-op on a missing
290
+ // path.
289
291
  const cleanupTempDir = async (): Promise<void> => {
290
292
  try {
291
293
  await rm(tempWorkspaceDir, { recursive: true, force: true });
@@ -302,7 +304,10 @@ export async function streamCommitImport(
302
304
  let entryIndex = 0;
303
305
  try {
304
306
  const entries = parseVBundleStream(source);
305
- let expected: Map<string, { sha256: string; size: number }> | null = null;
307
+ let expected: Map<
308
+ string,
309
+ { sha256: string; size: number; linkTarget: string | null }
310
+ > | null = null;
306
311
 
307
312
  for await (const entry of entries) {
308
313
  if (entryIndex === 0) {
@@ -311,6 +316,30 @@ export async function streamCommitImport(
311
316
  const manifestResult = await readAndValidateManifest(entry);
312
317
  manifest = manifestResult.manifest;
313
318
  expected = manifestResult.expected;
319
+
320
+ // Defense-in-depth: refuse to populate the temp tree when the
321
+ // bundle's compat range excludes APP_VERSION. The version gate
322
+ // runs BEFORE we materialize `${workspaceDir}/.import-<uuid>`
323
+ // (the mkdir below is sequenced after this check), so on a fresh
324
+ // filesystem an incompatible bundle leaves zero filesystem trace.
325
+ // Throwing inside the generator's try block still triggers
326
+ // cleanupTempDir() in the catch — a safe no-op on a missing path
327
+ // — and mapThrownToResult translates VersionIncompatibleError into
328
+ // the version_incompatible result shape. Catches legacy bundles
329
+ // whose ExportJob row predates the platform compat-column rollout
330
+ // (compat columns NULL → platform gate skipped) and any future
331
+ // drift between the platform gate and the manifest.
332
+ const compatResult = policy.evaluateRuntimeCompatibility(
333
+ manifest.compatibility,
334
+ APP_VERSION,
335
+ );
336
+ if (!compatResult.ok) {
337
+ throw new VersionIncompatibleError(
338
+ compatResult.bundle_compat,
339
+ compatResult.runtime_version,
340
+ );
341
+ }
342
+
314
343
  // Entry-count ceiling check. The manifest declares every file the
315
344
  // bundle claims to contain, so one check here bounds the work the
316
345
  // importer is willing to do for this bundle.
@@ -320,6 +349,24 @@ export async function streamCommitImport(
320
349
  `bundle contains more than ${bundleEntryCap} entries (declared: ${manifest.contents.length})`,
321
350
  );
322
351
  }
352
+
353
+ // Only NOW — after the manifest is parsed, the version gate passes,
354
+ // and the entry-count ceiling is enforced — do we materialize the
355
+ // temp staging dir on disk. Doing this lazily preserves the plan
356
+ // invariant that importers gate on runtime-version compat BEFORE
357
+ // any state mutation. If this throws, the outer catch runs
358
+ // cleanupTempDir (a safe no-op on a missing path) and
359
+ // mapThrownToResult translates the WriteFailedError into the
360
+ // write_failed shape of ImportCommitResult.
361
+ try {
362
+ await mkdir(tempWorkspaceDir, { recursive: true });
363
+ } catch (err) {
364
+ throw wrapWriteError(
365
+ `Failed to create temp workspace dir "${tempWorkspaceDir}"`,
366
+ err,
367
+ );
368
+ }
369
+
323
370
  entryIndex += 1;
324
371
  continue;
325
372
  }
@@ -391,7 +438,7 @@ export async function streamCommitImport(
391
438
  continue;
392
439
  }
393
440
 
394
- if (entry.header.type !== "file") {
441
+ if (entry.header.type !== "file" && entry.header.type !== "symlink") {
395
442
  // pax-header / other — drain and skip. Non-file payloads are
396
443
  // metadata for the tar extractor itself, not user data.
397
444
  entry.body.resume();
@@ -411,6 +458,164 @@ export async function streamCommitImport(
411
458
  );
412
459
  }
413
460
 
461
+ // Symlink branch: typeflag-2 entry, OR a regular-file tar entry whose
462
+ // manifest declared `link_target`. `verifySymlinkEntry` cross-validates
463
+ // both directions — tar symlink without manifest link_target,
464
+ // tar regular file with manifest link_target, linkname/manifest
465
+ // disagreement, sha mismatch, traversal, absolute target. It also
466
+ // drains the body so the tar extractor advances.
467
+ if (
468
+ entry.header.type === "symlink" ||
469
+ expectedEntry.linkTarget !== null
470
+ ) {
471
+ verifySymlinkEntry({ entry, expectedEntry });
472
+
473
+ // Defense-in-depth: even though verifySymlinkEntry rejected absolute
474
+ // / `..` traversal, re-check from the IMPORTER perspective using the
475
+ // resolved disk path (which maps archive paths through the resolver,
476
+ // e.g. legacy `prompts/USER.md` -> `users/<slug>.md`).
477
+ const linkTargetStr = expectedEntry.linkTarget as string;
478
+ const diskPath = pathResolver.resolve(archivePath);
479
+ if (!diskPath) {
480
+ importedFiles.push({
481
+ path: archivePath,
482
+ disk_path: "",
483
+ action: "skipped",
484
+ size: 0,
485
+ sha256: expectedEntry.sha256,
486
+ backup_path: null,
487
+ });
488
+ warnings.push(
489
+ `Skipped "${archivePath}": no known disk target for this archive path`,
490
+ );
491
+ seen.add(archivePath);
492
+ onProgress?.({ archivePath, bytesWritten: 0, entryIndex });
493
+ entryIndex += 1;
494
+ continue;
495
+ }
496
+
497
+ const wsResolved = resolve(realWorkspaceDir);
498
+ const targetResolved = resolve(dirname(diskPath), linkTargetStr);
499
+ if (
500
+ linkTargetStr.startsWith("/") ||
501
+ (targetResolved !== wsResolved &&
502
+ !targetResolved.startsWith(wsResolved + sep))
503
+ ) {
504
+ importedFiles.push({
505
+ path: archivePath,
506
+ disk_path: diskPath,
507
+ action: "skipped",
508
+ size: 0,
509
+ sha256: expectedEntry.sha256,
510
+ backup_path: null,
511
+ });
512
+ warnings.push(
513
+ `Skipped "${archivePath}": symlink target "${linkTargetStr}" escapes workspace`,
514
+ );
515
+ seen.add(archivePath);
516
+ onProgress?.({ archivePath, bytesWritten: 0, entryIndex });
517
+ entryIndex += 1;
518
+ continue;
519
+ }
520
+
521
+ // Legacy guardian persona protection — match commitImport's
522
+ // behavior. If the bundle ships `prompts/USER.md` as a symlink and
523
+ // the destination guardian persona is already user-customized,
524
+ // skip rather than clobber.
525
+ if (
526
+ policy.isLegacyPersonaArchivePath(archivePath) &&
527
+ isGuardianPersonaCustomized(diskPath)
528
+ ) {
529
+ log.warn(
530
+ { archivePath, diskPath },
531
+ "Skipping legacy prompts/USER.md symlink import: guardian persona is already customized",
532
+ );
533
+ importedFiles.push({
534
+ path: archivePath,
535
+ disk_path: diskPath,
536
+ action: "skipped",
537
+ size: 0,
538
+ sha256: expectedEntry.sha256,
539
+ backup_path: null,
540
+ });
541
+ warnings.push(
542
+ `Skipped "${archivePath}": guardian persona at "${diskPath}" is already customized`,
543
+ );
544
+ seen.add(archivePath);
545
+ onProgress?.({ archivePath, bytesWritten: 0, entryIndex });
546
+ entryIndex += 1;
547
+ continue;
548
+ }
549
+
550
+ // Rebase onto temp workspace so the swap moves the symlink into the
551
+ // live workspace atomically.
552
+ const tempDiskPath = rebaseOntoTempWorkspace(
553
+ diskPath,
554
+ realWorkspaceDir,
555
+ tempWorkspaceDir,
556
+ );
557
+ if (!tempDiskPath) {
558
+ importedFiles.push({
559
+ path: archivePath,
560
+ disk_path: diskPath,
561
+ action: "skipped",
562
+ size: 0,
563
+ sha256: expectedEntry.sha256,
564
+ backup_path: null,
565
+ });
566
+ warnings.push(
567
+ `Skipped "${archivePath}": disk target "${diskPath}" falls outside the workspace directory`,
568
+ );
569
+ seen.add(archivePath);
570
+ onProgress?.({ archivePath, bytesWritten: 0, entryIndex });
571
+ entryIndex += 1;
572
+ continue;
573
+ }
574
+
575
+ try {
576
+ await mkdir(dirname(tempDiskPath), { recursive: true });
577
+ } catch (err) {
578
+ throw wrapWriteError(
579
+ `Failed to create parent directory for "${tempDiskPath}"`,
580
+ err,
581
+ );
582
+ }
583
+
584
+ try {
585
+ await symlink(linkTargetStr, tempDiskPath);
586
+ } catch (err) {
587
+ throw wrapWriteError(
588
+ `Failed to create symlink "${tempDiskPath}" -> "${linkTargetStr}"`,
589
+ err,
590
+ );
591
+ }
592
+
593
+ const isWorkspaceNamespaced = archivePath.startsWith("workspace/");
594
+ const importedFileIndex = importedFiles.length;
595
+ importedFiles.push({
596
+ path: archivePath,
597
+ disk_path: diskPath,
598
+ action: "created",
599
+ size: 0,
600
+ sha256: expectedEntry.sha256,
601
+ backup_path: null,
602
+ });
603
+ if (isWorkspaceNamespaced) {
604
+ hasWorkspaceNamespacedEntry = true;
605
+ } else {
606
+ legacyStaged.push({
607
+ tempPath: tempDiskPath,
608
+ livePath: diskPath,
609
+ archivePath,
610
+ importedFileIndex,
611
+ });
612
+ }
613
+ seen.add(archivePath);
614
+ onProgress?.({ archivePath, bytesWritten: 0, entryIndex });
615
+ entryIndex += 1;
616
+ continue;
617
+ }
618
+
414
619
  // Reject tar entries whose declared size disagrees with the manifest.
415
620
  // The bundle-size ceiling below trusts `expectedEntry.size`; if a
416
621
  // crafted bundle declared a tiny size in `manifest.json` but carried a
@@ -510,7 +715,7 @@ export async function streamCommitImport(
510
715
  // bundle was exported. We check against the LIVE workspace path
511
716
  // (diskPath) because the swap hasn't happened yet.
512
717
  if (
513
- archivePath === LEGACY_USER_MD_ARCHIVE_PATH &&
718
+ policy.isLegacyPersonaArchivePath(archivePath) &&
514
719
  isGuardianPersonaCustomized(diskPath)
515
720
  ) {
516
721
  log.warn(
@@ -591,14 +796,15 @@ export async function streamCommitImport(
591
796
  // Classify the entry as `workspace/*` (namespaced) vs legacy format.
592
797
  // Namespaced entries flip the swap-gate flag; legacy entries are
593
798
  // staged for an in-place promote after the stream completes.
594
- const isWorkspaceNamespaced = archivePath.startsWith("workspace/");
799
+ const isWorkspaceNamespaced =
800
+ policy.isWorkspaceNamespacedArchivePath(archivePath);
595
801
 
596
802
  // Config files need sanitization before writing to strip
597
803
  // environment-specific fields (defense-in-depth; matches commitImport).
598
804
  // Configs are small (KB-scale) so buffering them is fine. Hash
599
805
  // verification still runs on the RAW bytes — the manifest declares the
600
806
  // sha/size of the archive content, not the sanitized output.
601
- if (CONFIG_ARCHIVE_PATHS.has(archivePath)) {
807
+ if (policy.isConfigArchivePath(archivePath)) {
602
808
  const rawBytes = await collectHashVerified(entry.body, {
603
809
  sha256: expectedEntry.sha256,
604
810
  size: expectedEntry.size,
@@ -1116,11 +1322,13 @@ export async function streamCommitImport(
1116
1322
  log.warn({ err }, "invalidateConfigCache threw after import");
1117
1323
  }
1118
1324
 
1119
- // Attempt to remove the backup dir (best-effort). Leaving it around is not
1120
- // a correctness issue, only a disk-space one, so we swallow errors. The
1325
+ // Remove the backup dir (best-effort). Leaving it around is not a
1326
+ // correctness issue, only a disk-space one, so we swallow errors. The
1121
1327
  // backup dir now always exists once swap succeeds — we created it during
1122
- // swapWorkspaceContents to hold the pre-import live entries.
1123
- rm(backupDir, { recursive: true, force: true }).catch((err) => {
1328
+ // swapWorkspaceContents to hold the pre-import live entries. Awaited so
1329
+ // callers (and tests) observe a workspace free of `.pre-import-*`
1330
+ // residue once this function returns.
1331
+ await rm(backupDir, { recursive: true, force: true }).catch((err) => {
1124
1332
  log.warn({ err, backupDir }, "Failed to remove pre-import backup dir");
1125
1333
  });
1126
1334
 
@@ -1165,10 +1373,37 @@ async function promoteLegacyStagedFiles(
1165
1373
  ): Promise<void> {
1166
1374
  for (const entry of staged) {
1167
1375
  // Backup before overwrite, matching commitImport.
1376
+ //
1377
+ // Use lstat (not existsSync) to detect a pre-existing entry: existsSync
1378
+ // follows symlinks, so a dangling pre-existing symlink at livePath would
1379
+ // report `false` and we'd skip the backup before later atomically
1380
+ // replacing it via rename.
1381
+ let preExisting: boolean;
1382
+ try {
1383
+ await lstat(entry.livePath);
1384
+ preExisting = true;
1385
+ } catch (err) {
1386
+ if (isENOENT(err)) {
1387
+ preExisting = false;
1388
+ } else {
1389
+ throw err;
1390
+ }
1391
+ }
1392
+
1168
1393
  let backupPath: string | null = null;
1169
- if (existsSync(entry.livePath)) {
1394
+ if (preExisting) {
1170
1395
  backupPath = generateBackupPath(entry.livePath);
1171
- await copyFile(entry.livePath, backupPath);
1396
+ // copyFile follows symlinks and copies the resolved file's content, so
1397
+ // backing up a pre-existing symlink with copyFile would lose the
1398
+ // symlink shape. Recreate the link via readlink + symlink instead;
1399
+ // fall back to copyFile for regular files.
1400
+ const liveStat = await lstat(entry.livePath);
1401
+ if (liveStat.isSymbolicLink()) {
1402
+ const target = await readlink(entry.livePath);
1403
+ await symlink(target, backupPath);
1404
+ } else {
1405
+ await copyFile(entry.livePath, backupPath);
1406
+ }
1172
1407
  }
1173
1408
 
1174
1409
  await mkdir(dirname(entry.livePath), { recursive: true });
@@ -1192,7 +1427,25 @@ async function promoteLegacyStagedFiles(
1192
1427
  await rename(entry.tempPath, entry.livePath);
1193
1428
  } catch (err) {
1194
1429
  if (isEXDEV(err)) {
1195
- await copyFile(entry.tempPath, entry.livePath);
1430
+ // copyFile follows symlinks and copies the target's CONTENT — so a
1431
+ // legacy-format symlink entry (e.g. `prompts/USER.md` encoded as a
1432
+ // typeflag-2 record) would land as a regular file containing the
1433
+ // linked target's bytes. lstat the source first; if it's a symlink,
1434
+ // recreate the symlink shape via readlink + symlink. Mirrors the
1435
+ // verbatimSymlinks: true contract that copyTreeSkippingTransient
1436
+ // already uses on the atomic-swap path.
1437
+ const srcStat = await lstat(entry.tempPath);
1438
+ if (srcStat.isSymbolicLink()) {
1439
+ const target = await readlink(entry.tempPath);
1440
+ // Unlike rename (which atomically overwrites), fs.promises.symlink
1441
+ // fails with EEXIST if the destination already exists. Remove any
1442
+ // pre-existing entry at livePath first — the backup above
1443
+ // preserved its contents.
1444
+ await rm(entry.livePath, { force: true });
1445
+ await symlink(target, entry.livePath);
1446
+ } else {
1447
+ await copyFile(entry.tempPath, entry.livePath);
1448
+ }
1196
1449
  await rm(entry.tempPath, { force: true });
1197
1450
  } else {
1198
1451
  throw err;
@@ -1346,7 +1599,7 @@ async function planCarryOverPreservedPaths(
1346
1599
  tempWorkspaceDir: string,
1347
1600
  ): Promise<CarriedPath[]> {
1348
1601
  const plan: CarriedPath[] = [];
1349
- for (const rel of WORKSPACE_PRESERVE_PATHS) {
1602
+ for (const rel of policy.WORKSPACE_PRESERVE_PATHS) {
1350
1603
  const livePath = join(realWorkspaceDir, rel);
1351
1604
  const tempPath = join(tempWorkspaceDir, rel);
1352
1605
 
@@ -1584,6 +1837,9 @@ async function swapWorkspaceContents(
1584
1837
  tempWorkspaceDir: string,
1585
1838
  backupDir: string,
1586
1839
  ): Promise<void> {
1840
+ // Symlinks in the temp tree pass through unchanged: `rename` moves the
1841
+ // symlink inode without dereferencing, and the EXDEV fallback (`fs.cp`
1842
+ // with `verbatimSymlinks: true`) preserves them too.
1587
1843
  await mkdir(backupDir, { recursive: true });
1588
1844
 
1589
1845
  // Phase 1: move every top-level entry out of real into backup. Skip
@@ -1794,6 +2050,13 @@ async function copyTreeSkippingTransient(
1794
2050
  await cp(src, dst, {
1795
2051
  recursive: true,
1796
2052
  preserveTimestamps: true,
2053
+ // Preserve symlinks instead of dereferencing. Without this, an
2054
+ // EXDEV-fallback copy of a tree containing a class-1 symlink would
2055
+ // resolve the symlink to its target's bytes — wrong both for the
2056
+ // streaming importer's symlink entries (which must land on disk as
2057
+ // real symlinks) and for any pre-existing symlinks in carried
2058
+ // preserved subtrees.
2059
+ verbatimSymlinks: true,
1797
2060
  filter: async (source) => {
1798
2061
  try {
1799
2062
  const info = await lstat(source);
@@ -2184,6 +2447,15 @@ function mapThrownToResult(err: unknown): ImportCommitResult {
2184
2447
  };
2185
2448
  }
2186
2449
 
2450
+ if (err instanceof VersionIncompatibleError) {
2451
+ return {
2452
+ ok: false,
2453
+ reason: "version_incompatible",
2454
+ bundle_compat: err.bundleCompat,
2455
+ runtime_version: err.runtimeVersion,
2456
+ };
2457
+ }
2458
+
2187
2459
  // Errors we raised ourselves for disk-side failures.
2188
2460
  if (err instanceof WriteFailedError) {
2189
2461
  return {
@@ -2215,6 +2487,25 @@ function wrapWriteError(prefix: string, cause: unknown): WriteFailedError {
2215
2487
  return new WriteFailedError(`${prefix}: ${errMessage(cause)}`);
2216
2488
  }
2217
2489
 
2490
+ /**
2491
+ * Sentinel error thrown when the bundle's manifest declares a runtime-version
2492
+ * compat range that excludes the current `APP_VERSION`. Caught by the same
2493
+ * try/catch that wraps the streaming parse loop so `cleanupTempDir()` runs
2494
+ * before `mapThrownToResult` translates it into the `version_incompatible`
2495
+ * shape of `ImportCommitResult`.
2496
+ */
2497
+ class VersionIncompatibleError extends Error {
2498
+ constructor(
2499
+ readonly bundleCompat: policy.RuntimeCompatibility,
2500
+ readonly runtimeVersion: string,
2501
+ ) {
2502
+ super(
2503
+ policy.formatRuntimeCompatibilityMessage(bundleCompat, runtimeVersion),
2504
+ );
2505
+ this.name = "VersionIncompatibleError";
2506
+ }
2507
+ }
2508
+
2218
2509
  function errMessage(err: unknown): string {
2219
2510
  return err instanceof Error ? err.message : String(err);
2220
2511
  }
@@ -20,6 +20,7 @@
20
20
  */
21
21
 
22
22
  import { createHash } from "node:crypto";
23
+ import { posix } from "node:path";
23
24
  import { Transform, type TransformCallback } from "node:stream";
24
25
 
25
26
  import type { StreamedTarEntry } from "./vbundle-tar-stream.js";
@@ -38,8 +39,16 @@ import {
38
39
 
39
40
  export interface ManifestReadResult {
40
41
  manifest: ManifestType;
41
- /** Fast lookup from archive path -> expected sha256 + size (from manifest.contents). */
42
- expected: Map<string, { sha256: string; size: number }>;
42
+ /**
43
+ * Fast lookup from archive path -> expected sha256 + size + linkTarget
44
+ * (from manifest.contents). `linkTarget` is non-null only for symlink
45
+ * entries — regular file entries carry `null` so callers can branch on
46
+ * type without a separate map.
47
+ */
48
+ expected: Map<
49
+ string,
50
+ { sha256: string; size: number; linkTarget: string | null }
51
+ >;
43
52
  }
44
53
 
45
54
  /**
@@ -177,7 +186,10 @@ export async function readAndValidateManifest(
177
186
  manifest = translateLegacyManifest(legacy);
178
187
  }
179
188
 
180
- const expected = new Map<string, { sha256: string; size: number }>();
189
+ const expected = new Map<
190
+ string,
191
+ { sha256: string; size: number; linkTarget: string | null }
192
+ >();
181
193
  for (const file of manifest.contents) {
182
194
  if (expected.has(file.path)) {
183
195
  throw new StreamingValidationError(
@@ -185,12 +197,153 @@ export async function readAndValidateManifest(
185
197
  `Manifest contains duplicate entry for path: ${file.path}`,
186
198
  );
187
199
  }
188
- expected.set(file.path, { sha256: file.sha256, size: file.size_bytes });
200
+ expected.set(file.path, {
201
+ sha256: file.sha256,
202
+ size: file.size_bytes,
203
+ linkTarget: file.link_target ?? null,
204
+ });
189
205
  }
190
206
 
191
207
  return { manifest, expected };
192
208
  }
193
209
 
210
+ // ---------------------------------------------------------------------------
211
+ // Symlink entry verifier
212
+ // ---------------------------------------------------------------------------
213
+
214
+ /** SHA-256 hex digest of UTF-8 bytes / Uint8Array — local mirror of the helper in vbundle-streaming-importer.ts. */
215
+ function sha256Hex(data: Uint8Array | string): string {
216
+ return createHash("sha256").update(data).digest("hex");
217
+ }
218
+
219
+ /**
220
+ * Verify a streaming tar entry whose manifest declared it as a symlink.
221
+ *
222
+ * Throws `StreamingValidationError` with stable codes for each failure mode:
223
+ * - `entry_size`: tar header declared a non-zero size (symlink bodies must be empty).
224
+ * - `symlink_not_declared`: manifest entry didn't carry a `link_target` so the
225
+ * tar typeflag-2 record is unauthorized.
226
+ * - `link_target_mismatch`: tar header `linkname` doesn't equal the manifest's
227
+ * declared `link_target`. Either the bundle was tampered with or the producer
228
+ * and writer disagreed.
229
+ * - `entry_hash`: manifest's declared sha256 does not match the sha256 of the
230
+ * declared link target string. Catches manifest tampering where the attacker
231
+ * adjusted `link_target` but forgot to recompute the digest.
232
+ * - `symlink_target_escapes_archive`: the resolved link target points outside
233
+ * the archive root (e.g. `../../etc/passwd`). Importer would otherwise
234
+ * follow the symlink to a privileged path on the host.
235
+ *
236
+ * On success, drains the entry body via `entry.body.resume()` so the tar
237
+ * extractor's `next` callback fires and the parser can advance to the next
238
+ * entry — symlink bodies are size=0, but `tar-stream` still emits a Readable
239
+ * that needs to be consumed.
240
+ */
241
+ export function verifySymlinkEntry(input: {
242
+ entry: StreamedTarEntry;
243
+ expectedEntry: { sha256: string; size: number; linkTarget: string | null };
244
+ }): void {
245
+ const { entry, expectedEntry } = input;
246
+ const archivePath = entry.header.name;
247
+
248
+ if (entry.header.size !== 0) {
249
+ entry.body.resume();
250
+ throw new StreamingValidationError(
251
+ "entry_size",
252
+ `Symlink entry ${archivePath} declared size ${entry.header.size}; symlink bodies must be empty`,
253
+ archivePath,
254
+ );
255
+ }
256
+
257
+ // Reject manifest-declared size_bytes != 0 for symlink entries. The buffered
258
+ // validator catches this via FILE_SIZE_MISMATCH; without this guard a
259
+ // crafted bundle could declare `link_target` plus `size_bytes: 100` and the
260
+ // streaming side would accept (header.size is independent of the manifest's
261
+ // size_bytes).
262
+ if (expectedEntry.size !== 0) {
263
+ entry.body.resume();
264
+ throw new StreamingValidationError(
265
+ "entry_size",
266
+ `Symlink ${archivePath} has non-zero manifest-declared size ${expectedEntry.size} (expected 0)`,
267
+ archivePath,
268
+ );
269
+ }
270
+
271
+ if (expectedEntry.linkTarget == null) {
272
+ entry.body.resume();
273
+ throw new StreamingValidationError(
274
+ "symlink_not_declared",
275
+ `Tar entry ${archivePath} is a symlink but the manifest did not declare a link_target`,
276
+ archivePath,
277
+ );
278
+ }
279
+
280
+ const tarLinkname = entry.header.linkname;
281
+ if (tarLinkname !== expectedEntry.linkTarget) {
282
+ entry.body.resume();
283
+ throw new StreamingValidationError(
284
+ "link_target_mismatch",
285
+ `Symlink target mismatch for ${archivePath}: tar header linkname=${JSON.stringify(
286
+ tarLinkname,
287
+ )}, manifest link_target=${JSON.stringify(expectedEntry.linkTarget)}`,
288
+ archivePath,
289
+ );
290
+ }
291
+
292
+ const computedSha = sha256Hex(expectedEntry.linkTarget);
293
+ if (computedSha !== expectedEntry.sha256) {
294
+ entry.body.resume();
295
+ throw new StreamingValidationError(
296
+ "entry_hash",
297
+ `Symlink hash mismatch for ${archivePath}: manifest sha256=${expectedEntry.sha256}, computed over link_target=${computedSha}`,
298
+ archivePath,
299
+ );
300
+ }
301
+
302
+ // Absolute targets escape the archive root unconditionally — and would
303
+ // bypass the resolution check below because `posix.join("workspace",
304
+ // "/etc/passwd")` normalizes the leading `/` away and returns
305
+ // `"workspace/etc/passwd"`. Reject these explicitly before resolution so
306
+ // a symlink with target `/etc/passwd` cannot be imported as a real
307
+ // host-filesystem symlink.
308
+ if (expectedEntry.linkTarget.startsWith("/")) {
309
+ entry.body.resume();
310
+ throw new StreamingValidationError(
311
+ "symlink_target_escapes_archive",
312
+ `Symlink ${archivePath} has absolute target ${JSON.stringify(
313
+ expectedEntry.linkTarget,
314
+ )}, which escapes the archive root`,
315
+ archivePath,
316
+ );
317
+ }
318
+
319
+ // Path traversal: resolve the symlink target relative to the symlink's own
320
+ // directory inside the archive. If the resolved path escapes the archive
321
+ // root (begins with `..` or equals `..`), the target points outside the
322
+ // bundle — refuse to import. Also reject a resolved path that is itself
323
+ // absolute as defense-in-depth (dirname could in theory yield an absolute
324
+ // path; cheap to guard).
325
+ const resolved = posix.normalize(
326
+ posix.join(posix.dirname(archivePath), expectedEntry.linkTarget),
327
+ );
328
+ if (
329
+ resolved === ".." ||
330
+ resolved.startsWith("../") ||
331
+ resolved.startsWith("/")
332
+ ) {
333
+ entry.body.resume();
334
+ throw new StreamingValidationError(
335
+ "symlink_target_escapes_archive",
336
+ `Symlink ${archivePath} target ${JSON.stringify(
337
+ expectedEntry.linkTarget,
338
+ )} resolves to ${resolved}, which escapes the archive root`,
339
+ archivePath,
340
+ );
341
+ }
342
+
343
+ // All checks pass — drain the (empty) body so the tar extractor advances.
344
+ entry.body.resume();
345
+ }
346
+
194
347
  // ---------------------------------------------------------------------------
195
348
  // Per-entry hash + size verifier
196
349
  // ---------------------------------------------------------------------------