@vellumai/assistant 0.7.0 → 0.7.1
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 +6 -7
- package/Dockerfile +1 -0
- package/README.md +2 -2
- package/__tests__/permissions/gateway-threshold-reader.test.ts +79 -139
- package/bun.lock +3 -0
- package/docs/architecture/security.md +18 -16
- package/knip.json +1 -0
- package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +1 -5
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -5
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -16
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +1 -9
- package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +12 -12
- package/node_modules/@vellumai/slack-text/bun.lock +24 -0
- package/node_modules/@vellumai/slack-text/package.json +18 -0
- package/node_modules/@vellumai/slack-text/src/index.test.ts +153 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +235 -0
- package/node_modules/@vellumai/slack-text/tsconfig.json +20 -0
- package/openapi.yaml +294 -107
- package/package.json +4 -2
- package/scripts/generate-openapi.ts +16 -111
- package/src/__tests__/agent-wake-override-profile.test.ts +23 -1
- package/src/__tests__/anthropic-provider.test.ts +56 -13
- package/src/__tests__/app-conversation-ids-backfill.test.ts +278 -0
- package/src/__tests__/app-conversation-ids.test.ts +151 -0
- package/src/__tests__/approval-cascade.test.ts +0 -15
- package/src/__tests__/approval-routes-http.test.ts +6 -17
- package/src/__tests__/assistant-event-hub.test.ts +126 -77
- package/src/__tests__/assistant-event.test.ts +0 -5
- package/src/__tests__/assistant-events-sse-hardening.test.ts +37 -15
- package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -29
- package/src/__tests__/background-shell-host-bash.test.ts +34 -43
- package/src/__tests__/call-controller.test.ts +1 -1
- package/src/__tests__/call-site-routing-provider.test.ts +193 -0
- package/src/__tests__/channel-approval-routes.test.ts +10 -296
- package/src/__tests__/channel-approvals.test.ts +25 -17
- package/src/__tests__/channel-guardian.test.ts +100 -146
- package/src/__tests__/checker.test.ts +20 -34
- package/src/__tests__/compact-event-conversation-id-guard.test.ts +50 -0
- package/src/__tests__/compaction-events.test.ts +2 -0
- package/src/__tests__/config-schema.test.ts +6 -48
- package/src/__tests__/config-watcher.test.ts +12 -0
- package/src/__tests__/connection-policy.test.ts +1 -52
- package/src/__tests__/contacts-write.test.ts +2 -64
- package/src/__tests__/context-image-dimensions.test.ts +1 -1
- package/src/__tests__/context-search-memory-source.test.ts +120 -1
- package/src/__tests__/context-search-memory-v2-source.test.ts +383 -0
- package/src/__tests__/context-search-pkb-source.test.ts +49 -0
- package/src/__tests__/context-search-workspace-source.test.ts +9 -22
- package/src/__tests__/context-window-manager.test.ts +46 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +102 -29
- package/src/__tests__/conversation-agent-loop.test.ts +980 -13
- package/src/__tests__/conversation-analysis-routes.test.ts +12 -10
- package/src/__tests__/conversation-attention-telegram.test.ts +11 -3
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -291
- package/src/__tests__/conversation-history-web-search.test.ts +4 -3
- package/src/__tests__/conversation-inference-profile-route.test.ts +12 -23
- package/src/__tests__/conversation-lifecycle.test.ts +4 -4
- package/src/__tests__/conversation-process-callsite.test.ts +79 -2
- package/src/__tests__/conversation-queue.test.ts +3 -8
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -161
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -32
- package/src/__tests__/conversation-routes-slash-commands.test.ts +75 -66
- package/src/__tests__/conversation-runtime-assembly.test.ts +257 -3
- package/src/__tests__/conversation-slash-commands.test.ts +24 -4
- package/src/__tests__/conversation-slash-queue.test.ts +2 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-starter-routes.test.ts +79 -2
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -5
- package/src/__tests__/conversation-surfaces-standalone.test.ts +18 -14
- package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -2
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +8 -46
- package/src/__tests__/conversation-usage.test.ts +253 -3
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +0 -39
- package/src/__tests__/credential-health-service.test.ts +68 -0
- package/src/__tests__/credential-security-e2e.test.ts +4 -3
- package/src/__tests__/credential-security-invariants.test.ts +1 -5
- package/src/__tests__/credential-token-resolver.test.ts +180 -0
- package/src/__tests__/cu-unified-flow.test.ts +33 -16
- package/src/__tests__/daemon-assistant-events.test.ts +34 -21
- package/src/__tests__/daemon-credential-client.test.ts +4 -1
- package/src/__tests__/db-connection-isolation.test.ts +125 -0
- package/src/__tests__/db-migration-rollback.test.ts +101 -0
- package/src/__tests__/db-slack-compaction-watermark-migration.test.ts +169 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +7 -80
- package/src/__tests__/document-conversations.test.ts +332 -0
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +2 -2
- package/src/__tests__/emit-event-signal.test.ts +4 -6
- package/src/__tests__/events-client-registration.test.ts +193 -49
- package/src/__tests__/filing-service.test.ts +58 -7
- package/src/__tests__/first-greeting.test.ts +156 -150
- package/src/__tests__/fixtures/mock-chrome-extension.ts +108 -66
- package/src/__tests__/get-skill-detail-audit.test.ts +3 -8
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -1
- package/src/__tests__/guardian-grant-minting.test.ts +7 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +7 -2
- package/src/__tests__/guardian-routing-state.test.ts +1 -1
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +32 -11
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +2 -83
- package/src/__tests__/headless-browser-mode.test.ts +4 -9
- package/src/__tests__/headless-browser-navigate.test.ts +21 -20
- package/src/__tests__/heartbeat-service.test.ts +289 -7
- package/src/__tests__/helpers/channel-test-adapter.ts +2 -2
- package/src/__tests__/helpers/create-guardian-binding.ts +91 -0
- package/src/__tests__/host-bash-proxy.test.ts +46 -122
- package/src/__tests__/host-browser-e2e-cloud.test.ts +36 -497
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +26 -96
- package/src/__tests__/host-browser-proxy.test.ts +111 -185
- package/src/__tests__/host-browser-routes.test.ts +45 -75
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +26 -30
- package/src/__tests__/host-cu-proxy.test.ts +56 -111
- package/src/__tests__/host-file-proxy.test.ts +44 -98
- package/src/__tests__/host-file-read-tool.test.ts +42 -21
- package/src/__tests__/host-shell-tool.test.ts +33 -68
- package/src/__tests__/host-transfer-pending-interactions.test.ts +2 -18
- package/src/__tests__/host-transfer-proxy.test.ts +43 -53
- package/src/__tests__/http-user-message-parity.test.ts +0 -6
- package/src/__tests__/inbound-slack-persistence.test.ts +31 -0
- package/src/__tests__/injector-chain.test.ts +10 -5
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +124 -0
- package/src/__tests__/inline-command-runner.test.ts +0 -66
- package/src/__tests__/inline-skill-load-permissions.test.ts +0 -2
- package/src/__tests__/install-skill-routing.test.ts +1 -13
- package/src/__tests__/llm-callsite-catalog.test.ts +34 -0
- package/src/__tests__/llm-catalog-parity.test.ts +90 -0
- package/src/__tests__/llm-context-resolution.test.ts +180 -0
- package/src/__tests__/llm-resolver.test.ts +80 -12
- package/src/__tests__/llm-usage-store.test.ts +269 -4
- package/src/__tests__/log-export-routes.test.ts +89 -0
- package/src/__tests__/managed-profile-guard.test.ts +225 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +0 -10
- package/src/__tests__/manual-token-reconciliation.test.ts +334 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +95 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +197 -291
- package/src/__tests__/migration-export-http.test.ts +33 -26
- package/src/__tests__/migration-export-streaming.test.ts +18 -10
- package/src/__tests__/migration-export-to-gcs.test.ts +49 -9
- package/src/__tests__/migration-import-commit-http.test.ts +66 -21
- package/src/__tests__/migration-import-from-gcs.test.ts +50 -9
- package/src/__tests__/migration-import-from-url.test.ts +20 -6
- package/src/__tests__/migration-import-preflight-http.test.ts +95 -95
- package/src/__tests__/migration-parity-persistence.test.ts +62 -25
- package/src/__tests__/migration-transport.test.ts +115 -23
- package/src/__tests__/migration-validate-http.test.ts +105 -80
- package/src/__tests__/migration-wizard.test.ts +133 -27
- package/src/__tests__/non-member-access-request.test.ts +1 -1
- package/src/__tests__/notification-guardian-path.test.ts +1 -1
- package/src/__tests__/oauth-store.test.ts +19 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +21 -12
- package/src/__tests__/prechat-onboarding-contract.test.ts +31 -7
- package/src/__tests__/pricing.test.ts +68 -4
- package/src/__tests__/process-message-background-slack.test.ts +331 -0
- package/src/__tests__/provider-managed-proxy-integration.test.ts +153 -17
- package/src/__tests__/provider-send-message-override-profile.test.ts +50 -0
- package/src/__tests__/provider-usage-tracking.test.ts +208 -0
- package/src/__tests__/reaction-persistence.test.ts +9 -6
- package/src/__tests__/rebind-secrets-screen.test.ts +53 -16
- package/src/__tests__/recording-handler.test.ts +64 -81
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +4 -3
- package/src/__tests__/relay-server.test.ts +18 -13
- package/src/__tests__/require-fresh-approval.test.ts +13 -22
- package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +3 -4
- package/src/__tests__/runtime-events-sse.test.ts +3 -12
- package/src/__tests__/search-skills-unified.test.ts +9 -15
- package/src/__tests__/secret-ingress-cli.test.ts +2 -5
- package/src/__tests__/secret-ingress-http.test.ts +0 -4
- package/src/__tests__/secret-onetime-send.test.ts +4 -2
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +24 -7
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +42 -47
- package/src/__tests__/secret-response-routing.test.ts +29 -15
- package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -1
- package/src/__tests__/secret-scanner.test.ts +2 -545
- package/src/__tests__/send-endpoint-busy.test.ts +9 -24
- package/src/__tests__/settings-routes.test.ts +1 -1
- package/src/__tests__/shell-credential-ref.test.ts +0 -8
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -56
- package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -11
- package/src/__tests__/skill-tool-factory.test.ts +97 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +9 -30
- package/src/__tests__/skills-files-catalog-fallback.test.ts +11 -17
- package/src/__tests__/slack-inbound-verification.test.ts +1 -62
- package/src/__tests__/subagent-fork-notifications.test.ts +57 -47
- package/src/__tests__/subagent-manager-notify.test.ts +70 -70
- package/src/__tests__/subagent-notify-parent.test.ts +80 -83
- package/src/__tests__/system-prompt.test.ts +115 -13
- package/src/__tests__/terminal-tools.test.ts +0 -89
- package/src/__tests__/thread-backfill.test.ts +945 -31
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -36
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -6
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -16
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +9 -19
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -7
- package/src/__tests__/tool-executor.test.ts +12 -19
- package/src/__tests__/tool-metrics-listener.test.ts +0 -35
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
- package/src/__tests__/tool-trace-listener.test.ts +0 -17
- package/src/__tests__/transfer-progress-screen.test.ts +63 -26
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -149
- package/src/__tests__/trusted-contact-multichannel.test.ts +2 -4
- package/src/__tests__/trusted-contact-verification.test.ts +1 -1
- package/src/__tests__/tts-catalog-parity.test.ts +16 -5
- package/src/__tests__/usage-attribution.test.ts +247 -0
- package/src/__tests__/usage-cli.test.ts +143 -0
- package/src/__tests__/usage-grouped-buckets.test.ts +155 -0
- package/src/__tests__/usage-routes.test.ts +150 -0
- package/src/__tests__/validation-results-screen.test.ts +39 -16
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +12 -3
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +49 -137
- package/src/__tests__/verification-control-plane-policy.test.ts +4 -7
- package/src/__tests__/voice-session-bridge.test.ts +5 -5
- package/src/__tests__/workspace-migration-062-drop-memory-v2-edges-json.test.ts +103 -0
- package/src/__tests__/workspace-migration-063-release-notes-dynamic-model-context.test.ts +77 -0
- package/src/__tests__/workspace-migration-064-unwind-main-agent-opus-seed.test.ts +225 -0
- package/src/__tests__/workspace-migration-memory-v2-init.test.ts +8 -30
- package/src/acp/index.ts +0 -15
- package/src/acp/session-manager.ts +37 -34
- package/src/agent/loop.ts +16 -1
- package/src/approvals/AGENTS.md +4 -0
- package/src/approvals/__tests__/guardian-feed-event.test.ts +10 -3
- package/src/approvals/guardian-request-resolvers.ts +10 -2
- package/src/backup/__tests__/backup-worker.test.ts +36 -8
- package/src/backup/__tests__/paths.test.ts +2 -2
- package/src/backup/__tests__/restore.test.ts +45 -28
- package/src/backup/backup-worker.ts +36 -2
- package/src/backup/paths.ts +9 -6
- package/src/browser-session/events.ts +0 -9
- package/src/calls/call-store.ts +1 -34
- package/src/calls/guardian-question-copy.ts +0 -108
- package/src/calls/relay-server.ts +0 -24
- package/src/calls/twilio-rest.ts +0 -38
- package/src/calls/twilio-routes.ts +1 -1
- package/src/calls/voice-session-bridge.ts +7 -38
- package/src/channels/types.ts +1 -36
- package/src/cli/commands/__tests__/cache.test.ts +152 -5
- package/src/cli/commands/__tests__/memory-v2.test.ts +14 -28
- package/src/cli/commands/__tests__/trust.test.ts +21 -387
- package/src/cli/commands/backup.ts +4 -4
- package/src/cli/commands/cache-fs.ts +8 -0
- package/src/cli/commands/cache.ts +153 -82
- package/src/cli/commands/clients.ts +63 -5
- package/src/cli/commands/completions.ts +3 -3
- package/src/cli/commands/contacts.ts +231 -76
- package/src/cli/commands/keys.ts +4 -1
- package/src/cli/commands/memory-v2.ts +24 -52
- package/src/cli/commands/oauth/shared.ts +2 -29
- package/src/cli/commands/pending.ts +102 -0
- package/src/cli/commands/skills.ts +77 -35
- package/src/cli/commands/trust.ts +70 -430
- package/src/cli/commands/usage.ts +25 -16
- package/src/cli/lib/daemon-credential-client.ts +14 -0
- package/src/cli/program.ts +2 -0
- package/src/cli.ts +0 -21
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/bundled-skills/messaging/TOOLS.json +14 -4
- package/src/config/env-registry.ts +12 -2
- package/src/config/env.ts +3 -14
- package/src/config/feature-flag-registry.json +30 -30
- package/src/config/llm-callsite-catalog.ts +12 -0
- package/src/config/llm-context-resolution.ts +80 -0
- package/src/config/llm-resolver.ts +58 -22
- package/src/config/loader.ts +3 -3
- package/src/config/schema.ts +2 -158
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +271 -0
- package/src/config/schemas/calls.ts +5 -5
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/ingress.ts +1 -1
- package/src/config/schemas/llm.ts +31 -3
- package/src/config/schemas/memory-retrieval.ts +2 -2
- package/src/config/schemas/memory-v2.ts +9 -0
- package/src/config/schemas/security.ts +1 -42
- package/src/config/schemas/services.ts +6 -6
- package/src/config/schemas/skills.ts +5 -5
- package/src/config/schemas/tts.ts +1 -1
- package/src/config/seed-inference-profiles.ts +117 -0
- package/src/config/skills.ts +0 -90
- package/src/config/types.ts +3 -6
- package/src/contacts/contact-store.ts +0 -17
- package/src/contacts/contacts-write.ts +1 -105
- package/src/context/window-manager.ts +44 -5
- package/src/credential-execution/process-manager.ts +34 -10
- package/src/credential-health/credential-health-service.ts +21 -16
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +75 -82
- package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -9
- package/src/daemon/connection-policy.ts +1 -26
- package/src/daemon/conversation-agent-loop-handlers.ts +53 -4
- package/src/daemon/conversation-agent-loop.ts +277 -36
- package/src/daemon/conversation-history.ts +8 -8
- package/src/daemon/conversation-launch.ts +20 -135
- package/src/daemon/conversation-lifecycle.ts +1 -1
- package/src/daemon/conversation-messaging.ts +1 -0
- package/src/daemon/conversation-process.ts +83 -163
- package/src/daemon/conversation-runtime-assembly.ts +219 -76
- package/src/daemon/conversation-slash.ts +47 -5
- package/src/daemon/conversation-store.ts +7 -31
- package/src/daemon/conversation-surfaces.ts +22 -28
- package/src/daemon/conversation-tool-setup.ts +3 -33
- package/src/daemon/conversation-usage.ts +36 -0
- package/src/daemon/conversation.ts +117 -233
- package/src/daemon/daemon-control.ts +3 -71
- package/src/daemon/daemon-skill-host.ts +8 -11
- package/src/daemon/dictation-profile-store.ts +2 -26
- package/src/daemon/first-greeting.ts +44 -156
- package/src/daemon/handlers/config-channels.ts +12 -12
- package/src/daemon/handlers/config-ingress.ts +4 -165
- package/src/daemon/handlers/config-model.ts +1 -1
- package/src/daemon/handlers/config-voice.ts +0 -42
- package/src/daemon/handlers/conversations.ts +11 -190
- package/src/daemon/handlers/recording.ts +26 -158
- package/src/daemon/handlers/shared.ts +23 -71
- package/src/daemon/handlers/skills.ts +42 -93
- package/src/daemon/host-bash-proxy.ts +67 -45
- package/src/daemon/host-browser-proxy.ts +65 -27
- package/src/daemon/host-cu-proxy.ts +40 -39
- package/src/daemon/host-file-proxy.ts +58 -37
- package/src/daemon/host-transfer-proxy.ts +84 -46
- package/src/daemon/lifecycle.ts +49 -15
- package/src/daemon/message-types/conversations.ts +7 -0
- package/src/daemon/message-types/host-bash.ts +1 -0
- package/src/daemon/message-types/host-cu.ts +1 -0
- package/src/daemon/message-types/host-file.ts +1 -0
- package/src/daemon/message-types/host-transfer.ts +1 -0
- package/src/daemon/message-types/messages.ts +10 -9
- package/src/daemon/message-types/workspace.ts +1 -1
- package/src/daemon/process-message.ts +102 -239
- package/src/daemon/server.ts +13 -462
- package/src/daemon/shutdown-handlers.ts +2 -2
- package/src/daemon/tool-side-effects.ts +125 -107
- package/src/daemon/trust-context.ts +13 -0
- package/src/daemon/wake-target-adapter.ts +4 -9
- package/src/events/domain-events.ts +0 -8
- package/src/events/tool-audit-listener.ts +3 -1
- package/src/events/tool-domain-event-publisher.ts +0 -10
- package/src/events/tool-metrics-listener.ts +0 -17
- package/src/events/tool-trace-listener.ts +0 -14
- package/src/filing/filing-service.ts +13 -1
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +6 -2
- package/src/heartbeat/heartbeat-service.ts +23 -5
- package/src/home/__tests__/feed-writer.test.ts +0 -4
- package/src/home/__tests__/relationship-state-writer.test.ts +30 -0
- package/src/home/feed-writer.ts +1 -2
- package/src/home/relationship-state-writer.ts +16 -3
- package/src/ipc/__tests__/browser-ipc.test.ts +2 -12
- package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -1
- package/src/ipc/assistant-server.ts +3 -10
- package/src/ipc/routes/__tests__/memory-v2-backfill.test.ts +39 -20
- package/src/ipc/routes/route-adapter.ts +1 -1
- package/src/ipc/routes/trust-rules.test.ts +0 -95
- package/src/ipc/skill-ipc-types.ts +41 -0
- package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +13 -27
- package/src/ipc/skill-routes/__tests__/identity.test.ts +4 -23
- package/src/ipc/skill-routes/events.ts +12 -23
- package/src/ipc/skill-routes/identity.ts +4 -17
- package/src/ipc/skill-routes/index.ts +1 -1
- package/src/ipc/skill-server.ts +6 -39
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +0 -8
- package/src/live-voice/protocol.ts +4 -13
- package/src/mcp/manager.ts +0 -5
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +55 -0
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +127 -0
- package/src/memory/app-git-service.ts +0 -32
- package/src/memory/app-store.ts +154 -0
- package/src/memory/attachments-store.ts +6 -0
- package/src/memory/context-search/sources/memory-v2.ts +578 -0
- package/src/memory/context-search/sources/memory.ts +5 -0
- package/src/memory/context-search/sources/pkb.ts +10 -1
- package/src/memory/context-search/sources/workspace.ts +3 -2
- package/src/memory/conversation-crud.ts +29 -4
- package/src/memory/conversation-disk-view.ts +1 -5
- package/src/memory/conversation-starter-checkpoints.ts +63 -0
- package/src/memory/db-connection.ts +62 -0
- package/src/memory/db-init.ts +14 -0
- package/src/memory/embedding-backend.ts +3 -21
- package/src/memory/embedding-gemini.ts +0 -2
- package/src/memory/embedding-local.ts +6 -6
- package/src/memory/embedding-ollama.ts +6 -6
- package/src/memory/embedding-openai.ts +6 -6
- package/src/memory/embedding-types.ts +21 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +3 -7
- package/src/memory/graph/conversation-graph-memory.ts +35 -13
- package/src/memory/graph/injection.test.ts +2 -2
- package/src/memory/graph/injection.ts +1 -1
- package/src/memory/guardian-action-store.ts +0 -83
- package/src/memory/guardian-approvals.ts +0 -48
- package/src/memory/indexer.ts +1 -15
- package/src/memory/job-handlers/conversation-starters.ts +36 -53
- package/src/memory/job-utils.ts +0 -6
- package/src/memory/jobs-store.ts +0 -1
- package/src/memory/jobs-worker.ts +2 -16
- package/src/memory/llm-request-log-store.ts +0 -41
- package/src/memory/llm-usage-store.ts +129 -43
- package/src/memory/memory-v2-activation-log-store.ts +115 -0
- package/src/memory/migrations/233-document-conversations.ts +54 -0
- package/src/memory/migrations/234-memory-v2-activation-logs.ts +55 -0
- package/src/memory/migrations/235-llm-usage-attribution.ts +31 -0
- package/src/memory/migrations/235-slack-compaction-watermark.ts +44 -0
- package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +26 -0
- package/src/memory/migrations/__tests__/234-memory-v2-activation-logs.test.ts +182 -0
- package/src/memory/migrations/index.ts +14 -0
- package/src/memory/migrations/registry.ts +24 -0
- package/src/memory/raw-query.ts +2 -68
- package/src/memory/schema/conversations.ts +7 -0
- package/src/memory/schema/infrastructure.ts +25 -0
- package/src/memory/search/semantic.ts +5 -16
- package/src/memory/tool-usage-store.ts +2 -0
- package/src/memory/usage-buckets.ts +40 -1
- package/src/memory/usage-grouped-buckets.ts +127 -0
- package/src/memory/v2/__tests__/activation.test.ts +289 -90
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +2 -129
- package/src/memory/v2/__tests__/consolidation-job.test.ts +28 -11
- package/src/memory/v2/__tests__/edge-index.test.ts +278 -0
- package/src/memory/v2/__tests__/injection.test.ts +384 -15
- package/src/memory/v2/__tests__/migration.test.ts +64 -36
- package/src/memory/v2/__tests__/page-store.test.ts +191 -8
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +181 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +115 -3
- package/src/memory/v2/__tests__/static-context.test.ts +153 -0
- package/src/memory/v2/activation.ts +168 -97
- package/src/memory/v2/backfill-jobs.ts +15 -100
- package/src/memory/v2/consolidation-job.ts +14 -12
- package/src/memory/v2/edge-index.ts +191 -0
- package/src/memory/v2/injection.ts +182 -58
- package/src/memory/v2/migration.ts +57 -64
- package/src/memory/v2/now-text.ts +2 -3
- package/src/memory/v2/page-store.ts +168 -31
- package/src/memory/v2/prompts/consolidation.ts +118 -42
- package/src/memory/v2/prompts/sweep.ts +3 -3
- package/src/memory/v2/skill-store.ts +55 -7
- package/src/memory/v2/static-context.ts +62 -0
- package/src/memory/v2/types.ts +10 -20
- package/src/memory/validation.ts +0 -11
- package/src/messaging/draft-store.ts +0 -6
- package/src/messaging/provider-types.ts +8 -0
- package/src/messaging/provider.ts +7 -0
- package/src/messaging/providers/gmail/client.ts +1 -121
- package/src/messaging/providers/outlook/client.ts +0 -73
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +226 -0
- package/src/messaging/providers/slack/adapter.ts +122 -21
- package/src/messaging/providers/slack/backfill.test.ts +95 -6
- package/src/messaging/providers/slack/backfill.ts +89 -11
- package/src/messaging/providers/slack/client.ts +10 -124
- package/src/messaging/providers/slack/message-metadata.ts +12 -2
- package/src/messaging/providers/slack/render-transcript.test.ts +56 -0
- package/src/messaging/providers/slack/render-transcript.ts +126 -25
- package/src/messaging/providers/slack/types.ts +1 -0
- package/src/oauth/connection-resolver.test.ts +8 -0
- package/src/oauth/connection-resolver.ts +8 -16
- package/src/oauth/credential-token-resolver.ts +97 -0
- package/src/oauth/manual-token-connection.ts +30 -34
- package/src/oauth/oauth-store.ts +6 -4
- package/src/outbound-proxy/certs.ts +0 -7
- package/src/outbound-proxy/config.ts +0 -74
- package/src/outbound-proxy/health.ts +0 -44
- package/src/outbound-proxy/index.ts +0 -22
- package/src/permissions/approval-provenance.test.ts +184 -0
- package/src/permissions/approval-provenance.ts +70 -0
- package/src/permissions/checker.ts +4 -1
- package/src/permissions/gateway-threshold-reader.ts +4 -1
- package/src/permissions/prompter.ts +9 -2
- package/src/permissions/secret-prompter.ts +21 -48
- package/src/permissions/types.ts +33 -0
- package/src/permissions/workspace-policy.ts +0 -5
- package/src/platform/sync-identity.ts +0 -8
- package/src/plugins/defaults/injectors.ts +69 -2
- package/src/plugins/defaults/overflow-reduce.ts +3 -2
- package/src/plugins/types.ts +8 -0
- package/src/prompts/system-prompt.ts +34 -70
- package/src/prompts/templates/BOOTSTRAP.md +52 -6
- package/src/prompts/update-bulletin-job.ts +2 -0
- package/src/providers/__tests__/retry-callsite.test.ts +138 -1
- package/src/providers/anthropic/client.ts +72 -33
- package/src/providers/call-site-routing.ts +42 -3
- package/src/providers/gemini/client.ts +18 -2
- package/src/providers/managed-proxy/context.ts +0 -5
- package/src/providers/model-catalog.ts +105 -19
- package/src/providers/openai/chat-completions-provider.ts +6 -0
- package/src/providers/openai/responses-provider.ts +7 -1
- package/src/providers/provider-send-message.ts +45 -2
- package/src/providers/ratelimit.ts +7 -2
- package/src/providers/registry.ts +14 -9
- package/src/providers/retry.ts +96 -8
- package/src/providers/types.ts +13 -0
- package/src/providers/usage-tracking.ts +96 -0
- package/src/runtime/AGENTS.md +10 -6
- package/src/runtime/__tests__/agent-wake.test.ts +89 -0
- package/src/runtime/agent-wake.ts +39 -2
- package/src/runtime/assistant-event-hub.ts +541 -45
- package/src/runtime/assistant-event.ts +1 -6
- package/src/runtime/auth/context.ts +0 -9
- package/src/runtime/auth/middleware.ts +1 -1
- package/src/runtime/auth/route-policy.ts +11 -9
- package/src/runtime/auth/token-service.ts +0 -11
- package/src/runtime/channel-approvals.ts +6 -2
- package/src/runtime/channel-verification-service.ts +3 -5
- package/src/runtime/http-errors.ts +0 -34
- package/src/runtime/http-router.ts +6 -3
- package/src/runtime/http-server.ts +22 -82
- package/src/runtime/http-types.ts +5 -0
- package/src/runtime/interactive-ui.ts +0 -1
- package/src/runtime/middleware/auth.ts +0 -20
- package/src/runtime/migrations/__tests__/v1-test-helpers.ts +112 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +11 -4
- package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +253 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +19 -6
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +71 -27
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +41 -2
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +143 -79
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +143 -23
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +371 -0
- package/src/runtime/migrations/migration-transport.ts +46 -13
- package/src/runtime/migrations/migration-wizard.ts +2 -2
- package/src/runtime/migrations/origin-mode.ts +40 -0
- package/src/runtime/migrations/vbundle-builder.ts +133 -79
- package/src/runtime/migrations/vbundle-import-analyzer.ts +9 -7
- package/src/runtime/migrations/vbundle-importer.ts +7 -7
- package/src/runtime/migrations/vbundle-metadata-merge.ts +1 -1
- package/src/runtime/migrations/vbundle-streaming-importer.ts +3 -3
- package/src/runtime/migrations/vbundle-streaming-validator.ts +48 -26
- package/src/runtime/migrations/vbundle-validator.ts +214 -41
- package/src/runtime/pending-interactions.ts +13 -4
- package/src/runtime/routes/__tests__/acp-routes.test.ts +0 -1
- package/src/runtime/routes/__tests__/backup-routes.test.ts +28 -19
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +235 -0
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +58 -0
- package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +54 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +19 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +7 -7
- package/src/runtime/routes/acp-routes.test.ts +0 -3
- package/src/runtime/routes/acp-routes.ts +3 -7
- package/src/runtime/routes/app-management-routes.ts +18 -9
- package/src/runtime/routes/approval-routes.ts +55 -14
- package/src/runtime/routes/avatar-routes.ts +3 -5
- package/src/runtime/routes/browser-routes.ts +1 -15
- package/src/runtime/routes/channel-guardian-routes.ts +1 -5
- package/src/runtime/routes/channel-readiness-routes.ts +3 -7
- package/src/runtime/routes/channel-route-shared.ts +2 -28
- package/src/runtime/routes/client-routes.ts +45 -12
- package/src/runtime/routes/consolidation-routes.ts +115 -0
- package/src/runtime/routes/conversation-list-routes.ts +12 -29
- package/src/runtime/routes/conversation-management-routes.ts +14 -51
- package/src/runtime/routes/conversation-query-routes.ts +120 -8
- package/src/runtime/routes/conversation-routes.ts +44 -528
- package/src/runtime/routes/conversation-starter-routes.ts +19 -40
- package/src/runtime/routes/documents-routes.ts +53 -18
- package/src/runtime/routes/events-routes.ts +59 -91
- package/src/runtime/routes/filing-routes.ts +18 -1
- package/src/runtime/routes/guardian-action-routes.ts +4 -9
- package/src/runtime/routes/host-bash-routes.ts +3 -2
- package/src/runtime/routes/host-browser-routes.ts +9 -33
- package/src/runtime/routes/host-cu-routes.ts +6 -1
- package/src/runtime/routes/host-file-routes.ts +3 -2
- package/src/runtime/routes/host-transfer-routes.ts +11 -15
- package/src/runtime/routes/identity-routes.ts +78 -6
- package/src/runtime/routes/inbound-message-handler.ts +580 -137
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -88
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +3 -0
- package/src/runtime/routes/index.ts +4 -0
- package/src/runtime/routes/integrations/slack/channel.ts +0 -24
- package/src/runtime/routes/llm-call-sites-routes.ts +22 -0
- package/src/runtime/routes/memory-v2-routes.ts +10 -15
- package/src/runtime/routes/migration-routes.ts +188 -31
- package/src/runtime/routes/playground/guard.ts +1 -1
- package/src/runtime/routes/playground/index.ts +0 -2
- package/src/runtime/routes/recording-routes.ts +4 -24
- package/src/runtime/routes/rename-conversation-routes.ts +2 -6
- package/src/runtime/routes/schedule-routes.ts +3 -6
- package/src/runtime/routes/secret-routes.ts +87 -18
- package/src/runtime/routes/settings-routes.ts +29 -28
- package/src/runtime/routes/skills-routes.ts +12 -31
- package/src/runtime/routes/suggest-trust-rule-routes.ts +32 -1
- package/src/runtime/routes/task-routes.ts +6 -6
- package/src/runtime/routes/trust-rules-routes.ts +3 -94
- package/src/runtime/routes/types.ts +4 -4
- package/src/runtime/routes/upgrade-broadcast-routes.ts +3 -10
- package/src/runtime/routes/usage-routes.ts +87 -10
- package/src/runtime/routes/user-routes.ts +17 -31
- package/src/runtime/routes/work-items-routes.ts +1 -4
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -2
- package/src/runtime/services/analyze-conversation.ts +7 -17
- package/src/runtime/services/conversation-serializer.ts +2 -4
- package/src/runtime/verification-outbound-actions.ts +1 -1
- package/src/runtime/verification-rate-limiter.ts +1 -1
- package/src/schedule/schedule-store.ts +0 -16
- package/src/security/secret-scanner.ts +14 -547
- package/src/security/secure-keys.ts +31 -11
- package/src/security/token-manager.ts +7 -3
- package/src/signals/cancel.ts +16 -25
- package/src/signals/conversation-undo.ts +2 -27
- package/src/signals/emit-event.ts +1 -2
- package/src/signals/user-message.ts +108 -22
- package/src/skills/catalog-install.ts +1 -0
- package/src/skills/clawhub.ts +2 -2
- package/src/skills/inline-command-runner.ts +1 -7
- package/src/subagent/manager.ts +67 -84
- package/src/tasks/task-store.ts +1 -28
- package/src/telemetry/types.ts +6 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +38 -15
- package/src/telemetry/usage-telemetry-reporter.ts +3 -5
- package/src/tools/acp/spawn.test.ts +1 -2
- package/src/tools/acp/steer.test.ts +1 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +44 -127
- package/src/tools/browser/browser-execution.ts +31 -147
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +92 -68
- package/src/tools/browser/cdp-client/factory.ts +48 -76
- package/src/tools/browser/cdp-client/index.ts +1 -14
- package/src/tools/executor.ts +44 -31
- package/src/tools/host-filesystem/edit.ts +3 -2
- package/src/tools/host-filesystem/read.ts +3 -2
- package/src/tools/host-filesystem/transfer.test.ts +45 -42
- package/src/tools/host-filesystem/transfer.ts +4 -3
- package/src/tools/host-filesystem/write.ts +3 -2
- package/src/tools/host-terminal/host-shell.ts +4 -3
- package/src/tools/network/script-proxy/index.ts +1 -10
- package/src/tools/permission-checker.ts +66 -1
- package/src/tools/skills/sandbox-runner.ts +1 -6
- package/src/tools/skills/skill-tool-factory.ts +32 -0
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/terminal/shell.ts +2 -78
- package/src/tools/types.ts +12 -39
- package/src/tts/__tests__/provider-catalog.test.ts +2 -2
- package/src/tts/provider-catalog.ts +1 -1
- package/src/usage/actors.ts +2 -1
- package/src/usage/attribution.ts +185 -0
- package/src/usage/pricing.ts +166 -0
- package/src/usage/types.ts +14 -0
- package/src/util/json.ts +13 -0
- package/src/util/logger.ts +3 -3
- package/src/util/pricing.ts +50 -3
- package/src/work-items/work-item-runner.ts +15 -42
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +4 -3
- package/src/workspace/migrations/052-seed-default-inference-profiles.ts +3 -3
- package/src/workspace/migrations/060-memory-v2-init.ts +2 -18
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +59 -0
- package/src/workspace/migrations/062-drop-memory-v2-edges-json.ts +27 -0
- package/src/workspace/migrations/063-release-notes-dynamic-model-context.ts +70 -0
- package/src/workspace/migrations/064-unwind-main-agent-opus-seed.ts +64 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/provider-commit-message-generator.ts +3 -3
- package/src/__tests__/sandbox-diagnostics.test.ts +0 -138
- package/src/__tests__/sandbox-host-parity.test.ts +0 -1024
- package/src/__tests__/secret-detection-handler.test.ts +0 -67
- package/src/__tests__/secret-scanner-executor.test.ts +0 -450
- package/src/__tests__/tcc-sandbox-deny.test.ts +0 -198
- package/src/__tests__/terminal-sandbox.test.ts +0 -374
- package/src/__tests__/tool-notification-listener.test.ts +0 -65
- package/src/context/__tests__/microcompact.test.ts +0 -805
- package/src/context/microcompact.ts +0 -443
- package/src/daemon/handlers/slack-channel-oauth-install.ts +0 -197
- package/src/events/tool-notification-listener.ts +0 -17
- package/src/ipc/routes/__tests__/memory-v2-validate.test.ts +0 -219
- package/src/memory/v2/__tests__/edges.test.ts +0 -435
- package/src/memory/v2/edges.ts +0 -217
- package/src/prompts/__tests__/system-prompt-memory-v2.test.ts +0 -197
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +0 -518
- package/src/runtime/__tests__/client-registry.test.ts +0 -271
- package/src/runtime/chrome-extension-registry.ts +0 -368
- package/src/runtime/client-registry.ts +0 -254
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +0 -329
- package/src/tools/secret-detection-handler.ts +0 -269
- package/src/tools/terminal/backends/native.ts +0 -327
- package/src/tools/terminal/backends/types.ts +0 -37
- package/src/tools/terminal/sandbox-diagnostics.ts +0 -87
- package/src/tools/terminal/sandbox.ts +0 -40
|
@@ -1,1024 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration tests for host/sandbox parity.
|
|
3
|
-
*
|
|
4
|
-
* Both the sandbox (file_read, file_write, file_edit, bash) and host
|
|
5
|
-
* (host_file_read, host_file_write, host_file_edit, host_bash) tool
|
|
6
|
-
* families delegate to the same underlying engine:
|
|
7
|
-
*
|
|
8
|
-
* FileSystemOps — shared read/write/edit logic
|
|
9
|
-
* applyEdit — pure match/replace engine
|
|
10
|
-
* shell.ts — spawn + output formatting
|
|
11
|
-
*
|
|
12
|
-
* The only intentional difference is the PathPolicy:
|
|
13
|
-
* - sandboxPolicy: paths must stay within a boundary directory
|
|
14
|
-
* - hostPolicy: paths must be absolute (no boundary)
|
|
15
|
-
*
|
|
16
|
-
* These tests verify that for equivalent in-scope operations, both
|
|
17
|
-
* code-paths produce identical results — same content, same error
|
|
18
|
-
* codes, same diff shapes — and that the expected divergence on
|
|
19
|
-
* out-of-scope operations is correct.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
existsSync,
|
|
24
|
-
mkdirSync,
|
|
25
|
-
mkdtempSync,
|
|
26
|
-
readFileSync,
|
|
27
|
-
realpathSync,
|
|
28
|
-
rmSync,
|
|
29
|
-
writeFileSync,
|
|
30
|
-
} from "node:fs";
|
|
31
|
-
import { tmpdir } from "node:os";
|
|
32
|
-
import { join } from "node:path";
|
|
33
|
-
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
34
|
-
|
|
35
|
-
// Mock the logger before any transitive imports that depend on pino
|
|
36
|
-
mock.module("../util/logger.js", () => ({
|
|
37
|
-
getLogger: () => ({
|
|
38
|
-
error: () => {},
|
|
39
|
-
warn: () => {},
|
|
40
|
-
info: () => {},
|
|
41
|
-
debug: () => {},
|
|
42
|
-
}),
|
|
43
|
-
}));
|
|
44
|
-
|
|
45
|
-
import { applyEdit } from "../tools/shared/filesystem/edit-engine.js";
|
|
46
|
-
import {
|
|
47
|
-
FileSystemOps,
|
|
48
|
-
type PathPolicy,
|
|
49
|
-
} from "../tools/shared/filesystem/file-ops-service.js";
|
|
50
|
-
import {
|
|
51
|
-
hostPolicy,
|
|
52
|
-
sandboxPolicy,
|
|
53
|
-
} from "../tools/shared/filesystem/path-policy.js";
|
|
54
|
-
import {
|
|
55
|
-
formatShellOutput,
|
|
56
|
-
MAX_OUTPUT_LENGTH,
|
|
57
|
-
} from "../tools/shared/shell-output.js";
|
|
58
|
-
|
|
59
|
-
// Dynamically import modules that depend on the mocked logger
|
|
60
|
-
const { NativeBackend } = await import("../tools/terminal/backends/native.js");
|
|
61
|
-
const { wrapCommand } = await import("../tools/terminal/sandbox.js");
|
|
62
|
-
const { ToolError } = await import("../util/errors.js");
|
|
63
|
-
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
// Helpers
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
|
|
68
|
-
const testDirs: string[] = [];
|
|
69
|
-
|
|
70
|
-
function makeTempDir(): string {
|
|
71
|
-
const dir = realpathSync(mkdtempSync(join(tmpdir(), "parity-test-")));
|
|
72
|
-
testDirs.push(dir);
|
|
73
|
-
return dir;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
afterEach(() => {
|
|
77
|
-
for (const dir of testDirs.splice(0)) {
|
|
78
|
-
rmSync(dir, { recursive: true, force: true });
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
/** Build a sandbox-bound PathPolicy that resolves relative to boundary. */
|
|
83
|
-
function sandboxPolicyFor(boundary: string): PathPolicy {
|
|
84
|
-
return (rawPath, options) => sandboxPolicy(rawPath, boundary, options);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Build a host PathPolicy (just requires absolute paths). */
|
|
88
|
-
function hostPolicyFn(): PathPolicy {
|
|
89
|
-
return hostPolicy;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Run the same operation against both sandbox and host FileSystemOps
|
|
94
|
-
* and return both results for comparison.
|
|
95
|
-
*/
|
|
96
|
-
function dualOps(boundary: string): {
|
|
97
|
-
sandbox: FileSystemOps;
|
|
98
|
-
host: FileSystemOps;
|
|
99
|
-
} {
|
|
100
|
-
return {
|
|
101
|
-
sandbox: new FileSystemOps(sandboxPolicyFor(boundary)),
|
|
102
|
-
host: new FileSystemOps(hostPolicyFn()),
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// ===========================================================================
|
|
107
|
-
// 1. File read parity
|
|
108
|
-
// ===========================================================================
|
|
109
|
-
|
|
110
|
-
describe("Read parity: sandbox vs host produce identical content", () => {
|
|
111
|
-
test("simple file read returns same numbered content", () => {
|
|
112
|
-
const dir = makeTempDir();
|
|
113
|
-
const content = "line one\nline two\nline three\n";
|
|
114
|
-
writeFileSync(join(dir, "data.txt"), content);
|
|
115
|
-
|
|
116
|
-
const { sandbox, host } = dualOps(dir);
|
|
117
|
-
|
|
118
|
-
const sandboxResult = sandbox.readFileSafe({ path: "data.txt" });
|
|
119
|
-
const hostResult = host.readFileSafe({ path: join(dir, "data.txt") });
|
|
120
|
-
|
|
121
|
-
expect(sandboxResult.ok).toBe(true);
|
|
122
|
-
expect(hostResult.ok).toBe(true);
|
|
123
|
-
if (!sandboxResult.ok || !hostResult.ok) return;
|
|
124
|
-
|
|
125
|
-
// Both should produce identical line-numbered output
|
|
126
|
-
expect(sandboxResult.value.content).toBe(hostResult.value.content);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("read with offset and limit returns same slice", () => {
|
|
130
|
-
const dir = makeTempDir();
|
|
131
|
-
writeFileSync(join(dir, "lines.txt"), "a\nb\nc\nd\ne\nf\n");
|
|
132
|
-
|
|
133
|
-
const { sandbox, host } = dualOps(dir);
|
|
134
|
-
|
|
135
|
-
const sandboxResult = sandbox.readFileSafe({
|
|
136
|
-
path: "lines.txt",
|
|
137
|
-
offset: 2,
|
|
138
|
-
limit: 3,
|
|
139
|
-
});
|
|
140
|
-
const hostResult = host.readFileSafe({
|
|
141
|
-
path: join(dir, "lines.txt"),
|
|
142
|
-
offset: 2,
|
|
143
|
-
limit: 3,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
expect(sandboxResult.ok).toBe(true);
|
|
147
|
-
expect(hostResult.ok).toBe(true);
|
|
148
|
-
if (!sandboxResult.ok || !hostResult.ok) return;
|
|
149
|
-
|
|
150
|
-
expect(sandboxResult.value.content).toBe(hostResult.value.content);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("reading a missing file returns NOT_FOUND from both", () => {
|
|
154
|
-
const dir = makeTempDir();
|
|
155
|
-
|
|
156
|
-
const { sandbox, host } = dualOps(dir);
|
|
157
|
-
|
|
158
|
-
const sandboxResult = sandbox.readFileSafe({ path: "nonexistent.txt" });
|
|
159
|
-
const hostResult = host.readFileSafe({
|
|
160
|
-
path: join(dir, "nonexistent.txt"),
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
expect(sandboxResult.ok).toBe(false);
|
|
164
|
-
expect(hostResult.ok).toBe(false);
|
|
165
|
-
if (sandboxResult.ok || hostResult.ok) return;
|
|
166
|
-
|
|
167
|
-
expect(sandboxResult.error.code).toBe("NOT_FOUND");
|
|
168
|
-
expect(hostResult.error.code).toBe("NOT_FOUND");
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test("reading a directory returns NOT_A_FILE from both", () => {
|
|
172
|
-
const dir = makeTempDir();
|
|
173
|
-
mkdirSync(join(dir, "subdir"));
|
|
174
|
-
|
|
175
|
-
const { sandbox, host } = dualOps(dir);
|
|
176
|
-
|
|
177
|
-
const sandboxResult = sandbox.readFileSafe({ path: "subdir" });
|
|
178
|
-
const hostResult = host.readFileSafe({ path: join(dir, "subdir") });
|
|
179
|
-
|
|
180
|
-
expect(sandboxResult.ok).toBe(false);
|
|
181
|
-
expect(hostResult.ok).toBe(false);
|
|
182
|
-
if (sandboxResult.ok || hostResult.ok) return;
|
|
183
|
-
|
|
184
|
-
expect(sandboxResult.error.code).toBe("NOT_A_FILE");
|
|
185
|
-
expect(hostResult.error.code).toBe("NOT_A_FILE");
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test("empty file read returns same content from both", () => {
|
|
189
|
-
const dir = makeTempDir();
|
|
190
|
-
writeFileSync(join(dir, "empty.txt"), "");
|
|
191
|
-
|
|
192
|
-
const { sandbox, host } = dualOps(dir);
|
|
193
|
-
|
|
194
|
-
const sandboxResult = sandbox.readFileSafe({ path: "empty.txt" });
|
|
195
|
-
const hostResult = host.readFileSafe({ path: join(dir, "empty.txt") });
|
|
196
|
-
|
|
197
|
-
expect(sandboxResult.ok).toBe(true);
|
|
198
|
-
expect(hostResult.ok).toBe(true);
|
|
199
|
-
if (!sandboxResult.ok || !hostResult.ok) return;
|
|
200
|
-
|
|
201
|
-
expect(sandboxResult.value.content).toBe(hostResult.value.content);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test("file with unicode content returns same from both", () => {
|
|
205
|
-
const dir = makeTempDir();
|
|
206
|
-
const unicode =
|
|
207
|
-
"Hello\nEmoji: \u{1F600}\nCJK: \u4F60\u597D\nAccent: caf\u00E9\n";
|
|
208
|
-
writeFileSync(join(dir, "unicode.txt"), unicode);
|
|
209
|
-
|
|
210
|
-
const { sandbox, host } = dualOps(dir);
|
|
211
|
-
|
|
212
|
-
const sandboxResult = sandbox.readFileSafe({ path: "unicode.txt" });
|
|
213
|
-
const hostResult = host.readFileSafe({ path: join(dir, "unicode.txt") });
|
|
214
|
-
|
|
215
|
-
expect(sandboxResult.ok).toBe(true);
|
|
216
|
-
expect(hostResult.ok).toBe(true);
|
|
217
|
-
if (!sandboxResult.ok || !hostResult.ok) return;
|
|
218
|
-
|
|
219
|
-
expect(sandboxResult.value.content).toBe(hostResult.value.content);
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// ===========================================================================
|
|
224
|
-
// 2. File write parity
|
|
225
|
-
// ===========================================================================
|
|
226
|
-
|
|
227
|
-
describe("Write parity: sandbox vs host produce identical results", () => {
|
|
228
|
-
test("writing a new file returns same shape from both", () => {
|
|
229
|
-
const dir = makeTempDir();
|
|
230
|
-
|
|
231
|
-
const { sandbox, host } = dualOps(dir);
|
|
232
|
-
|
|
233
|
-
const sandboxResult = sandbox.writeFileSafe({
|
|
234
|
-
path: "new-s.txt",
|
|
235
|
-
content: "hello",
|
|
236
|
-
});
|
|
237
|
-
const hostResult = host.writeFileSafe({
|
|
238
|
-
path: join(dir, "new-h.txt"),
|
|
239
|
-
content: "hello",
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
expect(sandboxResult.ok).toBe(true);
|
|
243
|
-
expect(hostResult.ok).toBe(true);
|
|
244
|
-
if (!sandboxResult.ok || !hostResult.ok) return;
|
|
245
|
-
|
|
246
|
-
expect(sandboxResult.value.isNewFile).toBe(true);
|
|
247
|
-
expect(hostResult.value.isNewFile).toBe(true);
|
|
248
|
-
expect(sandboxResult.value.newContent).toBe(hostResult.value.newContent);
|
|
249
|
-
expect(sandboxResult.value.oldContent).toBe(hostResult.value.oldContent);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test("overwriting an existing file returns old content from both", () => {
|
|
253
|
-
const dir = makeTempDir();
|
|
254
|
-
writeFileSync(join(dir, "existing-s.txt"), "old");
|
|
255
|
-
writeFileSync(join(dir, "existing-h.txt"), "old");
|
|
256
|
-
|
|
257
|
-
const { sandbox, host } = dualOps(dir);
|
|
258
|
-
|
|
259
|
-
const sandboxResult = sandbox.writeFileSafe({
|
|
260
|
-
path: "existing-s.txt",
|
|
261
|
-
content: "new",
|
|
262
|
-
});
|
|
263
|
-
const hostResult = host.writeFileSafe({
|
|
264
|
-
path: join(dir, "existing-h.txt"),
|
|
265
|
-
content: "new",
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
expect(sandboxResult.ok).toBe(true);
|
|
269
|
-
expect(hostResult.ok).toBe(true);
|
|
270
|
-
if (!sandboxResult.ok || !hostResult.ok) return;
|
|
271
|
-
|
|
272
|
-
expect(sandboxResult.value.isNewFile).toBe(false);
|
|
273
|
-
expect(hostResult.value.isNewFile).toBe(false);
|
|
274
|
-
expect(sandboxResult.value.oldContent).toBe("old");
|
|
275
|
-
expect(hostResult.value.oldContent).toBe("old");
|
|
276
|
-
expect(sandboxResult.value.newContent).toBe("new");
|
|
277
|
-
expect(hostResult.value.newContent).toBe("new");
|
|
278
|
-
|
|
279
|
-
// Verify actual files on disk
|
|
280
|
-
expect(readFileSync(join(dir, "existing-s.txt"), "utf-8")).toBe("new");
|
|
281
|
-
expect(readFileSync(join(dir, "existing-h.txt"), "utf-8")).toBe("new");
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
test("creating nested directories works from both", () => {
|
|
285
|
-
const dir = makeTempDir();
|
|
286
|
-
|
|
287
|
-
const { sandbox, host } = dualOps(dir);
|
|
288
|
-
|
|
289
|
-
const sandboxResult = sandbox.writeFileSafe({
|
|
290
|
-
path: "a/b/deep-s.txt",
|
|
291
|
-
content: "deep",
|
|
292
|
-
});
|
|
293
|
-
const hostResult = host.writeFileSafe({
|
|
294
|
-
path: join(dir, "c/d/deep-h.txt"),
|
|
295
|
-
content: "deep",
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
expect(sandboxResult.ok).toBe(true);
|
|
299
|
-
expect(hostResult.ok).toBe(true);
|
|
300
|
-
if (!sandboxResult.ok || !hostResult.ok) return;
|
|
301
|
-
|
|
302
|
-
expect(existsSync(join(dir, "a/b/deep-s.txt"))).toBe(true);
|
|
303
|
-
expect(existsSync(join(dir, "c/d/deep-h.txt"))).toBe(true);
|
|
304
|
-
expect(sandboxResult.value.isNewFile).toBe(true);
|
|
305
|
-
expect(hostResult.value.isNewFile).toBe(true);
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
// ===========================================================================
|
|
310
|
-
// 3. File edit parity
|
|
311
|
-
// ===========================================================================
|
|
312
|
-
|
|
313
|
-
describe("Edit parity: sandbox vs host produce identical edits", () => {
|
|
314
|
-
test("unique match edit produces same result from both", () => {
|
|
315
|
-
const dir = makeTempDir();
|
|
316
|
-
writeFileSync(join(dir, "edit-s.txt"), "one two three");
|
|
317
|
-
writeFileSync(join(dir, "edit-h.txt"), "one two three");
|
|
318
|
-
|
|
319
|
-
const { sandbox, host } = dualOps(dir);
|
|
320
|
-
|
|
321
|
-
const sandboxResult = sandbox.editFileSafe({
|
|
322
|
-
path: "edit-s.txt",
|
|
323
|
-
oldString: "two",
|
|
324
|
-
newString: "TWO",
|
|
325
|
-
replaceAll: false,
|
|
326
|
-
});
|
|
327
|
-
const hostResult = host.editFileSafe({
|
|
328
|
-
path: join(dir, "edit-h.txt"),
|
|
329
|
-
oldString: "two",
|
|
330
|
-
newString: "TWO",
|
|
331
|
-
replaceAll: false,
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
expect(sandboxResult.ok).toBe(true);
|
|
335
|
-
expect(hostResult.ok).toBe(true);
|
|
336
|
-
if (!sandboxResult.ok || !hostResult.ok) return;
|
|
337
|
-
|
|
338
|
-
expect(sandboxResult.value.matchCount).toBe(1);
|
|
339
|
-
expect(hostResult.value.matchCount).toBe(1);
|
|
340
|
-
expect(sandboxResult.value.newContent).toBe("one TWO three");
|
|
341
|
-
expect(hostResult.value.newContent).toBe("one TWO three");
|
|
342
|
-
expect(sandboxResult.value.matchMethod).toBe(hostResult.value.matchMethod);
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
test("replaceAll edit produces same result from both", () => {
|
|
346
|
-
const dir = makeTempDir();
|
|
347
|
-
const original = "foo bar foo baz foo";
|
|
348
|
-
writeFileSync(join(dir, "ra-s.txt"), original);
|
|
349
|
-
writeFileSync(join(dir, "ra-h.txt"), original);
|
|
350
|
-
|
|
351
|
-
const { sandbox, host } = dualOps(dir);
|
|
352
|
-
|
|
353
|
-
const sandboxResult = sandbox.editFileSafe({
|
|
354
|
-
path: "ra-s.txt",
|
|
355
|
-
oldString: "foo",
|
|
356
|
-
newString: "qux",
|
|
357
|
-
replaceAll: true,
|
|
358
|
-
});
|
|
359
|
-
const hostResult = host.editFileSafe({
|
|
360
|
-
path: join(dir, "ra-h.txt"),
|
|
361
|
-
oldString: "foo",
|
|
362
|
-
newString: "qux",
|
|
363
|
-
replaceAll: true,
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
expect(sandboxResult.ok).toBe(true);
|
|
367
|
-
expect(hostResult.ok).toBe(true);
|
|
368
|
-
if (!sandboxResult.ok || !hostResult.ok) return;
|
|
369
|
-
|
|
370
|
-
expect(sandboxResult.value.matchCount).toBe(3);
|
|
371
|
-
expect(hostResult.value.matchCount).toBe(3);
|
|
372
|
-
expect(sandboxResult.value.newContent).toBe(hostResult.value.newContent);
|
|
373
|
-
expect(sandboxResult.value.newContent).toBe("qux bar qux baz qux");
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
test("missing old_string returns MATCH_NOT_FOUND from both", () => {
|
|
377
|
-
const dir = makeTempDir();
|
|
378
|
-
writeFileSync(join(dir, "mnf-s.txt"), "hello world");
|
|
379
|
-
writeFileSync(join(dir, "mnf-h.txt"), "hello world");
|
|
380
|
-
|
|
381
|
-
const { sandbox, host } = dualOps(dir);
|
|
382
|
-
|
|
383
|
-
const sandboxResult = sandbox.editFileSafe({
|
|
384
|
-
path: "mnf-s.txt",
|
|
385
|
-
oldString: "xyz",
|
|
386
|
-
newString: "abc",
|
|
387
|
-
replaceAll: false,
|
|
388
|
-
});
|
|
389
|
-
const hostResult = host.editFileSafe({
|
|
390
|
-
path: join(dir, "mnf-h.txt"),
|
|
391
|
-
oldString: "xyz",
|
|
392
|
-
newString: "abc",
|
|
393
|
-
replaceAll: false,
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
expect(sandboxResult.ok).toBe(false);
|
|
397
|
-
expect(hostResult.ok).toBe(false);
|
|
398
|
-
if (sandboxResult.ok || hostResult.ok) return;
|
|
399
|
-
|
|
400
|
-
expect(sandboxResult.error.code).toBe("MATCH_NOT_FOUND");
|
|
401
|
-
expect(hostResult.error.code).toBe("MATCH_NOT_FOUND");
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
test("ambiguous match returns MATCH_AMBIGUOUS from both", () => {
|
|
405
|
-
const dir = makeTempDir();
|
|
406
|
-
const content = "repeat\nrepeat\n";
|
|
407
|
-
writeFileSync(join(dir, "amb-s.txt"), content);
|
|
408
|
-
writeFileSync(join(dir, "amb-h.txt"), content);
|
|
409
|
-
|
|
410
|
-
const { sandbox, host } = dualOps(dir);
|
|
411
|
-
|
|
412
|
-
const sandboxResult = sandbox.editFileSafe({
|
|
413
|
-
path: "amb-s.txt",
|
|
414
|
-
oldString: "repeat",
|
|
415
|
-
newString: "unique",
|
|
416
|
-
replaceAll: false,
|
|
417
|
-
});
|
|
418
|
-
const hostResult = host.editFileSafe({
|
|
419
|
-
path: join(dir, "amb-h.txt"),
|
|
420
|
-
oldString: "repeat",
|
|
421
|
-
newString: "unique",
|
|
422
|
-
replaceAll: false,
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
expect(sandboxResult.ok).toBe(false);
|
|
426
|
-
expect(hostResult.ok).toBe(false);
|
|
427
|
-
if (sandboxResult.ok || hostResult.ok) return;
|
|
428
|
-
|
|
429
|
-
expect(sandboxResult.error.code).toBe("MATCH_AMBIGUOUS");
|
|
430
|
-
expect(hostResult.error.code).toBe("MATCH_AMBIGUOUS");
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
test("editing a nonexistent file returns NOT_FOUND from both", () => {
|
|
434
|
-
const dir = makeTempDir();
|
|
435
|
-
|
|
436
|
-
const { sandbox, host } = dualOps(dir);
|
|
437
|
-
|
|
438
|
-
const sandboxResult = sandbox.editFileSafe({
|
|
439
|
-
path: "nope.txt",
|
|
440
|
-
oldString: "a",
|
|
441
|
-
newString: "b",
|
|
442
|
-
replaceAll: false,
|
|
443
|
-
});
|
|
444
|
-
const hostResult = host.editFileSafe({
|
|
445
|
-
path: join(dir, "nope.txt"),
|
|
446
|
-
oldString: "a",
|
|
447
|
-
newString: "b",
|
|
448
|
-
replaceAll: false,
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
expect(sandboxResult.ok).toBe(false);
|
|
452
|
-
expect(hostResult.ok).toBe(false);
|
|
453
|
-
if (sandboxResult.ok || hostResult.ok) return;
|
|
454
|
-
|
|
455
|
-
expect(sandboxResult.error.code).toBe("NOT_FOUND");
|
|
456
|
-
expect(hostResult.error.code).toBe("NOT_FOUND");
|
|
457
|
-
});
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
// ===========================================================================
|
|
461
|
-
// 4. Edit engine consistency (pure function)
|
|
462
|
-
// ===========================================================================
|
|
463
|
-
|
|
464
|
-
describe("applyEdit engine: deterministic across invocations", () => {
|
|
465
|
-
test("exact single match is idempotent", () => {
|
|
466
|
-
const content = "alpha beta gamma";
|
|
467
|
-
const r1 = applyEdit(content, "beta", "BETA", false);
|
|
468
|
-
const r2 = applyEdit(content, "beta", "BETA", false);
|
|
469
|
-
|
|
470
|
-
expect(r1).toEqual(r2);
|
|
471
|
-
expect(r1.ok).toBe(true);
|
|
472
|
-
if (!r1.ok) return;
|
|
473
|
-
expect(r1.updatedContent).toBe("alpha BETA gamma");
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
test("replaceAll is idempotent", () => {
|
|
477
|
-
const content = "x y x z x";
|
|
478
|
-
const r1 = applyEdit(content, "x", "X", true);
|
|
479
|
-
const r2 = applyEdit(content, "x", "X", true);
|
|
480
|
-
|
|
481
|
-
expect(r1).toEqual(r2);
|
|
482
|
-
expect(r1.ok).toBe(true);
|
|
483
|
-
if (!r1.ok) return;
|
|
484
|
-
expect(r1.matchCount).toBe(3);
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
test("not-found is consistent", () => {
|
|
488
|
-
const content = "hello";
|
|
489
|
-
const r1 = applyEdit(content, "missing", "found", false);
|
|
490
|
-
const r2 = applyEdit(content, "missing", "found", false);
|
|
491
|
-
|
|
492
|
-
expect(r1).toEqual(r2);
|
|
493
|
-
expect(r1.ok).toBe(false);
|
|
494
|
-
if (r1.ok) return;
|
|
495
|
-
expect(r1.reason).toBe("not_found");
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
test("ambiguous is consistent", () => {
|
|
499
|
-
const content = "dup dup dup";
|
|
500
|
-
const r1 = applyEdit(content, "dup", "uniq", false);
|
|
501
|
-
const r2 = applyEdit(content, "dup", "uniq", false);
|
|
502
|
-
|
|
503
|
-
expect(r1).toEqual(r2);
|
|
504
|
-
expect(r1.ok).toBe(false);
|
|
505
|
-
if (r1.ok) return;
|
|
506
|
-
expect(r1.reason).toBe("ambiguous");
|
|
507
|
-
if (r1.reason !== "ambiguous") return;
|
|
508
|
-
expect(r1.matchCount).toBe(3);
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
test("multiline content is handled correctly", () => {
|
|
512
|
-
const content = "line1\nline2\nline3\nline4\n";
|
|
513
|
-
const result = applyEdit(content, "line2\nline3", "replaced", false);
|
|
514
|
-
|
|
515
|
-
expect(result.ok).toBe(true);
|
|
516
|
-
if (!result.ok) return;
|
|
517
|
-
expect(result.updatedContent).toBe("line1\nreplaced\nline4\n");
|
|
518
|
-
expect(result.matchCount).toBe(1);
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
test("replacing with empty string works", () => {
|
|
522
|
-
const content = "keep remove keep";
|
|
523
|
-
const result = applyEdit(content, " remove", "", false);
|
|
524
|
-
|
|
525
|
-
expect(result.ok).toBe(true);
|
|
526
|
-
if (!result.ok) return;
|
|
527
|
-
expect(result.updatedContent).toBe("keep keep");
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
test("special regex characters in old_string are treated as literals", () => {
|
|
531
|
-
const content = "price is $100.00 (USD)";
|
|
532
|
-
const result = applyEdit(content, "$100.00", "$200.00", false);
|
|
533
|
-
|
|
534
|
-
expect(result.ok).toBe(true);
|
|
535
|
-
if (!result.ok) return;
|
|
536
|
-
expect(result.updatedContent).toBe("price is $200.00 (USD)");
|
|
537
|
-
});
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
// ===========================================================================
|
|
541
|
-
// 5. Path policy divergence — expected differences
|
|
542
|
-
// ===========================================================================
|
|
543
|
-
|
|
544
|
-
describe("Path policy divergence: sandbox blocks escapes, host requires absolute", () => {
|
|
545
|
-
test("sandbox blocks path traversal (../), host allows any absolute path", () => {
|
|
546
|
-
const dir = makeTempDir();
|
|
547
|
-
writeFileSync(join(dir, "inside.txt"), "safe content");
|
|
548
|
-
|
|
549
|
-
const sandboxOps = new FileSystemOps(sandboxPolicyFor(dir));
|
|
550
|
-
const hostOps = new FileSystemOps(hostPolicyFn());
|
|
551
|
-
|
|
552
|
-
// Sandbox: path traversal should be rejected
|
|
553
|
-
const sandboxResult = sandboxOps.readFileSafe({
|
|
554
|
-
path: "../../../etc/hostname",
|
|
555
|
-
});
|
|
556
|
-
expect(sandboxResult.ok).toBe(false);
|
|
557
|
-
if (!sandboxResult.ok) {
|
|
558
|
-
expect(sandboxResult.error.code).toBe("PATH_OUT_OF_BOUNDS");
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// Host: relative paths are rejected (requires absolute)
|
|
562
|
-
const hostResult = hostOps.readFileSafe({ path: "relative.txt" });
|
|
563
|
-
expect(hostResult.ok).toBe(false);
|
|
564
|
-
if (!hostResult.ok) {
|
|
565
|
-
expect(hostResult.error.code).toBe("PATH_NOT_ABSOLUTE");
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
test("sandbox allows relative paths within boundary", () => {
|
|
570
|
-
const dir = makeTempDir();
|
|
571
|
-
writeFileSync(join(dir, "valid.txt"), "data");
|
|
572
|
-
|
|
573
|
-
const sandboxOps = new FileSystemOps(sandboxPolicyFor(dir));
|
|
574
|
-
|
|
575
|
-
const result = sandboxOps.readFileSafe({ path: "valid.txt" });
|
|
576
|
-
expect(result.ok).toBe(true);
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
test("host requires absolute path even for simple filenames", () => {
|
|
580
|
-
const hostOps = new FileSystemOps(hostPolicyFn());
|
|
581
|
-
|
|
582
|
-
const result = hostOps.readFileSafe({ path: "just-a-name.txt" });
|
|
583
|
-
expect(result.ok).toBe(false);
|
|
584
|
-
if (!result.ok) {
|
|
585
|
-
expect(result.error.code).toBe("PATH_NOT_ABSOLUTE");
|
|
586
|
-
}
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
test("sandbox rejects absolute paths outside boundary", () => {
|
|
590
|
-
const dir = makeTempDir();
|
|
591
|
-
const sandboxOps = new FileSystemOps(sandboxPolicyFor(dir));
|
|
592
|
-
|
|
593
|
-
const result = sandboxOps.writeFileSafe({
|
|
594
|
-
path: "/tmp/somewhere-else.txt",
|
|
595
|
-
content: "bad",
|
|
596
|
-
});
|
|
597
|
-
expect(result.ok).toBe(false);
|
|
598
|
-
if (!result.ok) {
|
|
599
|
-
expect(result.error.code).toBe("PATH_OUT_OF_BOUNDS");
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
// ===========================================================================
|
|
605
|
-
// 6. Sandbox backend parity — NativeBackend & DockerBackend SandboxResult shape
|
|
606
|
-
// ===========================================================================
|
|
607
|
-
|
|
608
|
-
describe("SandboxResult shape consistency across backends", () => {
|
|
609
|
-
test("NativeBackend.wrap returns required fields", () => {
|
|
610
|
-
const native = new NativeBackend();
|
|
611
|
-
|
|
612
|
-
// On macOS this will succeed; on other platforms it will throw ToolError
|
|
613
|
-
try {
|
|
614
|
-
const result = native.wrap("echo test", "/tmp");
|
|
615
|
-
expect(typeof result.command).toBe("string");
|
|
616
|
-
expect(Array.isArray(result.args)).toBe(true);
|
|
617
|
-
expect(typeof result.sandboxed).toBe("boolean");
|
|
618
|
-
expect(result.sandboxed).toBe(true);
|
|
619
|
-
|
|
620
|
-
// All args must be strings
|
|
621
|
-
for (const arg of result.args) {
|
|
622
|
-
expect(typeof arg).toBe("string");
|
|
623
|
-
}
|
|
624
|
-
} catch (err) {
|
|
625
|
-
// NativeBackend explicitly throws ToolError on unsupported platforms or
|
|
626
|
-
// unsafe paths. Infrastructure calls (writeFileSync, mkdirSync) can
|
|
627
|
-
// throw system errors (ErrnoException). Both are legitimate — but
|
|
628
|
-
// programming errors like TypeError/ReferenceError should still fail.
|
|
629
|
-
const isToolError = err instanceof ToolError;
|
|
630
|
-
const isSystemError =
|
|
631
|
-
err instanceof Error &&
|
|
632
|
-
"syscall" in err &&
|
|
633
|
-
typeof (err as NodeJS.ErrnoException).code === "string";
|
|
634
|
-
expect(isToolError || isSystemError).toBe(true);
|
|
635
|
-
}
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
test("wrapCommand disabled returns bash with sandboxed=false", () => {
|
|
639
|
-
const result = wrapCommand("echo hi", "/tmp", { enabled: false });
|
|
640
|
-
|
|
641
|
-
expect(result.command).toBe("bash");
|
|
642
|
-
expect(result.args).toEqual(["-c", "--", "echo hi"]);
|
|
643
|
-
expect(result.sandboxed).toBe(false);
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
test("wrapCommand disabled result has same shape as enabled result", () => {
|
|
647
|
-
const disabled = wrapCommand("echo hi", "/tmp", { enabled: false });
|
|
648
|
-
|
|
649
|
-
// Both must have: command (string), args (string[]), sandboxed (boolean)
|
|
650
|
-
expect(typeof disabled.command).toBe("string");
|
|
651
|
-
expect(Array.isArray(disabled.args)).toBe(true);
|
|
652
|
-
expect(typeof disabled.sandboxed).toBe("boolean");
|
|
653
|
-
|
|
654
|
-
for (const arg of disabled.args) {
|
|
655
|
-
expect(typeof arg).toBe("string");
|
|
656
|
-
}
|
|
657
|
-
});
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
// ===========================================================================
|
|
661
|
-
// 7. Terminal output format consistency
|
|
662
|
-
// ===========================================================================
|
|
663
|
-
|
|
664
|
-
describe("Terminal output format: formatShellOutput shared by sandbox and host", () => {
|
|
665
|
-
test("successful command output has no XML status tags", () => {
|
|
666
|
-
const result = formatShellOutput("hello world", "", 0, false, 120);
|
|
667
|
-
|
|
668
|
-
expect(result.content).toBe("hello world");
|
|
669
|
-
expect(result.content).not.toContain("<command_exit");
|
|
670
|
-
expect(result.content).not.toContain("<command_completed");
|
|
671
|
-
expect(result.isError).toBe(false);
|
|
672
|
-
expect(result.status).toBeUndefined();
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
test("empty output on success produces <command_completed /> tag", () => {
|
|
676
|
-
const result = formatShellOutput("", "", 0, false, 120);
|
|
677
|
-
|
|
678
|
-
expect(result.content).toBe("<command_completed />");
|
|
679
|
-
expect(result.isError).toBe(false);
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
test("non-zero exit code with empty output produces <command_exit /> tag and descriptive message", () => {
|
|
683
|
-
const result = formatShellOutput("", "", 42, false, 120);
|
|
684
|
-
|
|
685
|
-
expect(result.content).toContain('<command_exit code="42" />');
|
|
686
|
-
expect(result.content).toContain("Command failed with exit code 42");
|
|
687
|
-
expect(result.content).toContain("No stdout or stderr output was produced");
|
|
688
|
-
expect(result.isError).toBe(true);
|
|
689
|
-
expect(result.status).toContain('<command_exit code="42" />');
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
test("stderr is appended to stdout with a newline separator", () => {
|
|
693
|
-
const result = formatShellOutput("out", "err", 0, false, 120);
|
|
694
|
-
|
|
695
|
-
expect(result.content).toBe("out\nerr");
|
|
696
|
-
});
|
|
697
|
-
|
|
698
|
-
test("stderr-only output uses stderr as the output", () => {
|
|
699
|
-
const result = formatShellOutput("", "error message", 0, false, 120);
|
|
700
|
-
|
|
701
|
-
expect(result.content).toBe("error message");
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
test("output truncation uses the shared MAX_OUTPUT_LENGTH constant", () => {
|
|
705
|
-
const longOutput = "x".repeat(MAX_OUTPUT_LENGTH + 100);
|
|
706
|
-
const result = formatShellOutput(longOutput, "", 0, false, 120);
|
|
707
|
-
|
|
708
|
-
expect(result.content).toContain('limit="20K"');
|
|
709
|
-
expect(result.content).toContain('file="');
|
|
710
|
-
// The <output_truncated tag starts right after MAX_OUTPUT_LENGTH chars + 1 newline
|
|
711
|
-
const tagStart = result.content.indexOf("<output_truncated");
|
|
712
|
-
expect(tagStart).toBe(MAX_OUTPUT_LENGTH + 1);
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
test("timed-out command appends timeout tag and sets isError", () => {
|
|
716
|
-
const result = formatShellOutput("partial", "", 137, true, 30);
|
|
717
|
-
|
|
718
|
-
expect(result.content).toContain("partial");
|
|
719
|
-
expect(result.content).toContain('<command_timeout seconds="30" />');
|
|
720
|
-
expect(result.isError).toBe(true);
|
|
721
|
-
expect(result.status).toContain("<command_timeout");
|
|
722
|
-
});
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
// ===========================================================================
|
|
726
|
-
// 8. Regression tests for edge cases found during migration
|
|
727
|
-
// ===========================================================================
|
|
728
|
-
|
|
729
|
-
describe("Regression: edge cases in shared FileSystemOps", () => {
|
|
730
|
-
test("writing empty content creates a file with empty content", () => {
|
|
731
|
-
const dir = makeTempDir();
|
|
732
|
-
|
|
733
|
-
const { sandbox, host } = dualOps(dir);
|
|
734
|
-
|
|
735
|
-
const sandboxResult = sandbox.writeFileSafe({
|
|
736
|
-
path: "empty-s.txt",
|
|
737
|
-
content: "",
|
|
738
|
-
});
|
|
739
|
-
const hostResult = host.writeFileSafe({
|
|
740
|
-
path: join(dir, "empty-h.txt"),
|
|
741
|
-
content: "",
|
|
742
|
-
});
|
|
743
|
-
|
|
744
|
-
expect(sandboxResult.ok).toBe(true);
|
|
745
|
-
expect(hostResult.ok).toBe(true);
|
|
746
|
-
|
|
747
|
-
expect(readFileSync(join(dir, "empty-s.txt"), "utf-8")).toBe("");
|
|
748
|
-
expect(readFileSync(join(dir, "empty-h.txt"), "utf-8")).toBe("");
|
|
749
|
-
});
|
|
750
|
-
|
|
751
|
-
test("editing a file with only whitespace works correctly", () => {
|
|
752
|
-
const dir = makeTempDir();
|
|
753
|
-
writeFileSync(join(dir, "ws-s.txt"), " \n \n ");
|
|
754
|
-
writeFileSync(join(dir, "ws-h.txt"), " \n \n ");
|
|
755
|
-
|
|
756
|
-
const { sandbox, host } = dualOps(dir);
|
|
757
|
-
|
|
758
|
-
const sandboxResult = sandbox.editFileSafe({
|
|
759
|
-
path: "ws-s.txt",
|
|
760
|
-
oldString: " \n \n ",
|
|
761
|
-
newString: "replaced",
|
|
762
|
-
replaceAll: false,
|
|
763
|
-
});
|
|
764
|
-
const hostResult = host.editFileSafe({
|
|
765
|
-
path: join(dir, "ws-h.txt"),
|
|
766
|
-
oldString: " \n \n ",
|
|
767
|
-
newString: "replaced",
|
|
768
|
-
replaceAll: false,
|
|
769
|
-
});
|
|
770
|
-
|
|
771
|
-
expect(sandboxResult.ok).toBe(true);
|
|
772
|
-
expect(hostResult.ok).toBe(true);
|
|
773
|
-
if (!sandboxResult.ok || !hostResult.ok) return;
|
|
774
|
-
|
|
775
|
-
expect(sandboxResult.value.newContent).toBe("replaced");
|
|
776
|
-
expect(hostResult.value.newContent).toBe("replaced");
|
|
777
|
-
});
|
|
778
|
-
|
|
779
|
-
test("write then read roundtrip produces consistent content", () => {
|
|
780
|
-
const dir = makeTempDir();
|
|
781
|
-
const content = "line 1\nline 2\nline 3";
|
|
782
|
-
|
|
783
|
-
const { sandbox, host } = dualOps(dir);
|
|
784
|
-
|
|
785
|
-
// Write via sandbox, read via both
|
|
786
|
-
sandbox.writeFileSafe({ path: "roundtrip.txt", content });
|
|
787
|
-
|
|
788
|
-
const sandboxRead = sandbox.readFileSafe({ path: "roundtrip.txt" });
|
|
789
|
-
const hostRead = host.readFileSafe({ path: join(dir, "roundtrip.txt") });
|
|
790
|
-
|
|
791
|
-
expect(sandboxRead.ok).toBe(true);
|
|
792
|
-
expect(hostRead.ok).toBe(true);
|
|
793
|
-
if (!sandboxRead.ok || !hostRead.ok) return;
|
|
794
|
-
|
|
795
|
-
expect(sandboxRead.value.content).toBe(hostRead.value.content);
|
|
796
|
-
});
|
|
797
|
-
|
|
798
|
-
test("write then edit then read roundtrip is consistent", () => {
|
|
799
|
-
const dir = makeTempDir();
|
|
800
|
-
const initial = "const x = 1;\nconst y = 2;\nconst z = 3;";
|
|
801
|
-
|
|
802
|
-
const { sandbox, host } = dualOps(dir);
|
|
803
|
-
|
|
804
|
-
sandbox.writeFileSafe({ path: "code.ts", content: initial });
|
|
805
|
-
|
|
806
|
-
sandbox.editFileSafe({
|
|
807
|
-
path: "code.ts",
|
|
808
|
-
oldString: "const y = 2;",
|
|
809
|
-
newString: "const y = 42;",
|
|
810
|
-
replaceAll: false,
|
|
811
|
-
});
|
|
812
|
-
|
|
813
|
-
const sandboxRead = sandbox.readFileSafe({ path: "code.ts" });
|
|
814
|
-
const hostRead = host.readFileSafe({ path: join(dir, "code.ts") });
|
|
815
|
-
|
|
816
|
-
expect(sandboxRead.ok).toBe(true);
|
|
817
|
-
expect(hostRead.ok).toBe(true);
|
|
818
|
-
if (!sandboxRead.ok || !hostRead.ok) return;
|
|
819
|
-
|
|
820
|
-
expect(sandboxRead.value.content).toBe(hostRead.value.content);
|
|
821
|
-
expect(sandboxRead.value.content).toContain("const y = 42;");
|
|
822
|
-
expect(sandboxRead.value.content).not.toContain("const y = 2;");
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
test("file with very long lines is handled identically", () => {
|
|
826
|
-
const dir = makeTempDir();
|
|
827
|
-
const longLine = "a".repeat(10_000);
|
|
828
|
-
writeFileSync(join(dir, "long-s.txt"), longLine);
|
|
829
|
-
writeFileSync(join(dir, "long-h.txt"), longLine);
|
|
830
|
-
|
|
831
|
-
const { sandbox, host } = dualOps(dir);
|
|
832
|
-
|
|
833
|
-
const sandboxResult = sandbox.readFileSafe({ path: "long-s.txt" });
|
|
834
|
-
const hostResult = host.readFileSafe({ path: join(dir, "long-h.txt") });
|
|
835
|
-
|
|
836
|
-
expect(sandboxResult.ok).toBe(true);
|
|
837
|
-
expect(hostResult.ok).toBe(true);
|
|
838
|
-
if (!sandboxResult.ok || !hostResult.ok) return;
|
|
839
|
-
|
|
840
|
-
expect(sandboxResult.value.content).toBe(hostResult.value.content);
|
|
841
|
-
});
|
|
842
|
-
|
|
843
|
-
test("concurrent writes to different files in same dir are isolated", () => {
|
|
844
|
-
const dir = makeTempDir();
|
|
845
|
-
|
|
846
|
-
const { sandbox } = dualOps(dir);
|
|
847
|
-
|
|
848
|
-
// Simulate two "concurrent" writes (sequential here, but tests isolation)
|
|
849
|
-
const r1 = sandbox.writeFileSafe({ path: "a.txt", content: "content-a" });
|
|
850
|
-
const r2 = sandbox.writeFileSafe({ path: "b.txt", content: "content-b" });
|
|
851
|
-
|
|
852
|
-
expect(r1.ok).toBe(true);
|
|
853
|
-
expect(r2.ok).toBe(true);
|
|
854
|
-
|
|
855
|
-
expect(readFileSync(join(dir, "a.txt"), "utf-8")).toBe("content-a");
|
|
856
|
-
expect(readFileSync(join(dir, "b.txt"), "utf-8")).toBe("content-b");
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
test("edit preserves file content exactly except for the replacement", () => {
|
|
860
|
-
const dir = makeTempDir();
|
|
861
|
-
// Content with trailing newline, tabs, and special chars
|
|
862
|
-
const content = "\tfirst line\n\tsecond line\n\tthird line\n";
|
|
863
|
-
writeFileSync(join(dir, "precise.txt"), content);
|
|
864
|
-
|
|
865
|
-
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
866
|
-
const result = ops.editFileSafe({
|
|
867
|
-
path: "precise.txt",
|
|
868
|
-
oldString: "\tsecond line",
|
|
869
|
-
newString: "\treplaced line",
|
|
870
|
-
replaceAll: false,
|
|
871
|
-
});
|
|
872
|
-
|
|
873
|
-
expect(result.ok).toBe(true);
|
|
874
|
-
if (!result.ok) return;
|
|
875
|
-
|
|
876
|
-
expect(result.value.newContent).toBe(
|
|
877
|
-
"\tfirst line\n\treplaced line\n\tthird line\n",
|
|
878
|
-
);
|
|
879
|
-
expect(readFileSync(join(dir, "precise.txt"), "utf-8")).toBe(
|
|
880
|
-
"\tfirst line\n\treplaced line\n\tthird line\n",
|
|
881
|
-
);
|
|
882
|
-
});
|
|
883
|
-
|
|
884
|
-
test("read with offset=1 and no limit returns all lines", () => {
|
|
885
|
-
const dir = makeTempDir();
|
|
886
|
-
writeFileSync(join(dir, "full.txt"), "one\ntwo\nthree\n");
|
|
887
|
-
|
|
888
|
-
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
889
|
-
const result = ops.readFileSafe({ path: "full.txt", offset: 1 });
|
|
890
|
-
|
|
891
|
-
expect(result.ok).toBe(true);
|
|
892
|
-
if (!result.ok) return;
|
|
893
|
-
|
|
894
|
-
expect(result.value.content).toContain("one");
|
|
895
|
-
expect(result.value.content).toContain("two");
|
|
896
|
-
expect(result.value.content).toContain("three");
|
|
897
|
-
});
|
|
898
|
-
|
|
899
|
-
test("symlink within sandbox boundary is readable", () => {
|
|
900
|
-
const dir = makeTempDir();
|
|
901
|
-
const targetFile = join(dir, "target.txt");
|
|
902
|
-
writeFileSync(targetFile, "linked content");
|
|
903
|
-
|
|
904
|
-
const linkPath = join(dir, "link.txt");
|
|
905
|
-
try {
|
|
906
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
907
|
-
require("node:fs").symlinkSync(targetFile, linkPath);
|
|
908
|
-
} catch {
|
|
909
|
-
// Symlink creation may fail on some systems — skip gracefully
|
|
910
|
-
return;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
const ops = new FileSystemOps(sandboxPolicyFor(dir));
|
|
914
|
-
const result = ops.readFileSafe({ path: "link.txt" });
|
|
915
|
-
|
|
916
|
-
expect(result.ok).toBe(true);
|
|
917
|
-
if (!result.ok) return;
|
|
918
|
-
expect(result.value.content).toContain("linked content");
|
|
919
|
-
});
|
|
920
|
-
});
|
|
921
|
-
|
|
922
|
-
// ===========================================================================
|
|
923
|
-
// 9. NativeBackend shape verification
|
|
924
|
-
// ===========================================================================
|
|
925
|
-
|
|
926
|
-
describe("NativeBackend: SandboxResult shape", () => {
|
|
927
|
-
test("NativeBackend has a wrap method", () => {
|
|
928
|
-
const native = new NativeBackend();
|
|
929
|
-
expect(typeof native.wrap).toBe("function");
|
|
930
|
-
});
|
|
931
|
-
|
|
932
|
-
test("disabled sandbox returns consistent bash -c -- invocation", () => {
|
|
933
|
-
// Various commands should all be wrapped consistently when disabled
|
|
934
|
-
const commands = [
|
|
935
|
-
"echo hello",
|
|
936
|
-
"ls -la",
|
|
937
|
-
"cat /etc/hosts",
|
|
938
|
-
"true && false",
|
|
939
|
-
];
|
|
940
|
-
for (const cmd of commands) {
|
|
941
|
-
const result = wrapCommand(cmd, "/tmp", { enabled: false });
|
|
942
|
-
expect(result.command).toBe("bash");
|
|
943
|
-
expect(result.args[0]).toBe("-c");
|
|
944
|
-
expect(result.args[1]).toBe("--");
|
|
945
|
-
expect(result.args[2]).toBe(cmd);
|
|
946
|
-
expect(result.sandboxed).toBe(false);
|
|
947
|
-
}
|
|
948
|
-
});
|
|
949
|
-
});
|
|
950
|
-
|
|
951
|
-
// ===========================================================================
|
|
952
|
-
// 10. Error handling consistency
|
|
953
|
-
// ===========================================================================
|
|
954
|
-
|
|
955
|
-
describe("Error handling consistency across code paths", () => {
|
|
956
|
-
test("FsError codes are consistent between sandbox and host for same conditions", () => {
|
|
957
|
-
const dir = makeTempDir();
|
|
958
|
-
|
|
959
|
-
const { sandbox, host } = dualOps(dir);
|
|
960
|
-
|
|
961
|
-
// NOT_FOUND
|
|
962
|
-
const sfNotFound = sandbox.readFileSafe({ path: "missing.txt" });
|
|
963
|
-
const hfNotFound = host.readFileSafe({ path: join(dir, "missing.txt") });
|
|
964
|
-
expect(sfNotFound.ok).toBe(false);
|
|
965
|
-
expect(hfNotFound.ok).toBe(false);
|
|
966
|
-
if (!sfNotFound.ok && !hfNotFound.ok) {
|
|
967
|
-
expect(sfNotFound.error.code).toBe(hfNotFound.error.code);
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
// NOT_A_FILE
|
|
971
|
-
mkdirSync(join(dir, "dirA"));
|
|
972
|
-
const sfNotFile = sandbox.readFileSafe({ path: "dirA" });
|
|
973
|
-
const hfNotFile = host.readFileSafe({ path: join(dir, "dirA") });
|
|
974
|
-
expect(sfNotFile.ok).toBe(false);
|
|
975
|
-
expect(hfNotFile.ok).toBe(false);
|
|
976
|
-
if (!sfNotFile.ok && !hfNotFile.ok) {
|
|
977
|
-
expect(sfNotFile.error.code).toBe(hfNotFile.error.code);
|
|
978
|
-
}
|
|
979
|
-
});
|
|
980
|
-
|
|
981
|
-
test("write error codes match between sandbox and host for same conditions", () => {
|
|
982
|
-
const dir = makeTempDir();
|
|
983
|
-
|
|
984
|
-
const { sandbox, host } = dualOps(dir);
|
|
985
|
-
|
|
986
|
-
// Both should succeed for valid operations
|
|
987
|
-
const sfWrite = sandbox.writeFileSafe({ path: "ok-s.txt", content: "ok" });
|
|
988
|
-
const hfWrite = host.writeFileSafe({
|
|
989
|
-
path: join(dir, "ok-h.txt"),
|
|
990
|
-
content: "ok",
|
|
991
|
-
});
|
|
992
|
-
|
|
993
|
-
expect(sfWrite.ok).toBe(true);
|
|
994
|
-
expect(hfWrite.ok).toBe(true);
|
|
995
|
-
});
|
|
996
|
-
|
|
997
|
-
test("edit MATCH_NOT_FOUND vs MATCH_AMBIGUOUS error codes match between paths", () => {
|
|
998
|
-
const dir = makeTempDir();
|
|
999
|
-
writeFileSync(join(dir, "err-s.txt"), "unique text");
|
|
1000
|
-
writeFileSync(join(dir, "err-h.txt"), "unique text");
|
|
1001
|
-
|
|
1002
|
-
const { sandbox, host } = dualOps(dir);
|
|
1003
|
-
|
|
1004
|
-
// MATCH_NOT_FOUND
|
|
1005
|
-
const sfMnf = sandbox.editFileSafe({
|
|
1006
|
-
path: "err-s.txt",
|
|
1007
|
-
oldString: "nope",
|
|
1008
|
-
newString: "x",
|
|
1009
|
-
replaceAll: false,
|
|
1010
|
-
});
|
|
1011
|
-
const hfMnf = host.editFileSafe({
|
|
1012
|
-
path: join(dir, "err-h.txt"),
|
|
1013
|
-
oldString: "nope",
|
|
1014
|
-
newString: "x",
|
|
1015
|
-
replaceAll: false,
|
|
1016
|
-
});
|
|
1017
|
-
expect(sfMnf.ok).toBe(false);
|
|
1018
|
-
expect(hfMnf.ok).toBe(false);
|
|
1019
|
-
if (!sfMnf.ok && !hfMnf.ok) {
|
|
1020
|
-
expect(sfMnf.error.code).toBe(hfMnf.error.code);
|
|
1021
|
-
expect(sfMnf.error.code).toBe("MATCH_NOT_FOUND");
|
|
1022
|
-
}
|
|
1023
|
-
});
|
|
1024
|
-
});
|