@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.
- package/ARCHITECTURE.md +48 -50
- package/Dockerfile +1 -0
- package/README.md +1 -2
- package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
- package/bun.lock +26 -26
- package/docs/architecture/memory.md +5 -2
- package/docs/architecture/security.md +20 -0
- package/docs/plugins.md +7 -9
- package/knip.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +52 -5
- package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
- package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
- package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
- package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
- package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
- package/node_modules/@vellumai/twilio-client/package.json +18 -0
- package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
- package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
- package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
- package/openapi.yaml +1020 -40
- package/package.json +6 -3
- package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
- package/src/__tests__/app-bundler.test.ts +170 -1
- package/src/__tests__/app-control-flow.test.ts +384 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
- package/src/__tests__/app-executors.test.ts +30 -43
- package/src/__tests__/approval-routes-http.test.ts +23 -6
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
- package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
- package/src/__tests__/assistant-event-hub.test.ts +157 -2
- package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -7
- package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
- package/src/__tests__/background-shell-host-bash.test.ts +14 -15
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
- package/src/__tests__/btw-routes.test.ts +13 -4
- package/src/__tests__/call-controller.test.ts +49 -1
- package/src/__tests__/call-conversation-messages.test.ts +8 -2
- package/src/__tests__/call-domain.test.ts +0 -2
- package/src/__tests__/call-routes-http.test.ts +0 -2
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
- package/src/__tests__/channel-readiness-service.test.ts +62 -2
- package/src/__tests__/checker.test.ts +3 -4
- package/src/__tests__/config-loader-backfill.test.ts +461 -147
- package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/config-set-platform-guard.test.ts +48 -4
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +20 -11
- package/src/__tests__/config-watcher.test.ts +142 -71
- package/src/__tests__/context-search-agent-runner.test.ts +61 -3
- package/src/__tests__/context-search-conversations-source.test.ts +0 -24
- package/src/__tests__/context-search-fanout.test.ts +0 -1
- package/src/__tests__/context-search-memory-source.test.ts +3 -7
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop.test.ts +454 -5
- package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-lifecycle.test.ts +36 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
- package/src/__tests__/conversation-process-callsite.test.ts +43 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
- package/src/__tests__/conversation-slash-commands.test.ts +0 -4
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-store.test.ts +0 -18
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +328 -0
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
- package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
- package/src/__tests__/credentials-cli.test.ts +12 -12
- package/src/__tests__/cu-unified-flow.test.ts +351 -23
- package/src/__tests__/daemon-credential-client.test.ts +101 -19
- package/src/__tests__/date-context.test.ts +164 -2
- package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
- package/src/__tests__/disk-pressure-guard.test.ts +262 -0
- package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
- package/src/__tests__/disk-pressure-policy.test.ts +241 -0
- package/src/__tests__/disk-pressure-routes.test.ts +379 -0
- package/src/__tests__/disk-pressure-tools.test.ts +277 -0
- package/src/__tests__/disk-usage.test.ts +150 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/events-client-registration.test.ts +52 -0
- package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
- package/src/__tests__/file-write-tool.test.ts +4 -10
- package/src/__tests__/filing-service.test.ts +3 -4
- package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
- package/src/__tests__/heartbeat-service.test.ts +968 -2
- package/src/__tests__/helpers/call-route-handler.ts +7 -1
- package/src/__tests__/host-app-control-proxy.test.ts +772 -0
- package/src/__tests__/host-app-control-routes.test.ts +263 -0
- package/src/__tests__/host-bash-proxy.test.ts +439 -47
- package/src/__tests__/host-bash-routes.test.ts +459 -0
- package/src/__tests__/host-browser-proxy.test.ts +24 -22
- package/src/__tests__/host-browser-routes.test.ts +39 -13
- package/src/__tests__/host-cu-proxy.test.ts +248 -52
- package/src/__tests__/host-cu-routes-targeted.test.ts +429 -0
- package/src/__tests__/host-file-edit-tool.test.ts +47 -1
- package/src/__tests__/host-file-proxy-targeted.test.ts +378 -0
- package/src/__tests__/host-file-proxy.test.ts +301 -45
- package/src/__tests__/host-file-read-tool.test.ts +17 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +420 -0
- package/src/__tests__/host-file-write-tool.test.ts +42 -1
- package/src/__tests__/host-proxy-base.test.ts +312 -0
- package/src/__tests__/host-shell-tool.test.ts +22 -4
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +932 -0
- package/src/__tests__/host-transfer-proxy.test.ts +121 -22
- package/src/__tests__/host-transfer-routes-targeted.test.ts +662 -0
- package/src/__tests__/http-user-message-parity.test.ts +108 -1
- package/src/__tests__/identity-intro-cache.test.ts +29 -0
- package/src/__tests__/identity-routes.test.ts +103 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
- package/src/__tests__/injector-chain.test.ts +18 -6
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- package/src/__tests__/inline-command-runner.test.ts +0 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
- package/src/__tests__/integration-status.test.ts +85 -5
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
- package/src/__tests__/managed-profile-guard.test.ts +18 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
- package/src/__tests__/mcp-abort-signal.test.ts +130 -0
- package/src/__tests__/mcp-auth-routes.test.ts +197 -0
- package/src/__tests__/mcp-cli.test.ts +338 -2
- package/src/__tests__/memory-admin-recall.test.ts +3 -11
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
- package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
- package/src/__tests__/migration-import-commit-http.test.ts +108 -2
- package/src/__tests__/mock-gateway-ipc.ts +1 -0
- package/src/__tests__/normalize-onboarding.test.ts +180 -0
- package/src/__tests__/oauth-cli.test.ts +0 -2
- package/src/__tests__/oauth-connect-routes.test.ts +316 -0
- package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
- package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
- package/src/__tests__/onboarding-persona-write.test.ts +308 -0
- package/src/__tests__/openai-provider.test.ts +45 -8
- package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
- package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
- package/src/__tests__/platform-callback-registration.test.ts +21 -4
- package/src/__tests__/platform.test.ts +2 -1
- package/src/__tests__/playbook-execution.test.ts +0 -43
- package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +214 -25
- package/src/__tests__/process-message-background-slack.test.ts +2 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
- package/src/__tests__/provider-tool-name.test.ts +23 -0
- package/src/__tests__/public-ingress-urls.test.ts +97 -0
- package/src/__tests__/relay-server.test.ts +15 -4
- package/src/__tests__/require-fresh-approval.test.ts +0 -1
- package/src/__tests__/retry-backoff.test.ts +87 -0
- package/src/__tests__/runtime-events-sse.test.ts +2 -2
- package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
- package/src/__tests__/schedule-retry.test.ts +715 -0
- package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
- package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
- package/src/__tests__/secret-ingress-http.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
- package/src/__tests__/skill-feature-flags.test.ts +43 -41
- package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
- package/src/__tests__/skill-load-inline-command.test.ts +0 -51
- package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
- package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
- package/src/__tests__/slack-channel-config.test.ts +9 -14
- package/src/__tests__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-config.test.ts +0 -1
- package/src/__tests__/test-preload.ts +8 -0
- package/src/__tests__/tool-approval-handler.test.ts +3 -4
- package/src/__tests__/tool-audit-listener.test.ts +48 -0
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/twilio-config.test.ts +3 -16
- package/src/__tests__/twilio-routes.test.ts +3 -5
- package/src/__tests__/twilio-validation.test.ts +93 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
- package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
- package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
- package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
- package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
- package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
- package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
- package/src/approvals/guardian-decision-primitive.ts +13 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -17
- package/src/backup/__tests__/paths.test.ts +0 -22
- package/src/backup/__tests__/restore.test.ts +51 -151
- package/src/backup/paths.ts +2 -18
- package/src/backup/restore.ts +107 -231
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/app-bundler.ts +51 -3
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-conversation-messages.ts +46 -10
- package/src/calls/relay-server.ts +4 -44
- package/src/calls/twilio-config.ts +2 -17
- package/src/calls/twilio-rest.ts +33 -105
- package/src/calls/twilio-routes.ts +11 -12
- package/src/channels/types.ts +8 -7
- package/src/cli/commands/__tests__/backup.test.ts +6 -277
- package/src/cli/commands/__tests__/gateway.test.ts +288 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -5
- package/src/cli/commands/backup.ts +6 -331
- package/src/cli/commands/bash.ts +35 -108
- package/src/cli/commands/clients.ts +36 -37
- package/src/cli/commands/contacts.ts +137 -25
- package/src/cli/commands/conversations.ts +2 -5
- package/src/cli/commands/credentials.ts +71 -7
- package/src/cli/commands/domain.ts +66 -15
- package/src/cli/commands/gateway.ts +183 -0
- package/src/cli/commands/keys.ts +9 -6
- package/src/cli/commands/mcp.ts +116 -156
- package/src/cli/commands/memory-v2.ts +303 -7
- package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
- package/src/cli/commands/oauth/connect.ts +127 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -4
- package/src/cli/commands/platform/__tests__/connect.test.ts +7 -3
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -3
- package/src/cli/commands/platform/__tests__/status.test.ts +116 -21
- package/src/cli/commands/platform/disconnect.ts +5 -4
- package/src/cli/commands/platform/index.ts +16 -25
- package/src/cli/commands/status.ts +57 -0
- package/src/cli/lib/daemon-credential-client.ts +110 -28
- package/src/cli/program.ts +6 -2
- package/src/config/assistant-feature-flags.ts +79 -12
- package/src/config/bundled-skills/acp/SKILL.md +6 -0
- package/src/config/bundled-skills/acp/TOOLS.json +1 -22
- package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
- package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
- package/src/config/bundled-skills/app-control/SKILL.md +75 -0
- package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
- package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
- package/src/config/bundled-skills/document/TOOLS.json +0 -8
- package/src/config/bundled-skills/followups/TOOLS.json +0 -12
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
- package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
- package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
- package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +25 -4
- package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
- package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
- package/src/config/bundled-skills/settings/SKILL.md +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +0 -12
- package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
- package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
- package/src/config/bundled-skills/subagent/SKILL.md +6 -2
- package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
- package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
- package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
- package/src/config/bundled-tool-registry.ts +21 -0
- package/src/config/env-registry.ts +0 -2
- package/src/config/env.ts +19 -20
- package/src/config/feature-flag-registry.json +47 -135
- package/src/config/loader.ts +197 -104
- package/src/config/sanitize-for-transfer.ts +2 -0
- package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +17 -9
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/calls.ts +0 -9
- package/src/config/schemas/channels.ts +0 -5
- package/src/config/schemas/heartbeat.ts +64 -1
- package/src/config/schemas/ingress.ts +10 -6
- package/src/config/schemas/llm.ts +7 -10
- package/src/config/schemas/memory-lifecycle.ts +90 -24
- package/src/config/schemas/memory-v2.ts +121 -13
- package/src/config/schemas/platform.ts +49 -3
- package/src/config/schemas/services.ts +29 -15
- package/src/config/schemas/skills.ts +0 -6
- package/src/config/seed-inference-profiles.ts +230 -33
- package/src/contacts/contact-store.ts +0 -55
- package/src/contacts/contacts-write.ts +0 -27
- package/src/context/window-manager.ts +1 -2
- package/src/credential-execution/feature-gates.ts +10 -10
- package/src/credential-execution/process-manager.ts +12 -41
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +187 -5
- package/src/daemon/assistant-attachments.ts +4 -4
- package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
- package/src/daemon/config-watcher.ts +89 -60
- package/src/daemon/conversation-agent-loop-handlers.ts +27 -3
- package/src/daemon/conversation-agent-loop.ts +202 -61
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +9 -4
- package/src/daemon/conversation-process.ts +24 -11
- package/src/daemon/conversation-runtime-assembly.ts +28 -2
- package/src/daemon/conversation-store.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +305 -4
- package/src/daemon/conversation-tool-setup.ts +66 -62
- package/src/daemon/conversation.ts +38 -24
- package/src/daemon/date-context.ts +71 -22
- package/src/daemon/disk-pressure-background-gate.ts +73 -0
- package/src/daemon/disk-pressure-guard.ts +343 -0
- package/src/daemon/disk-pressure-policy.ts +163 -0
- package/src/daemon/doordash-steps.ts +1 -1
- package/src/daemon/handlers/shared.ts +4 -2
- package/src/daemon/handlers/skills.ts +3 -4
- package/src/daemon/host-app-control-proxy.ts +389 -0
- package/src/daemon/host-bash-proxy.ts +117 -82
- package/src/daemon/host-browser-proxy.ts +67 -82
- package/src/daemon/host-cu-proxy.ts +127 -86
- package/src/daemon/host-file-proxy.ts +129 -69
- package/src/daemon/host-proxy-base.ts +294 -0
- package/src/daemon/host-proxy-preactivation.ts +82 -0
- package/src/daemon/host-transfer-proxy.ts +338 -129
- package/src/daemon/lifecycle.ts +194 -145
- package/src/daemon/meet-host-supervisor.ts +4 -4
- package/src/daemon/meet-manifest-loader.ts +0 -1
- package/src/daemon/memory-v2-startup.ts +14 -4
- package/src/daemon/message-protocol.ts +6 -8
- package/src/daemon/message-types/contacts.ts +23 -1
- package/src/daemon/message-types/conversations.ts +15 -8
- package/src/daemon/message-types/disk-pressure.ts +9 -0
- package/src/daemon/message-types/host-app-control.ts +150 -0
- package/src/daemon/message-types/host-bash.ts +4 -0
- package/src/daemon/message-types/host-cu.ts +2 -0
- package/src/daemon/message-types/host-file.ts +4 -0
- package/src/daemon/message-types/host-transfer.ts +3 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/message-types/schedules.ts +8 -3
- package/src/daemon/message-types/skills.ts +2 -2
- package/src/daemon/process-message.ts +18 -1
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/shutdown-handlers.ts +0 -3
- package/src/daemon/tool-setup-types.ts +51 -0
- package/src/daemon/tool-side-effects.ts +1 -1
- package/src/documents/document-store.ts +85 -0
- package/src/events/tool-audit-listener.ts +2 -1
- package/src/filing/filing-service.ts +30 -5
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +24 -23
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +252 -0
- package/src/heartbeat/heartbeat-run-store.ts +249 -0
- package/src/heartbeat/heartbeat-service.ts +459 -54
- package/src/home/__tests__/post-connect-feed.test.ts +99 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
- package/src/home/__tests__/suggested-prompts.test.ts +89 -0
- package/src/home/feed-scheduler.ts +18 -0
- package/src/home/post-connect-feed.ts +68 -0
- package/src/home/relationship-state-writer.ts +17 -92
- package/src/home/suggested-prompts.ts +46 -10
- package/src/inbound/platform-callback-registration.ts +8 -15
- package/src/inbound/public-ingress-urls.ts +32 -34
- package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
- package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
- package/src/ipc/assistant-server.ts +70 -3
- package/src/ipc/cli-client.ts +32 -1
- package/src/ipc/gateway-client.ts +37 -3
- package/src/live-voice/live-voice-archive.ts +4 -4
- package/src/live-voice/live-voice-metrics.ts +10 -10
- package/src/live-voice/protocol.ts +5 -7
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
- package/src/mcp/mcp-auth-orchestrator.ts +213 -0
- package/src/mcp/mcp-auth-state.ts +133 -0
- package/src/mcp/mcp-oauth-provider.ts +19 -0
- package/src/media/image-service.ts +1 -7
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
- package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
- package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
- package/src/memory/admin.ts +5 -9
- package/src/memory/anisotropy.test.ts +247 -0
- package/src/memory/anisotropy.ts +443 -0
- package/src/memory/auto-analysis-constants.ts +17 -0
- package/src/memory/auto-analysis-guard.ts +5 -15
- package/src/memory/canonical-guardian-store.ts +7 -7
- package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
- package/src/memory/context-search/agent-protocol.ts +6 -6
- package/src/memory/context-search/agent-runner.ts +51 -9
- package/src/memory/context-search/sources/conversations.ts +2 -11
- package/src/memory/context-search/sources/memory-v2.ts +22 -9
- package/src/memory/context-search/sources/memory.ts +0 -1
- package/src/memory/context-search/types.ts +0 -1
- package/src/memory/conversation-crud.ts +5 -13
- package/src/memory/conversation-key-store.ts +2 -15
- package/src/memory/db-init.ts +6 -0
- package/src/memory/embedding-backend.ts +9 -21
- package/src/memory/embedding-runtime-manager.ts +119 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +81 -25
- package/src/memory/graph/conversation-graph-memory.ts +43 -78
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +10 -67
- package/src/memory/graph/graph-search.ts +9 -20
- package/src/memory/graph/retriever.test.ts +6 -0
- package/src/memory/graph/retriever.ts +34 -10
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/indexer.ts +54 -45
- package/src/memory/job-handlers/backfill.ts +2 -11
- package/src/memory/job-handlers/cleanup.ts +43 -0
- package/src/memory/job-handlers/embedding.ts +6 -8
- package/src/memory/job-handlers/summarization.ts +2 -7
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
- package/src/memory/jobs/embed-concept-page.ts +28 -2
- package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
- package/src/memory/jobs-store.ts +114 -22
- package/src/memory/jobs-worker.ts +193 -106
- package/src/memory/memory-v2-activation-log-store.ts +33 -15
- package/src/memory/memory-v2-concept-frequency.ts +169 -0
- package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
- package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
- package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/pkb/pkb-search.test.ts +6 -0
- package/src/memory/pkb/pkb-search.ts +7 -0
- package/src/memory/qdrant-client.ts +49 -32
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/schema/infrastructure.ts +15 -0
- package/src/memory/search/semantic.ts +13 -67
- package/src/memory/sparse-tokenize.ts +49 -0
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +387 -344
- package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
- package/src/memory/v2/__tests__/injection.test.ts +181 -169
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +154 -188
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
- package/src/memory/v2/__tests__/static-context.test.ts +76 -2
- package/src/memory/v2/activation.ts +213 -239
- package/src/memory/v2/consolidation-job.ts +65 -17
- package/src/memory/v2/constants.ts +7 -0
- package/src/memory/v2/injection.ts +123 -103
- package/src/memory/v2/prompts/consolidation.ts +348 -92
- package/src/memory/v2/qdrant.ts +198 -1
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +113 -77
- package/src/memory/v2/skill-content.ts +4 -3
- package/src/memory/v2/skill-store.ts +91 -53
- package/src/memory/v2/sparse-bm25.ts +245 -0
- package/src/memory/v2/static-context.ts +28 -5
- package/src/memory/v2/types.ts +10 -10
- package/src/messaging/providers/gmail/types.ts +0 -49
- package/src/messaging/providers/slack/adapter.ts +1 -31
- package/src/messaging/providers/slack/types.ts +0 -32
- package/src/notifications/README.md +10 -10
- package/src/notifications/broadcaster.ts +1 -1
- package/src/notifications/copy-composer.ts +13 -0
- package/src/notifications/guardian-question-mode.ts +5 -5
- package/src/notifications/signal.ts +4 -0
- package/src/oauth/AGENTS.md +3 -1
- package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
- package/src/oauth/connect-orchestrator.ts +6 -0
- package/src/oauth/connection-resolver.test.ts +66 -1
- package/src/oauth/connection-resolver.ts +55 -1
- package/src/oauth/credential-token-resolver.ts +1 -3
- package/src/oauth/manual-token-connection.ts +0 -4
- package/src/oauth/oauth-connect-state.ts +77 -0
- package/src/oauth/seed-providers.ts +58 -1
- package/src/outbound-proxy/index.ts +1 -37
- package/src/outbound-proxy/logging.ts +1 -1
- package/src/outbound-proxy/policy.ts +6 -5
- package/src/outbound-proxy/router.ts +2 -1
- package/src/permissions/approval-policy.test.ts +6 -275
- package/src/permissions/approval-policy.ts +0 -51
- package/src/permissions/checker.test.ts +0 -1
- package/src/permissions/checker.ts +3 -17
- package/src/permissions/gateway-threshold-reader.ts +2 -0
- package/src/permissions/prompter.ts +34 -1
- package/src/permissions/secret-prompter.ts +6 -2
- package/src/plugins/defaults/injectors.ts +35 -2
- package/src/plugins/defaults/memory-retrieval.ts +5 -6
- package/src/plugins/types.ts +7 -0
- package/src/proactive-artifact/aux-message-injector.ts +74 -0
- package/src/proactive-artifact/decision.test.ts +226 -0
- package/src/proactive-artifact/decision.ts +165 -0
- package/src/proactive-artifact/index.ts +7 -0
- package/src/proactive-artifact/job.test.ts +867 -0
- package/src/proactive-artifact/job.ts +352 -0
- package/src/proactive-artifact/message-copy.ts +41 -0
- package/src/proactive-artifact/trigger-state.test.ts +277 -0
- package/src/proactive-artifact/trigger-state.ts +119 -0
- package/src/prompts/bootstrap-cleanup.ts +27 -0
- package/src/prompts/normalize-onboarding.ts +80 -0
- package/src/prompts/persona-resolver.ts +101 -9
- package/src/prompts/system-prompt.ts +23 -24
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/prompts/templates/SOUL.md +13 -1
- package/src/providers/__tests__/retry-callsite.test.ts +222 -1
- package/src/providers/model-intents.ts +7 -0
- package/src/providers/openrouter/client.ts +8 -0
- package/src/providers/retry.ts +50 -0
- package/src/providers/speech-to-text/provider-catalog.ts +7 -8
- package/src/providers/types.ts +1 -0
- package/src/runtime/__tests__/agent-wake.test.ts +456 -3
- package/src/runtime/agent-wake.ts +238 -100
- package/src/runtime/assistant-event-hub.ts +151 -99
- package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
- package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
- package/src/runtime/auth/middleware.ts +0 -96
- package/src/runtime/auth/route-policy.ts +32 -0
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/btw-sidechain.ts +2 -3
- package/src/runtime/channel-invite-transport.ts +2 -48
- package/src/runtime/channel-invite-transports/email.ts +1 -1
- package/src/runtime/channel-invite-transports/slack.ts +1 -1
- package/src/runtime/channel-invite-transports/telegram.ts +1 -1
- package/src/runtime/channel-invite-transports/voice.ts +1 -1
- package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
- package/src/runtime/channel-invite-types.ts +54 -0
- package/src/runtime/channel-readiness-service.ts +32 -13
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/guardian-reply-router.ts +10 -0
- package/src/runtime/http-server.ts +3 -329
- package/src/runtime/http-types.ts +0 -5
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
- package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
- package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
- package/src/runtime/migrations/migration-transport.ts +7 -7
- package/src/runtime/migrations/vbundle-builder.ts +327 -60
- package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
- package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
- package/src/runtime/migrations/vbundle-importer.ts +245 -68
- package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
- package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
- package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
- package/src/runtime/migrations/vbundle-validator.ts +114 -0
- package/src/runtime/pending-interactions.ts +43 -9
- package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
- package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -5
- package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
- package/src/runtime/routes/approval-interception-types.ts +13 -0
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
- package/src/runtime/routes/backup-routes.ts +15 -38
- package/src/runtime/routes/btw-routes.ts +14 -37
- package/src/runtime/routes/client-routes.ts +21 -2
- package/src/runtime/routes/contact-prompt-routes.ts +183 -0
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-query-routes.ts +36 -1
- package/src/runtime/routes/conversation-routes.ts +65 -39
- package/src/runtime/routes/debug-bash-routes.ts +163 -0
- package/src/runtime/routes/disk-pressure-routes.ts +121 -0
- package/src/runtime/routes/document-pdf-renderer.ts +169 -0
- package/src/runtime/routes/documents-routes.ts +32 -75
- package/src/runtime/routes/errors.ts +19 -4
- package/src/runtime/routes/events-routes.ts +38 -0
- package/src/runtime/routes/gateway-log-routes.ts +79 -0
- package/src/runtime/routes/guardian-approval-interception.ts +2 -8
- package/src/runtime/routes/heartbeat-routes.ts +103 -38
- package/src/runtime/routes/host-app-control-routes.ts +134 -0
- package/src/runtime/routes/host-bash-routes.ts +56 -6
- package/src/runtime/routes/host-browser-routes.ts +108 -13
- package/src/runtime/routes/host-cu-routes.ts +66 -9
- package/src/runtime/routes/host-file-routes.ts +54 -5
- package/src/runtime/routes/host-transfer-routes.ts +122 -19
- package/src/runtime/routes/http-adapter.ts +1 -0
- package/src/runtime/routes/identity-intro-cache.ts +30 -0
- package/src/runtime/routes/identity-routes.ts +21 -180
- package/src/runtime/routes/inbound-message-handler.ts +78 -21
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
- package/src/runtime/routes/index.ts +14 -0
- package/src/runtime/routes/mcp-auth-routes.ts +132 -0
- package/src/runtime/routes/memory-item-routes.test.ts +41 -15
- package/src/runtime/routes/memory-item-routes.ts +10 -12
- package/src/runtime/routes/memory-v2-routes.ts +474 -1
- package/src/runtime/routes/migration-routes.ts +96 -0
- package/src/runtime/routes/oauth-connect-routes.ts +153 -0
- package/src/runtime/routes/schedule-routes.ts +7 -0
- package/src/runtime/verification-outbound-actions.ts +4 -4
- package/src/runtime/verification-templates.ts +4 -7
- package/src/schedule/integration-status.ts +66 -2
- package/src/schedule/recurrence-engine.ts +4 -1
- package/src/schedule/retry-backoff.ts +18 -0
- package/src/schedule/retry-policy.ts +82 -0
- package/src/schedule/run-script.ts +37 -5
- package/src/schedule/schedule-recovery.ts +64 -0
- package/src/schedule/schedule-store.ts +106 -2
- package/src/schedule/scheduler-types.ts +25 -0
- package/src/schedule/scheduler.ts +83 -39
- package/src/security/encrypted-store.ts +2 -0
- package/src/security/oauth-callback-registry.ts +8 -0
- package/src/security/secure-keys.ts +55 -0
- package/src/sequence/analytics.ts +5 -5
- package/src/sequence/engine.ts +1 -1
- package/src/skills/catalog-files.ts +2 -8
- package/src/skills/include-graph.ts +5 -5
- package/src/skills/remote-skill-policy.ts +10 -16
- package/src/skills/skill-file-provider.ts +1 -1
- package/src/skills/skill-file-types.ts +13 -0
- package/src/skills/skillssh-audit-types.ts +28 -0
- package/src/skills/skillssh-registry.ts +8 -21
- package/src/subagent/index.ts +1 -7
- package/src/subagent/manager.ts +1 -15
- package/src/tasks/task-runner.ts +0 -1
- package/src/tasks/task-store.ts +0 -3
- package/src/telemetry/types.ts +2 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
- package/src/tools/apps/executors.ts +56 -69
- package/src/tools/background-tool-registry.ts +17 -3
- package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
- package/src/tools/browser/browser-execution.ts +2 -2
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
- package/src/tools/browser/cdp-client/factory.ts +23 -24
- package/src/tools/browser/cdp-client/index.ts +1 -14
- package/src/tools/computer-use/definitions.ts +42 -20
- package/src/tools/executor.ts +2 -0
- package/src/tools/host-filesystem/edit.test.ts +151 -0
- package/src/tools/host-filesystem/edit.ts +68 -0
- package/src/tools/host-filesystem/read.test.ts +129 -0
- package/src/tools/host-filesystem/read.ts +68 -0
- package/src/tools/host-filesystem/transfer.test.ts +127 -2
- package/src/tools/host-filesystem/transfer.ts +78 -3
- package/src/tools/host-filesystem/write.test.ts +134 -0
- package/src/tools/host-filesystem/write.ts +68 -0
- package/src/tools/host-terminal/host-shell.ts +66 -1
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/register.test.ts +12 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/schedule/create.ts +6 -0
- package/src/tools/schedule/list.ts +2 -0
- package/src/tools/schedule/update.ts +10 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
- package/src/tools/shared/filesystem/path-policy.ts +25 -1
- package/src/tools/skills/load.ts +0 -32
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +32 -11
- package/src/tools/types.ts +28 -2
- package/src/tts/provider-catalog.ts +3 -5
- package/src/usage/pricing.ts +1 -1
- package/src/util/disk-usage.ts +138 -0
- package/src/util/platform.ts +21 -11
- package/src/util/process-liveness.ts +26 -0
- package/src/workspace/hatched-date.ts +86 -0
- package/src/workspace/heartbeat-service.ts +19 -0
- package/src/workspace/migrations/003-seed-device-id.ts +1 -1
- package/src/workspace/migrations/006-services-config.ts +8 -5
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
- package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
- package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
- package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/utils.ts +21 -0
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
- package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
- package/src/__tests__/twilio-rest.test.ts +0 -34
- package/src/backup/__tests__/backup-key.test.ts +0 -152
- package/src/backup/__tests__/backup-worker.test.ts +0 -782
- package/src/backup/__tests__/offsite-writer.test.ts +0 -641
- package/src/backup/__tests__/stream-crypt.test.ts +0 -228
- package/src/backup/backup-key.ts +0 -137
- package/src/backup/backup-worker.ts +0 -472
- package/src/backup/offsite-writer.ts +0 -222
- package/src/backup/stream-crypt.ts +0 -263
- package/src/daemon/message-types/pairing.ts +0 -58
- package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
- package/src/memory/v2/skill-qdrant.ts +0 -395
- package/src/outbound-proxy/config.ts +0 -20
- package/src/outbound-proxy/health.ts +0 -18
- package/src/outbound-proxy/types.ts +0 -150
- package/src/runtime/capability-tokens.ts +0 -190
- package/src/signals/bash.ts +0 -198
- 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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
//
|
|
278
|
-
// the
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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<
|
|
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
|
|
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 =
|
|
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 (
|
|
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
|
-
//
|
|
1120
|
-
//
|
|
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
|
-
|
|
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 (
|
|
1394
|
+
if (preExisting) {
|
|
1170
1395
|
backupPath = generateBackupPath(entry.livePath);
|
|
1171
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
42
|
-
|
|
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<
|
|
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, {
|
|
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
|
// ---------------------------------------------------------------------------
|