@vellumai/assistant 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +32 -49
- 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/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 +39 -1
- 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/skill-host-contracts/src/assistant-event.ts +9 -0
- 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 +565 -12
- 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 +374 -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 +109 -2
- package/src/__tests__/assistant-event.test.ts +10 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -2
- package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -7
- package/src/__tests__/background-shell-host-bash.test.ts +14 -15
- 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-domain.test.ts +0 -2
- package/src/__tests__/call-routes-http.test.ts +0 -2
- package/src/__tests__/channel-readiness-service.test.ts +59 -1
- package/src/__tests__/checker.test.ts +3 -4
- package/src/__tests__/config-loader-backfill.test.ts +90 -155
- 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-set-platform-guard.test.ts +48 -4
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
- package/src/__tests__/config-watcher.test.ts +2 -2
- 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-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-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-slash-commands.test.ts +0 -4
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -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 +5 -12
- package/src/__tests__/cu-unified-flow.test.ts +185 -23
- package/src/__tests__/daemon-credential-client.test.ts +101 -19
- package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- 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-service.test.ts +718 -1
- package/src/__tests__/helpers/call-route-handler.ts +7 -1
- package/src/__tests__/host-app-control-proxy.test.ts +602 -0
- package/src/__tests__/host-app-control-routes.test.ts +263 -0
- package/src/__tests__/host-bash-proxy.test.ts +246 -47
- package/src/__tests__/host-bash-routes.test.ts +294 -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 +41 -52
- package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
- package/src/__tests__/host-file-edit-tool.test.ts +47 -1
- package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
- package/src/__tests__/host-file-proxy.test.ts +37 -43
- package/src/__tests__/host-file-read-tool.test.ts +17 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +262 -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 +583 -0
- package/src/__tests__/host-transfer-proxy.test.ts +121 -22
- package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- 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__/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-skill-lifecycle.test.ts +0 -1
- package/src/__tests__/mcp-auth-routes.test.ts +197 -0
- package/src/__tests__/mcp-cli.test.ts +338 -2
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
- package/src/__tests__/migration-import-commit-http.test.ts +108 -2
- package/src/__tests__/mock-gateway-ipc.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +0 -2
- package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
- package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
- package/src/__tests__/prechat-onboarding-contract.test.ts +3 -1
- 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__/public-ingress-urls.test.ts +97 -0
- 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 +10 -6
- package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
- package/src/__tests__/schedule-retry.test.ts +715 -0
- package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- 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__/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-backfill-installation-id.test.ts +1 -5
- package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
- 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/bundler/app-bundler.ts +51 -3
- 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 -1
- package/src/cli/commands/backup.ts +6 -331
- package/src/cli/commands/clients.ts +36 -37
- package/src/cli/commands/contacts.ts +73 -0
- package/src/cli/commands/conversations.ts +2 -5
- package/src/cli/commands/credentials.ts +15 -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 +296 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
- package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
- package/src/cli/commands/platform/disconnect.ts +5 -4
- package/src/cli/commands/platform/index.ts +0 -18
- package/src/cli/lib/daemon-credential-client.ts +110 -28
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +67 -10
- 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/phone-calls/TOOLS.json +0 -12
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
- package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
- 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 -12
- package/src/config/feature-flag-registry.json +21 -133
- package/src/config/loader.ts +73 -99
- 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 +7 -4
- package/src/config/schemas/calls.ts +0 -9
- package/src/config/schemas/heartbeat.ts +63 -0
- package/src/config/schemas/ingress.ts +10 -6
- package/src/config/schemas/llm.ts +5 -10
- package/src/config/schemas/memory-lifecycle.ts +77 -24
- package/src/config/schemas/memory-v2.ts +48 -4
- package/src/config/schemas/platform.ts +6 -0
- package/src/config/schemas/services.ts +1 -15
- package/src/config/schemas/skills.ts +0 -6
- package/src/config/seed-inference-profiles.ts +1 -1
- package/src/contacts/contact-store.ts +0 -30
- 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 +126 -5
- package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
- package/src/daemon/config-watcher.ts +4 -3
- package/src/daemon/conversation-agent-loop-handlers.ts +21 -3
- package/src/daemon/conversation-agent-loop.ts +32 -28
- package/src/daemon/conversation-lifecycle.ts +8 -1
- package/src/daemon/conversation-process.ts +16 -11
- package/src/daemon/conversation-runtime-assembly.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +125 -4
- package/src/daemon/conversation-tool-setup.ts +16 -55
- package/src/daemon/conversation.ts +21 -2
- package/src/daemon/doordash-steps.ts +1 -1
- package/src/daemon/handlers/shared.ts +4 -1
- package/src/daemon/host-app-control-proxy.ts +293 -0
- package/src/daemon/host-bash-proxy.ts +84 -74
- package/src/daemon/host-browser-proxy.ts +67 -82
- package/src/daemon/host-cu-proxy.ts +81 -86
- package/src/daemon/host-file-proxy.ts +93 -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 +247 -129
- package/src/daemon/lifecycle.ts +115 -117
- package/src/daemon/message-protocol.ts +3 -8
- package/src/daemon/message-types/contacts.ts +23 -1
- package/src/daemon/message-types/conversations.ts +11 -8
- 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/schedules.ts +8 -3
- package/src/daemon/message-types/skills.ts +2 -2
- package/src/daemon/process-message.ts +18 -1
- 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/events/tool-audit-listener.ts +2 -1
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +15 -7
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
- package/src/heartbeat/heartbeat-run-store.ts +236 -0
- package/src/heartbeat/heartbeat-service.ts +280 -49
- 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/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/public-ingress-urls.ts +32 -34
- package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
- package/src/ipc/assistant-server.ts +14 -1
- package/src/ipc/cli-client.ts +32 -1
- package/src/live-voice/live-voice-metrics.ts +10 -10
- 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/memory/__tests__/jobs-store-job-classes.test.ts +24 -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/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 +32 -7
- package/src/memory/context-search/sources/memory-v2.ts +17 -5
- package/src/memory/conversation-crud.ts +1 -1
- package/src/memory/conversation-key-store.ts +2 -15
- package/src/memory/db-init.ts +4 -0
- package/src/memory/embedding-backend.ts +9 -21
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -4
- package/src/memory/graph/conversation-graph-memory.ts +1 -24
- package/src/memory/graph/graph-search.ts +8 -0
- package/src/memory/graph/retriever.ts +28 -0
- package/src/memory/graph/tools.ts +1 -1
- 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 +66 -22
- package/src/memory/jobs-worker.ts +112 -63
- package/src/memory/memory-v2-activation-log-store.ts +1 -1
- 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/index.ts +5 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/pkb/pkb-search.ts +7 -0
- package/src/memory/qdrant-client.ts +50 -20
- package/src/memory/schema/infrastructure.ts +15 -0
- package/src/memory/search/semantic.ts +7 -0
- package/src/memory/sparse-tokenize.ts +49 -0
- package/src/memory/v2/__tests__/activation.test.ts +77 -95
- package/src/memory/v2/__tests__/injection.test.ts +43 -21
- package/src/memory/v2/__tests__/sim.test.ts +166 -6
- package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
- package/src/memory/v2/__tests__/static-context.test.ts +0 -1
- package/src/memory/v2/activation.ts +69 -88
- package/src/memory/v2/consolidation-job.ts +3 -5
- package/src/memory/v2/constants.ts +7 -0
- package/src/memory/v2/injection.ts +86 -53
- package/src/memory/v2/prompts/consolidation.ts +312 -91
- package/src/memory/v2/qdrant.ts +99 -1
- package/src/memory/v2/sim.ts +126 -16
- package/src/memory/v2/skill-qdrant.ts +12 -3
- package/src/memory/v2/skill-store.ts +16 -1
- package/src/memory/v2/sparse-bm25.ts +245 -0
- package/src/memory/v2/static-context.ts +6 -5
- 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/guardian-question-mode.ts +5 -5
- package/src/oauth/connect-orchestrator.ts +4 -0
- package/src/oauth/credential-token-resolver.ts +1 -3
- package/src/oauth/manual-token-connection.ts +0 -4
- 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/prompts/bootstrap-cleanup.ts +27 -0
- package/src/prompts/system-prompt.ts +3 -18
- package/src/prompts/templates/SOUL.md +13 -1
- package/src/providers/speech-to-text/provider-catalog.ts +7 -8
- package/src/runtime/assistant-event-hub.ts +118 -96
- package/src/runtime/assistant-event.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
- package/src/runtime/auth/middleware.ts +0 -96
- package/src/runtime/auth/route-policy.ts +19 -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/http-server.ts +3 -329
- package/src/runtime/http-types.ts +0 -5
- 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 +35 -9
- package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
- 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 +1 -0
- package/src/runtime/routes/contact-prompt-routes.ts +183 -0
- package/src/runtime/routes/conversation-query-routes.ts +36 -1
- package/src/runtime/routes/conversation-routes.ts +30 -13
- package/src/runtime/routes/document-pdf-renderer.ts +165 -0
- package/src/runtime/routes/documents-routes.ts +30 -0
- package/src/runtime/routes/errors.ts +19 -4
- package/src/runtime/routes/events-routes.ts +12 -6
- 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 +36 -6
- package/src/runtime/routes/host-browser-routes.ts +108 -13
- package/src/runtime/routes/host-cu-routes.ts +44 -14
- package/src/runtime/routes/host-file-routes.ts +33 -10
- package/src/runtime/routes/host-transfer-routes.ts +64 -24
- 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 +15 -43
- package/src/runtime/routes/inbound-message-handler.ts +1 -9
- 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/transcribe-audio.test.ts +0 -20
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
- package/src/runtime/routes/index.ts +8 -0
- package/src/runtime/routes/mcp-auth-routes.ts +132 -0
- package/src/runtime/routes/memory-item-routes.ts +10 -12
- package/src/runtime/routes/memory-v2-routes.ts +441 -1
- package/src/runtime/routes/migration-routes.ts +96 -0
- package/src/runtime/routes/schedule-routes.ts +7 -0
- 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/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 +63 -38
- package/src/security/oauth-callback-registry.ts +8 -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 +5 -5
- 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/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/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.ts +26 -0
- package/src/tools/host-filesystem/read.ts +26 -0
- package/src/tools/host-filesystem/transfer.ts +31 -1
- package/src/tools/host-filesystem/write.ts +26 -0
- package/src/tools/host-terminal/host-shell.ts +58 -0
- 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/tool-approval-handler.ts +1 -5
- package/src/tools/types.ts +4 -0
- package/src/usage/pricing.ts +1 -1
- package/src/workspace/hatched-date.ts +86 -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/AGENTS.md +1 -1
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
- package/src/workspace/migrations/utils.ts +21 -0
- 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/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/mcp-reload.ts +0 -18
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buffer-vs-streaming `.vbundle` import parity suite.
|
|
3
|
+
*
|
|
4
|
+
* Pins the existing disk-outcome equivalence between `commitImport`
|
|
5
|
+
* (buffer-based) and `streamCommitImport` (streaming) as a regression net
|
|
6
|
+
* BEFORE any production code is migrated to a shared policy module.
|
|
7
|
+
*
|
|
8
|
+
* Each test builds one archive, mkdtemps two sibling workspaces, seeds them
|
|
9
|
+
* identically, runs each importer against its own workspace, and asserts the
|
|
10
|
+
* post-import disk trees are byte-for-byte identical:
|
|
11
|
+
*
|
|
12
|
+
* expect(walkDiskTree(streamWs)).toEqual(walkDiskTree(bufferWs))
|
|
13
|
+
*
|
|
14
|
+
* Per-case invariants (carry-forward markers survive, persona lands at the
|
|
15
|
+
* right disk path, traversal entries do not erase the workspace, etc.) are
|
|
16
|
+
* asserted on top of disk-tree equality.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
import {
|
|
21
|
+
existsSync,
|
|
22
|
+
mkdirSync,
|
|
23
|
+
mkdtempSync,
|
|
24
|
+
readdirSync,
|
|
25
|
+
readFileSync,
|
|
26
|
+
realpathSync,
|
|
27
|
+
rmSync,
|
|
28
|
+
writeFileSync,
|
|
29
|
+
} from "node:fs";
|
|
30
|
+
import { tmpdir } from "node:os";
|
|
31
|
+
import { dirname, join, relative } from "node:path";
|
|
32
|
+
import { Readable } from "node:stream";
|
|
33
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
34
|
+
|
|
35
|
+
import { buildVBundle } from "../vbundle-builder.js";
|
|
36
|
+
import { DefaultPathResolver } from "../vbundle-import-analyzer.js";
|
|
37
|
+
import { commitImport } from "../vbundle-importer.js";
|
|
38
|
+
import { streamCommitImport } from "../vbundle-streaming-importer.js";
|
|
39
|
+
import { defaultV1Options } from "./v1-test-helpers.js";
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Shared helpers
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Allocate a fresh workspace path under a temp parent dir we own. The parent
|
|
47
|
+
* is realpath-resolved so macOS `/var` → `/private/var` symlink mismatches
|
|
48
|
+
* don't trip the streaming importer's `rebaseOntoTempWorkspace` containment
|
|
49
|
+
* check (which compares `resolve(diskPath)` against `resolve(workspaceDir)`).
|
|
50
|
+
*
|
|
51
|
+
* Returns `<parent>/workspace`. The directory itself is NOT created — tests
|
|
52
|
+
* that need it pre-existing call `mkdirSync` themselves.
|
|
53
|
+
*/
|
|
54
|
+
function freshWorkspace(): string {
|
|
55
|
+
const parent = realpathSync(
|
|
56
|
+
mkdtempSync(join(tmpdir(), "vbundle-import-parity-")),
|
|
57
|
+
);
|
|
58
|
+
return join(parent, "workspace");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Best-effort cleanup of a workspace's parent dir. */
|
|
62
|
+
function cleanupWorkspaceParent(workspaceDir: string): void {
|
|
63
|
+
try {
|
|
64
|
+
rmSync(join(workspaceDir, ".."), { recursive: true, force: true });
|
|
65
|
+
} catch {
|
|
66
|
+
// best-effort
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function sha256Hex(data: Uint8Array): string {
|
|
71
|
+
return createHash("sha256").update(data).digest("hex");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Recursively walk `root` and return a `Map<relPath, sha256Hex>` for every
|
|
76
|
+
* regular file. Skips dot-prefixed scratch dirs the streaming importer may
|
|
77
|
+
* leave behind on failure paths (`.import-*`, `.pre-import-*`) plus the
|
|
78
|
+
* import marker file — the buffer importer never produces these, so they'd
|
|
79
|
+
* spuriously break parity if included.
|
|
80
|
+
*
|
|
81
|
+
* Two importers are parity-equivalent for a given input iff the maps they
|
|
82
|
+
* produce on identically-seeded sibling workspaces are equal.
|
|
83
|
+
*/
|
|
84
|
+
function walkDiskTree(root: string): Map<string, string> {
|
|
85
|
+
const out = new Map<string, string>();
|
|
86
|
+
if (!existsSync(root)) return out;
|
|
87
|
+
|
|
88
|
+
const stack: string[] = [root];
|
|
89
|
+
while (stack.length > 0) {
|
|
90
|
+
const dir = stack.pop()!;
|
|
91
|
+
let entries;
|
|
92
|
+
try {
|
|
93
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
94
|
+
} catch {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
// Skip streaming-importer scratch artifacts so they don't show up as
|
|
99
|
+
// false negatives in the parity comparison.
|
|
100
|
+
if (
|
|
101
|
+
entry.name.startsWith(".import-") ||
|
|
102
|
+
entry.name.startsWith(".pre-import-") ||
|
|
103
|
+
entry.name === ".import-marker.json"
|
|
104
|
+
) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const abs = join(dir, entry.name);
|
|
108
|
+
if (entry.isDirectory()) {
|
|
109
|
+
stack.push(abs);
|
|
110
|
+
} else if (entry.isFile()) {
|
|
111
|
+
const rel = relative(root, abs);
|
|
112
|
+
out.set(rel, sha256Hex(readFileSync(abs)));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interface SeedFile {
|
|
120
|
+
relPath: string;
|
|
121
|
+
content: string | Uint8Array;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Mkdir parents and write each file to `workspaceDir`. */
|
|
125
|
+
function seedLiveWorkspace(workspaceDir: string, files: SeedFile[]): void {
|
|
126
|
+
mkdirSync(workspaceDir, { recursive: true });
|
|
127
|
+
for (const { relPath, content } of files) {
|
|
128
|
+
const abs = join(workspaceDir, relPath);
|
|
129
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
130
|
+
writeFileSync(abs, content);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function runBufferImport(workspaceDir: string, archive: Uint8Array): void {
|
|
135
|
+
const result = commitImport({
|
|
136
|
+
archiveData: archive,
|
|
137
|
+
pathResolver: new DefaultPathResolver(workspaceDir),
|
|
138
|
+
workspaceDir,
|
|
139
|
+
});
|
|
140
|
+
if (!result.ok) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`buffer commitImport unexpectedly failed: ${JSON.stringify(result)}`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function runStreamImport(
|
|
148
|
+
workspaceDir: string,
|
|
149
|
+
archive: Uint8Array,
|
|
150
|
+
importCredentials?: (
|
|
151
|
+
credentials: Array<{ account: string; value: string }>,
|
|
152
|
+
) => Promise<void>,
|
|
153
|
+
): Promise<void> {
|
|
154
|
+
const result = await streamCommitImport({
|
|
155
|
+
source: Readable.from([Buffer.from(archive)]),
|
|
156
|
+
pathResolver: new DefaultPathResolver(workspaceDir),
|
|
157
|
+
workspaceDir,
|
|
158
|
+
importCredentials,
|
|
159
|
+
});
|
|
160
|
+
if (!result.ok) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`streamCommitImport unexpectedly failed: ${JSON.stringify(result)}`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Parity tests
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
describe("vbundle import parity (buffer vs streaming)", () => {
|
|
172
|
+
let bufferWs: string;
|
|
173
|
+
let streamWs: string;
|
|
174
|
+
|
|
175
|
+
beforeEach(() => {
|
|
176
|
+
bufferWs = freshWorkspace();
|
|
177
|
+
streamWs = freshWorkspace();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
afterEach(() => {
|
|
181
|
+
cleanupWorkspaceParent(bufferWs);
|
|
182
|
+
cleanupWorkspaceParent(streamWs);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("A — full workspace + credentials: identical disk outcome", async () => {
|
|
186
|
+
const dbBytes = new Uint8Array(16);
|
|
187
|
+
for (let i = 0; i < dbBytes.length; i++) dbBytes[i] = (i * 17) & 0xff;
|
|
188
|
+
|
|
189
|
+
const configJson = JSON.stringify({ version: 1 });
|
|
190
|
+
const metadataJson = JSON.stringify({
|
|
191
|
+
version: 5,
|
|
192
|
+
credentials: [
|
|
193
|
+
{
|
|
194
|
+
credentialId: "id-openai-api_key",
|
|
195
|
+
service: "openai",
|
|
196
|
+
field: "api_key",
|
|
197
|
+
allowedTools: [],
|
|
198
|
+
allowedDomains: [],
|
|
199
|
+
createdAt: 1700000000000,
|
|
200
|
+
updatedAt: 1700000000000,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const openaiKey = new Uint8Array(16);
|
|
206
|
+
for (let i = 0; i < openaiKey.length; i++) openaiKey[i] = (i + 5) & 0xff;
|
|
207
|
+
const anthropicKey = new TextEncoder().encode("sk-ant-test");
|
|
208
|
+
|
|
209
|
+
const { archive } = buildVBundle({
|
|
210
|
+
files: [
|
|
211
|
+
{ path: "workspace/data/db/assistant.db", data: dbBytes },
|
|
212
|
+
{
|
|
213
|
+
path: "workspace/config.json",
|
|
214
|
+
data: new TextEncoder().encode(configJson),
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
path: "workspace/data/credentials/metadata.json",
|
|
218
|
+
data: new TextEncoder().encode(metadataJson),
|
|
219
|
+
},
|
|
220
|
+
{ path: "credentials/openai-key", data: openaiKey },
|
|
221
|
+
{ path: "credentials/anthropic-key", data: anthropicKey },
|
|
222
|
+
],
|
|
223
|
+
...defaultV1Options(),
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Pre-create both workspaces (streaming importer expects to operate
|
|
227
|
+
// against an existing dir and the atomic-swap path requires it).
|
|
228
|
+
mkdirSync(bufferWs, { recursive: true });
|
|
229
|
+
mkdirSync(streamWs, { recursive: true });
|
|
230
|
+
|
|
231
|
+
runBufferImport(bufferWs, archive);
|
|
232
|
+
await runStreamImport(streamWs, archive, async () => {
|
|
233
|
+
// parity test: credentials are intercepted but not persisted
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const bufferMap = walkDiskTree(bufferWs);
|
|
237
|
+
const streamMap = walkDiskTree(streamWs);
|
|
238
|
+
|
|
239
|
+
expect(streamMap).toEqual(bufferMap);
|
|
240
|
+
|
|
241
|
+
// Sanity: the three workspace-bound files we expect both importers to
|
|
242
|
+
// land are present in the parity map.
|
|
243
|
+
expect(bufferMap.has("data/db/assistant.db")).toBe(true);
|
|
244
|
+
expect(bufferMap.has("config.json")).toBe(true);
|
|
245
|
+
expect(bufferMap.has("data/credentials/metadata.json")).toBe(true);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("B — config-only partial bundle: live preserved paths survive on both sides", async () => {
|
|
249
|
+
const seeds: SeedFile[] = [
|
|
250
|
+
{ relPath: "data/db/marker", content: "db-marker" },
|
|
251
|
+
{ relPath: "data/qdrant/marker", content: "qdrant-marker" },
|
|
252
|
+
{ relPath: "embedding-models/marker", content: "embedding-marker" },
|
|
253
|
+
{ relPath: "deprecated/marker", content: "deprecated-marker" },
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
seedLiveWorkspace(bufferWs, seeds);
|
|
257
|
+
seedLiveWorkspace(streamWs, seeds);
|
|
258
|
+
|
|
259
|
+
const configJson = JSON.stringify({ version: 1 });
|
|
260
|
+
const { archive } = buildVBundle({
|
|
261
|
+
files: [
|
|
262
|
+
// Validator requires the DB entry (legacy or workspace-prefixed).
|
|
263
|
+
// It's not the focus of this case — config.json is — but it must
|
|
264
|
+
// be present for the bundle to validate.
|
|
265
|
+
{ path: "data/db/assistant.db", data: new Uint8Array() },
|
|
266
|
+
{
|
|
267
|
+
path: "workspace/config.json",
|
|
268
|
+
data: new TextEncoder().encode(configJson),
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
...defaultV1Options(),
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
runBufferImport(bufferWs, archive);
|
|
275
|
+
await runStreamImport(streamWs, archive);
|
|
276
|
+
|
|
277
|
+
const bufferMap = walkDiskTree(bufferWs);
|
|
278
|
+
const streamMap = walkDiskTree(streamWs);
|
|
279
|
+
|
|
280
|
+
expect(streamMap).toEqual(bufferMap);
|
|
281
|
+
|
|
282
|
+
// Each carry-forward marker must still be on disk.
|
|
283
|
+
for (const seed of seeds) {
|
|
284
|
+
expect(bufferMap.has(seed.relPath)).toBe(true);
|
|
285
|
+
expect(streamMap.has(seed.relPath)).toBe(true);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("C — legacy prompts/USER.md: both importers land it at users/<slug>.md", async () => {
|
|
290
|
+
const personaBody = `_ Lines starting with _ are comments - they won't appear in the system prompt
|
|
291
|
+
|
|
292
|
+
# User Profile
|
|
293
|
+
|
|
294
|
+
- Preferred name/reference: Captain Parity
|
|
295
|
+
- Pronouns: they/them
|
|
296
|
+
- Locale: en-US
|
|
297
|
+
- Work role: Quartermaster
|
|
298
|
+
- Goals: Verify importer parity
|
|
299
|
+
- Hobbies/fun: Diff'ing trees
|
|
300
|
+
- Daily tools: Terminal
|
|
301
|
+
`;
|
|
302
|
+
|
|
303
|
+
// Pre-create users/ in both workspaces so the resolver's containment
|
|
304
|
+
// check has a real on-disk parent to anchor against.
|
|
305
|
+
mkdirSync(join(bufferWs, "users"), { recursive: true });
|
|
306
|
+
mkdirSync(join(streamWs, "users"), { recursive: true });
|
|
307
|
+
|
|
308
|
+
const { archive } = buildVBundle({
|
|
309
|
+
files: [
|
|
310
|
+
{ path: "data/db/assistant.db", data: new Uint8Array() },
|
|
311
|
+
{
|
|
312
|
+
path: "prompts/USER.md",
|
|
313
|
+
data: new TextEncoder().encode(personaBody),
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
...defaultV1Options(),
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const bufferGuardianPath = join(bufferWs, "users", "captain.md");
|
|
320
|
+
const streamGuardianPath = join(streamWs, "users", "captain.md");
|
|
321
|
+
|
|
322
|
+
const bufferResolver = new DefaultPathResolver(
|
|
323
|
+
bufferWs,
|
|
324
|
+
undefined,
|
|
325
|
+
() => bufferGuardianPath,
|
|
326
|
+
);
|
|
327
|
+
const streamResolver = new DefaultPathResolver(
|
|
328
|
+
streamWs,
|
|
329
|
+
undefined,
|
|
330
|
+
() => streamGuardianPath,
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
const bufferResult = commitImport({
|
|
334
|
+
archiveData: archive,
|
|
335
|
+
pathResolver: bufferResolver,
|
|
336
|
+
workspaceDir: bufferWs,
|
|
337
|
+
});
|
|
338
|
+
expect(bufferResult.ok).toBe(true);
|
|
339
|
+
|
|
340
|
+
const streamResult = await streamCommitImport({
|
|
341
|
+
source: Readable.from([Buffer.from(archive)]),
|
|
342
|
+
pathResolver: streamResolver,
|
|
343
|
+
workspaceDir: streamWs,
|
|
344
|
+
});
|
|
345
|
+
expect(streamResult.ok).toBe(true);
|
|
346
|
+
|
|
347
|
+
const bufferMap = walkDiskTree(bufferWs);
|
|
348
|
+
const streamMap = walkDiskTree(streamWs);
|
|
349
|
+
|
|
350
|
+
expect(streamMap).toEqual(bufferMap);
|
|
351
|
+
|
|
352
|
+
expect(existsSync(bufferGuardianPath)).toBe(true);
|
|
353
|
+
expect(existsSync(streamGuardianPath)).toBe(true);
|
|
354
|
+
expect(readFileSync(bufferGuardianPath, "utf-8")).toBe(personaBody);
|
|
355
|
+
expect(readFileSync(streamGuardianPath, "utf-8")).toBe(personaBody);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("D — no workspace entries at all: legacy bundle leaves seeded files in place", async () => {
|
|
359
|
+
const seeds: SeedFile[] = [
|
|
360
|
+
{ relPath: "unrelated.txt", content: "stay" },
|
|
361
|
+
{ relPath: "custom-dir/note.md", content: "# note" },
|
|
362
|
+
];
|
|
363
|
+
|
|
364
|
+
seedLiveWorkspace(bufferWs, seeds);
|
|
365
|
+
seedLiveWorkspace(streamWs, seeds);
|
|
366
|
+
|
|
367
|
+
const dbBytes = new TextEncoder().encode("legacy-db-payload");
|
|
368
|
+
const { archive } = buildVBundle({
|
|
369
|
+
files: [{ path: "data/db/assistant.db", data: dbBytes }],
|
|
370
|
+
...defaultV1Options(),
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
runBufferImport(bufferWs, archive);
|
|
374
|
+
await runStreamImport(streamWs, archive);
|
|
375
|
+
|
|
376
|
+
const bufferMap = walkDiskTree(bufferWs);
|
|
377
|
+
const streamMap = walkDiskTree(streamWs);
|
|
378
|
+
|
|
379
|
+
expect(streamMap).toEqual(bufferMap);
|
|
380
|
+
|
|
381
|
+
for (const seed of seeds) {
|
|
382
|
+
expect(bufferMap.has(seed.relPath)).toBe(true);
|
|
383
|
+
expect(streamMap.has(seed.relPath)).toBe(true);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test("E — path-traversal workspace entry: both importers refuse to clear", async () => {
|
|
388
|
+
seedLiveWorkspace(bufferWs, [{ relPath: "marker.txt", content: "keep" }]);
|
|
389
|
+
seedLiveWorkspace(streamWs, [{ relPath: "marker.txt", content: "keep" }]);
|
|
390
|
+
|
|
391
|
+
const { archive } = buildVBundle({
|
|
392
|
+
files: [
|
|
393
|
+
{ path: "data/db/assistant.db", data: new Uint8Array() },
|
|
394
|
+
{
|
|
395
|
+
path: "workspace/../../etc/passwd",
|
|
396
|
+
data: new TextEncoder().encode("nope"),
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
...defaultV1Options(),
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
runBufferImport(bufferWs, archive);
|
|
403
|
+
await runStreamImport(streamWs, archive);
|
|
404
|
+
|
|
405
|
+
const bufferMap = walkDiskTree(bufferWs);
|
|
406
|
+
const streamMap = walkDiskTree(streamWs);
|
|
407
|
+
|
|
408
|
+
expect(streamMap).toEqual(bufferMap);
|
|
409
|
+
|
|
410
|
+
expect(bufferMap.has("marker.txt")).toBe(true);
|
|
411
|
+
expect(streamMap.has("marker.txt")).toBe(true);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the pure policy module shared by both vbundle importers.
|
|
3
|
+
*
|
|
4
|
+
* No `node:fs`, no temp dirs — every test exercises a constant or a
|
|
5
|
+
* predicate over strings.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
compareSemver,
|
|
12
|
+
CONFIG_ARCHIVE_PATHS,
|
|
13
|
+
CREDENTIAL_METADATA_ARCHIVE_PATH,
|
|
14
|
+
evaluateRuntimeCompatibility,
|
|
15
|
+
isConfigArchivePath,
|
|
16
|
+
isCredentialMetadataArchivePath,
|
|
17
|
+
isLegacyPersonaArchivePath,
|
|
18
|
+
isWorkspaceNamespacedArchivePath,
|
|
19
|
+
LEGACY_USER_MD_ARCHIVE_PATH,
|
|
20
|
+
partitionWorkspacePreserveSkipDirs,
|
|
21
|
+
WORKSPACE_PRESERVE_PATHS,
|
|
22
|
+
} from "../vbundle-import-policy.js";
|
|
23
|
+
|
|
24
|
+
describe("LEGACY_USER_MD_ARCHIVE_PATH", () => {
|
|
25
|
+
test("equals the legacy guardian persona archive path", () => {
|
|
26
|
+
expect(LEGACY_USER_MD_ARCHIVE_PATH).toBe("prompts/USER.md");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("CONFIG_ARCHIVE_PATHS", () => {
|
|
31
|
+
test("contains exactly the two known config archive paths", () => {
|
|
32
|
+
expect(CONFIG_ARCHIVE_PATHS.size).toBe(2);
|
|
33
|
+
expect(CONFIG_ARCHIVE_PATHS.has("workspace/config.json")).toBe(true);
|
|
34
|
+
expect(CONFIG_ARCHIVE_PATHS.has("config/settings.json")).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("CREDENTIAL_METADATA_ARCHIVE_PATH", () => {
|
|
39
|
+
test("equals the workspace-namespaced credential metadata path", () => {
|
|
40
|
+
expect(CREDENTIAL_METADATA_ARCHIVE_PATH).toBe(
|
|
41
|
+
"workspace/data/credentials/metadata.json",
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("WORKSPACE_PRESERVE_PATHS", () => {
|
|
47
|
+
test("matches the literal 4-element ordered list", () => {
|
|
48
|
+
expect(WORKSPACE_PRESERVE_PATHS).toEqual([
|
|
49
|
+
"embedding-models",
|
|
50
|
+
"deprecated",
|
|
51
|
+
"data/db",
|
|
52
|
+
"data/qdrant",
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("isWorkspaceNamespacedArchivePath", () => {
|
|
58
|
+
test("true for paths under workspace/", () => {
|
|
59
|
+
expect(isWorkspaceNamespacedArchivePath("workspace/foo")).toBe(true);
|
|
60
|
+
expect(isWorkspaceNamespacedArchivePath("workspace/data/db/x")).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("false for non-workspace paths", () => {
|
|
64
|
+
expect(isWorkspaceNamespacedArchivePath("prompts/USER.md")).toBe(false);
|
|
65
|
+
expect(isWorkspaceNamespacedArchivePath("data/db/assistant.db")).toBe(
|
|
66
|
+
false,
|
|
67
|
+
);
|
|
68
|
+
expect(isWorkspaceNamespacedArchivePath("")).toBe(false);
|
|
69
|
+
expect(isWorkspaceNamespacedArchivePath("workspace")).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("isLegacyPersonaArchivePath", () => {
|
|
74
|
+
test("true only for the exact legacy path", () => {
|
|
75
|
+
expect(isLegacyPersonaArchivePath("prompts/USER.md")).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("false for near-misses and unrelated paths", () => {
|
|
79
|
+
expect(isLegacyPersonaArchivePath("prompts/USER")).toBe(false);
|
|
80
|
+
expect(isLegacyPersonaArchivePath("workspace/prompts/USER.md")).toBe(false);
|
|
81
|
+
expect(isLegacyPersonaArchivePath("")).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("isConfigArchivePath", () => {
|
|
86
|
+
test("true for both members of CONFIG_ARCHIVE_PATHS", () => {
|
|
87
|
+
expect(isConfigArchivePath("workspace/config.json")).toBe(true);
|
|
88
|
+
expect(isConfigArchivePath("config/settings.json")).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("false for non-members", () => {
|
|
92
|
+
expect(isConfigArchivePath("workspace/foo.json")).toBe(false);
|
|
93
|
+
expect(isConfigArchivePath("config/settings")).toBe(false);
|
|
94
|
+
expect(isConfigArchivePath("")).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("isCredentialMetadataArchivePath", () => {
|
|
99
|
+
test("true for the exact constant", () => {
|
|
100
|
+
expect(
|
|
101
|
+
isCredentialMetadataArchivePath(
|
|
102
|
+
"workspace/data/credentials/metadata.json",
|
|
103
|
+
),
|
|
104
|
+
).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("false for the legacy non-prefixed form and empty string", () => {
|
|
108
|
+
expect(
|
|
109
|
+
isCredentialMetadataArchivePath("data/credentials/metadata.json"),
|
|
110
|
+
).toBe(false);
|
|
111
|
+
expect(isCredentialMetadataArchivePath("")).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("partitionWorkspacePreserveSkipDirs", () => {
|
|
116
|
+
test("splits preserve-paths into top-level vs data-subdir skip sets", () => {
|
|
117
|
+
const { topLevelSkipDirs, dataSubdirSkipDirs } =
|
|
118
|
+
partitionWorkspacePreserveSkipDirs();
|
|
119
|
+
|
|
120
|
+
expect(topLevelSkipDirs.size).toBe(2);
|
|
121
|
+
expect(topLevelSkipDirs.has("embedding-models")).toBe(true);
|
|
122
|
+
expect(topLevelSkipDirs.has("deprecated")).toBe(true);
|
|
123
|
+
|
|
124
|
+
expect(dataSubdirSkipDirs.size).toBe(2);
|
|
125
|
+
expect(dataSubdirSkipDirs.has("db")).toBe(true);
|
|
126
|
+
expect(dataSubdirSkipDirs.has("qdrant")).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("compareSemver", () => {
|
|
131
|
+
test("0.10.0 > 0.9.0 (numeric, not lexical)", () => {
|
|
132
|
+
expect(compareSemver("0.10.0", "0.9.0")).toBe(1);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("equal triples return 0", () => {
|
|
136
|
+
expect(compareSemver("0.7.1", "0.7.1")).toBe(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("smaller patch returns -1", () => {
|
|
140
|
+
expect(compareSemver("0.7.0", "0.7.1")).toBe(-1);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("prerelease tag is stripped (treated as base release)", () => {
|
|
144
|
+
expect(compareSemver("0.7.1-staging.1", "0.7.1")).toBe(0);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("non-version string returns null", () => {
|
|
148
|
+
expect(compareSemver("not-a-version", "0.7.1")).toBe(null);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("two-part version returns null", () => {
|
|
152
|
+
expect(compareSemver("1.2", "0.7.1")).toBe(null);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Regression: Number.parseInt accepts numeric prefixes and ignores
|
|
156
|
+
// trailing junk ("0foo" → 0), which previously coerced malformed
|
|
157
|
+
// triples through the gate. parseSemverTriple now requires each
|
|
158
|
+
// component to match /^\d+$/ exactly.
|
|
159
|
+
test("trailing junk on a component returns null (left arg)", () => {
|
|
160
|
+
expect(compareSemver("0.8.0foo", "0.8.0")).toBe(null);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("trailing junk on a component returns null (right arg)", () => {
|
|
164
|
+
expect(compareSemver("0.8.0", "0.7.1xyz")).toBe(null);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Leading zeros are accepted: "01.02.03" parses to the same numeric
|
|
168
|
+
// triple as "1.2.3" since Number("01") === 1. We pin this behavior
|
|
169
|
+
// so a future tightening doesn't accidentally regress callers that
|
|
170
|
+
// pass zero-padded versions from upstream tooling.
|
|
171
|
+
test("leading zeros parse equal to un-padded triple", () => {
|
|
172
|
+
expect(compareSemver("01.02.03", "1.2.3")).toBe(0);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("leading whitespace returns null", () => {
|
|
176
|
+
expect(compareSemver(" 0.8.0", "0.8.0")).toBe(null);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("negative component returns null", () => {
|
|
180
|
+
expect(compareSemver("0.8.-1", "0.8.0")).toBe(null);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe("evaluateRuntimeCompatibility", () => {
|
|
185
|
+
test("legacy sentinel passes regardless of runtime version", () => {
|
|
186
|
+
expect(
|
|
187
|
+
evaluateRuntimeCompatibility(
|
|
188
|
+
{ min_runtime_version: "0.0.0-legacy", max_runtime_version: null },
|
|
189
|
+
"0.7.1",
|
|
190
|
+
),
|
|
191
|
+
).toEqual({ ok: true });
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("runtime above min with no max passes", () => {
|
|
195
|
+
expect(
|
|
196
|
+
evaluateRuntimeCompatibility(
|
|
197
|
+
{ min_runtime_version: "0.7.0", max_runtime_version: null },
|
|
198
|
+
"0.7.1",
|
|
199
|
+
),
|
|
200
|
+
).toEqual({ ok: true });
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("runtime below min fails with full echo", () => {
|
|
204
|
+
const compat = {
|
|
205
|
+
min_runtime_version: "0.8.0",
|
|
206
|
+
max_runtime_version: null,
|
|
207
|
+
};
|
|
208
|
+
expect(evaluateRuntimeCompatibility(compat, "0.7.1")).toEqual({
|
|
209
|
+
ok: false,
|
|
210
|
+
reason: "version_incompatible",
|
|
211
|
+
bundle_compat: compat,
|
|
212
|
+
runtime_version: "0.7.1",
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("runtime above max fails", () => {
|
|
217
|
+
const compat = {
|
|
218
|
+
min_runtime_version: "0.7.0",
|
|
219
|
+
max_runtime_version: "0.7.5",
|
|
220
|
+
};
|
|
221
|
+
expect(evaluateRuntimeCompatibility(compat, "0.8.0")).toEqual({
|
|
222
|
+
ok: false,
|
|
223
|
+
reason: "version_incompatible",
|
|
224
|
+
bundle_compat: compat,
|
|
225
|
+
runtime_version: "0.8.0",
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("max is inclusive", () => {
|
|
230
|
+
expect(
|
|
231
|
+
evaluateRuntimeCompatibility(
|
|
232
|
+
{ min_runtime_version: "0.7.0", max_runtime_version: "0.7.5" },
|
|
233
|
+
"0.7.5",
|
|
234
|
+
),
|
|
235
|
+
).toEqual({ ok: true });
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("unparsable min skips the gate (fail-open)", () => {
|
|
239
|
+
expect(
|
|
240
|
+
evaluateRuntimeCompatibility(
|
|
241
|
+
{ min_runtime_version: "garbage", max_runtime_version: null },
|
|
242
|
+
"0.7.1",
|
|
243
|
+
),
|
|
244
|
+
).toEqual({ ok: true });
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Regression for Codex feedback: a malformed min like "0.8.0foo"
|
|
248
|
+
// previously coerced to [0, 8, 0] via Number.parseInt and incorrectly
|
|
249
|
+
// produced a version_incompatible decision against runtime "0.7.1".
|
|
250
|
+
// With strict per-component digit matching, the parse fails and the
|
|
251
|
+
// gate fails open.
|
|
252
|
+
test("malformed min with trailing junk fails open, does not block", () => {
|
|
253
|
+
expect(
|
|
254
|
+
evaluateRuntimeCompatibility(
|
|
255
|
+
{ min_runtime_version: "0.8.0foo", max_runtime_version: null },
|
|
256
|
+
"0.7.1",
|
|
257
|
+
),
|
|
258
|
+
).toEqual({ ok: true });
|
|
259
|
+
});
|
|
260
|
+
});
|