@vellumai/assistant 0.4.17 → 0.4.18
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/eslint.config.mjs +2 -2
- package/package.json +1 -1
- package/src/__tests__/access-request-decision.test.ts +128 -120
- package/src/__tests__/account-registry.test.ts +121 -110
- package/src/__tests__/active-skill-tools.test.ts +200 -172
- package/src/__tests__/actor-token-service.test.ts +341 -274
- package/src/__tests__/agent-loop-thinking.test.ts +28 -19
- package/src/__tests__/agent-loop.test.ts +798 -378
- package/src/__tests__/anthropic-provider.test.ts +405 -247
- package/src/__tests__/app-builder-tool-scripts.test.ts +97 -97
- package/src/__tests__/app-bundler.test.ts +112 -79
- package/src/__tests__/app-executors.test.ts +205 -178
- package/src/__tests__/app-git-history.test.ts +90 -73
- package/src/__tests__/app-git-service.test.ts +67 -53
- package/src/__tests__/app-open-proxy.test.ts +29 -25
- package/src/__tests__/approval-conversation-turn.test.ts +100 -81
- package/src/__tests__/approval-hardcoded-copy-guard.test.ts +45 -17
- package/src/__tests__/approval-message-composer.test.ts +119 -119
- package/src/__tests__/approval-primitive.test.ts +264 -233
- package/src/__tests__/approval-routes-http.test.ts +4 -3
- package/src/__tests__/asset-materialize-tool.test.ts +250 -178
- package/src/__tests__/asset-search-tool.test.ts +251 -191
- package/src/__tests__/assistant-attachment-directive.test.ts +187 -142
- package/src/__tests__/assistant-attachments.test.ts +254 -186
- package/src/__tests__/assistant-event-hub.test.ts +105 -63
- package/src/__tests__/assistant-event.test.ts +66 -58
- package/src/__tests__/assistant-events-sse-hardening.test.ts +113 -73
- package/src/__tests__/assistant-feature-flag-guard.test.ts +78 -52
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +48 -45
- package/src/__tests__/assistant-feature-flags-integration.test.ts +118 -77
- package/src/__tests__/assistant-id-boundary-guard.test.ts +158 -104
- package/src/__tests__/attachments-store.test.ts +240 -183
- package/src/__tests__/attachments.test.ts +70 -62
- package/src/__tests__/audit-log-rotation.test.ts +50 -35
- package/src/__tests__/browser-fill-credential.test.ts +169 -101
- package/src/__tests__/browser-manager.test.ts +97 -75
- package/src/__tests__/browser-runtime-check.test.ts +16 -15
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +12 -10
- package/src/__tests__/browser-skill-endstate.test.ts +97 -72
- package/src/__tests__/bundle-scanner.test.ts +47 -22
- package/src/__tests__/bundled-asset.test.ts +74 -47
- package/src/__tests__/call-constants.test.ts +19 -19
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-conversation-messages.test.ts +90 -65
- package/src/__tests__/call-domain.test.ts +149 -121
- package/src/__tests__/call-pointer-message-composer.test.ts +113 -83
- package/src/__tests__/call-pointer-messages.test.ts +213 -154
- package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +9 -10
- package/src/__tests__/call-recovery.test.ts +232 -212
- package/src/__tests__/call-routes-http.test.ts +0 -1
- package/src/__tests__/call-start-guardian-guard.test.ts +32 -30
- package/src/__tests__/call-state-machine.test.ts +62 -51
- package/src/__tests__/call-state.test.ts +89 -75
- package/src/__tests__/call-store.test.ts +387 -316
- package/src/__tests__/callback-handoff-copy.test.ts +84 -82
- package/src/__tests__/canonical-guardian-store.test.ts +331 -280
- package/src/__tests__/channel-approval-routes.test.ts +1643 -1115
- package/src/__tests__/channel-approval.test.ts +139 -137
- package/src/__tests__/channel-approvals.test.ts +0 -1
- package/src/__tests__/channel-delivery-store.test.ts +232 -194
- package/src/__tests__/channel-guardian.test.ts +5 -3
- package/src/__tests__/channel-invite-transport.test.ts +107 -92
- package/src/__tests__/channel-policy.test.ts +42 -38
- package/src/__tests__/channel-readiness-service.test.ts +119 -102
- package/src/__tests__/channel-reply-delivery.test.ts +147 -118
- package/src/__tests__/channel-retry-sweep.test.ts +153 -110
- package/src/__tests__/checker.test.ts +3309 -1850
- package/src/__tests__/clarification-resolver.test.ts +91 -79
- package/src/__tests__/classifier.test.ts +64 -54
- package/src/__tests__/claude-code-skill-regression.test.ts +42 -37
- package/src/__tests__/claude-code-tool-profiles.test.ts +31 -29
- package/src/__tests__/clawhub.test.ts +92 -82
- package/src/__tests__/cli.test.ts +30 -30
- package/src/__tests__/clipboard.test.ts +53 -46
- package/src/__tests__/commit-guarantee.test.ts +59 -52
- package/src/__tests__/commit-message-enrichment-service.test.ts +203 -75
- package/src/__tests__/compaction.benchmark.test.ts +33 -31
- package/src/__tests__/computer-use-session-compaction.test.ts +60 -50
- package/src/__tests__/computer-use-session-lifecycle.test.ts +145 -117
- package/src/__tests__/computer-use-session-working-dir.test.ts +62 -48
- package/src/__tests__/computer-use-skill-baseline.test.ts +22 -19
- package/src/__tests__/computer-use-skill-endstate.test.ts +45 -31
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +121 -88
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +65 -42
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +33 -18
- package/src/__tests__/computer-use-tools.test.ts +121 -98
- package/src/__tests__/config-schema.test.ts +443 -347
- package/src/__tests__/config-watcher.test.ts +96 -81
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +148 -133
- package/src/__tests__/conflict-intent-tokenization.test.ts +96 -78
- package/src/__tests__/conflict-policy.test.ts +151 -80
- package/src/__tests__/conflict-store.test.ts +203 -157
- package/src/__tests__/connection-policy.test.ts +89 -59
- package/src/__tests__/contacts-tools.test.ts +247 -178
- package/src/__tests__/context-memory-e2e.test.ts +306 -214
- package/src/__tests__/context-token-estimator.test.ts +114 -74
- package/src/__tests__/context-window-manager.test.ts +269 -167
- package/src/__tests__/contradiction-checker.test.ts +161 -135
- package/src/__tests__/conversation-attention-store.test.ts +350 -290
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-pairing.test.ts +220 -113
- package/src/__tests__/conversation-store.test.ts +390 -235
- package/src/__tests__/credential-broker-browser-fill.test.ts +325 -250
- package/src/__tests__/credential-broker-server-use.test.ts +283 -243
- package/src/__tests__/credential-broker.test.ts +128 -74
- package/src/__tests__/credential-host-pattern-match.test.ts +64 -44
- package/src/__tests__/credential-metadata-store.test.ts +360 -311
- package/src/__tests__/credential-policy-validate.test.ts +81 -65
- package/src/__tests__/credential-resolve.test.ts +212 -145
- package/src/__tests__/credential-security-e2e.test.ts +144 -103
- package/src/__tests__/credential-security-invariants.test.ts +253 -208
- package/src/__tests__/credential-selection.test.ts +254 -146
- package/src/__tests__/credential-vault-unit.test.ts +531 -341
- package/src/__tests__/credential-vault.test.ts +761 -484
- package/src/__tests__/daemon-assistant-events.test.ts +91 -66
- package/src/__tests__/daemon-lifecycle.test.ts +258 -190
- package/src/__tests__/daemon-server-session-init.test.ts +0 -1
- package/src/__tests__/date-context.test.ts +314 -249
- package/src/__tests__/db-migration-rollback.test.ts +259 -130
- package/src/__tests__/db-schedule-syntax-migration.test.ts +78 -41
- package/src/__tests__/delete-managed-skill-tool.test.ts +77 -53
- package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
- package/src/__tests__/dictation-mode-detection.test.ts +77 -55
- package/src/__tests__/dictation-profile-store.test.ts +70 -56
- package/src/__tests__/dictation-text-processing.test.ts +53 -35
- package/src/__tests__/diff.test.ts +102 -98
- package/src/__tests__/domain-normalize.test.ts +54 -54
- package/src/__tests__/domain-policy.test.ts +71 -55
- package/src/__tests__/dynamic-page-surface.test.ts +31 -33
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +69 -69
- package/src/__tests__/edit-engine.test.ts +56 -56
- package/src/__tests__/elevenlabs-client.test.ts +117 -91
- package/src/__tests__/elevenlabs-config.test.ts +32 -31
- package/src/__tests__/email-classifier.test.ts +15 -12
- package/src/__tests__/email-cli.test.ts +121 -108
- package/src/__tests__/emit-signal-routing-intent.test.ts +76 -69
- package/src/__tests__/encrypted-store.test.ts +180 -154
- package/src/__tests__/entity-extractor.test.ts +108 -87
- package/src/__tests__/entity-search.test.ts +664 -258
- package/src/__tests__/ephemeral-permissions.test.ts +224 -188
- package/src/__tests__/event-bus.test.ts +81 -77
- package/src/__tests__/extract-email.test.ts +29 -20
- package/src/__tests__/file-edit-tool.test.ts +62 -44
- package/src/__tests__/file-ops-service.test.ts +131 -114
- package/src/__tests__/file-read-tool.test.ts +48 -31
- package/src/__tests__/file-write-tool.test.ts +43 -37
- package/src/__tests__/filesystem-tools.test.ts +238 -209
- package/src/__tests__/followup-tools.test.ts +237 -162
- package/src/__tests__/forbidden-legacy-symbols.test.ts +19 -20
- package/src/__tests__/frontmatter.test.ts +96 -81
- package/src/__tests__/fuzzy-match-property.test.ts +75 -81
- package/src/__tests__/fuzzy-match.test.ts +71 -65
- package/src/__tests__/gateway-client-managed-outbound.test.ts +76 -57
- package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-image-service.test.ts +113 -100
- package/src/__tests__/gemini-provider.test.ts +297 -220
- package/src/__tests__/get-weather.test.ts +188 -114
- package/src/__tests__/gmail-integration.test.ts +0 -1
- package/src/__tests__/guardian-action-conversation-turn.test.ts +226 -171
- package/src/__tests__/guardian-action-copy-generator.test.ts +111 -93
- package/src/__tests__/guardian-action-followup-executor.test.ts +0 -1
- package/src/__tests__/guardian-action-followup-store.test.ts +199 -167
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +297 -250
- package/src/__tests__/guardian-action-late-reply.test.ts +462 -316
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +23 -18
- package/src/__tests__/guardian-action-store.test.ts +158 -109
- package/src/__tests__/guardian-action-sweep.test.ts +114 -100
- package/src/__tests__/guardian-actions-endpoint.test.ts +440 -256
- package/src/__tests__/guardian-control-plane-policy.test.ts +497 -331
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +217 -215
- package/src/__tests__/guardian-dispatch.test.ts +316 -256
- package/src/__tests__/guardian-grant-minting.test.ts +247 -178
- package/src/__tests__/guardian-outbound-http.test.ts +5 -3
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +99 -96
- package/src/__tests__/guardian-question-copy.test.ts +17 -17
- package/src/__tests__/guardian-question-mode.test.ts +134 -100
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/guardian-verification-intent-routing.test.ts +94 -88
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +0 -1
- package/src/__tests__/handle-user-message-secret-resume.test.ts +0 -1
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +92 -76
- package/src/__tests__/handlers-cu-observation-blob.test.ts +103 -70
- package/src/__tests__/handlers-ipc-blob-probe.test.ts +77 -51
- package/src/__tests__/handlers-slack-config.test.ts +63 -54
- package/src/__tests__/handlers-task-submit-slash.test.ts +18 -18
- package/src/__tests__/handlers-telegram-config.test.ts +662 -329
- package/src/__tests__/handlers-twitter-config.test.ts +525 -298
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -2
- package/src/__tests__/headless-browser-interactions.test.ts +444 -280
- package/src/__tests__/headless-browser-navigate.test.ts +116 -79
- package/src/__tests__/headless-browser-read-tools.test.ts +123 -86
- package/src/__tests__/headless-browser-snapshot.test.ts +71 -56
- package/src/__tests__/heartbeat-service.test.ts +76 -58
- package/src/__tests__/history-repair-observability.test.ts +14 -14
- package/src/__tests__/history-repair.test.ts +171 -167
- package/src/__tests__/home-base-bootstrap.test.ts +30 -27
- package/src/__tests__/hooks-blocking.test.ts +86 -37
- package/src/__tests__/hooks-cli.test.ts +104 -68
- package/src/__tests__/hooks-config.test.ts +81 -43
- package/src/__tests__/hooks-discovery.test.ts +106 -96
- package/src/__tests__/hooks-integration.test.ts +78 -72
- package/src/__tests__/hooks-manager.test.ts +99 -61
- package/src/__tests__/hooks-runner.test.ts +94 -71
- package/src/__tests__/hooks-settings.test.ts +69 -64
- package/src/__tests__/hooks-templates.test.ts +85 -54
- package/src/__tests__/hooks-ts-runner.test.ts +82 -45
- package/src/__tests__/hooks-watch.test.ts +32 -22
- package/src/__tests__/host-file-edit-tool.test.ts +190 -148
- package/src/__tests__/host-file-read-tool.test.ts +86 -63
- package/src/__tests__/host-file-write-tool.test.ts +98 -64
- package/src/__tests__/host-shell-tool.test.ts +342 -233
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
- package/src/__tests__/ingress-member-store.test.ts +163 -159
- package/src/__tests__/ingress-reconcile.test.ts +0 -1
- package/src/__tests__/ingress-routes-http.test.ts +441 -356
- package/src/__tests__/ingress-url-consistency.test.ts +125 -64
- package/src/__tests__/integration-status.test.ts +93 -73
- package/src/__tests__/intent-routing.test.ts +148 -118
- package/src/__tests__/invite-redemption-service.test.ts +163 -121
- package/src/__tests__/ipc-blob-store.test.ts +104 -91
- package/src/__tests__/ipc-contract-inventory.test.ts +27 -15
- package/src/__tests__/ipc-contract.test.ts +24 -23
- package/src/__tests__/ipc-protocol.test.ts +52 -46
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +61 -50
- package/src/__tests__/ipc-snapshot.test.ts +1135 -1056
- package/src/__tests__/ipc-validate.test.ts +240 -179
- package/src/__tests__/key-migration.test.ts +123 -90
- package/src/__tests__/keychain.test.ts +150 -123
- package/src/__tests__/lifecycle-docs-guard.test.ts +65 -64
- package/src/__tests__/llm-usage-store.test.ts +112 -87
- package/src/__tests__/managed-skill-lifecycle.test.ts +147 -108
- package/src/__tests__/managed-store.test.ts +411 -360
- package/src/__tests__/mcp-cli.test.ts +189 -123
- package/src/__tests__/mcp-health-check.test.ts +26 -21
- package/src/__tests__/media-generate-image.test.ts +122 -99
- package/src/__tests__/media-reuse-story.e2e.test.ts +282 -214
- package/src/__tests__/media-visibility-policy.test.ts +86 -38
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +146 -100
- package/src/__tests__/memory-lifecycle-e2e.test.ts +385 -297
- package/src/__tests__/memory-query-builder.test.ts +32 -33
- package/src/__tests__/memory-recall-quality.test.ts +761 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +443 -380
- package/src/__tests__/memory-regressions.test.ts +3725 -2642
- package/src/__tests__/memory-retrieval-budget.test.ts +7 -8
- package/src/__tests__/memory-retrieval.benchmark.test.ts +144 -109
- package/src/__tests__/memory-upsert-concurrency.test.ts +292 -201
- package/src/__tests__/messaging-send-tool.test.ts +36 -29
- package/src/__tests__/migration-cli-flows.test.ts +69 -53
- package/src/__tests__/migration-ordering.test.ts +103 -86
- package/src/__tests__/mime-builder.test.ts +55 -32
- package/src/__tests__/mock-signup-server.test.ts +384 -246
- package/src/__tests__/model-intents.test.ts +61 -37
- package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +9 -12
- package/src/__tests__/no-is-trusted-guard.test.ts +24 -21
- package/src/__tests__/non-member-access-request.test.ts +3 -2
- package/src/__tests__/notification-broadcaster.test.ts +99 -81
- package/src/__tests__/notification-decision-fallback.test.ts +223 -178
- package/src/__tests__/notification-decision-strategy.test.ts +375 -337
- package/src/__tests__/notification-deep-link.test.ts +67 -61
- package/src/__tests__/notification-guardian-path.test.ts +248 -206
- package/src/__tests__/notification-routing-intent.test.ts +166 -93
- package/src/__tests__/notification-thread-candidate-validation.test.ts +78 -75
- package/src/__tests__/notification-thread-candidates.test.ts +64 -61
- package/src/__tests__/oauth-callback-registry.test.ts +40 -30
- package/src/__tests__/oauth-connect-handler.test.ts +109 -89
- package/src/__tests__/oauth-scope-policy.test.ts +63 -55
- package/src/__tests__/oauth2-gateway-transport.test.ts +252 -174
- package/src/__tests__/onboarding-starter-tasks.test.ts +93 -89
- package/src/__tests__/onboarding-template-contract.test.ts +93 -94
- package/src/__tests__/openai-provider.test.ts +366 -274
- package/src/__tests__/pairing-concurrent.test.ts +18 -12
- package/src/__tests__/pairing-routes.test.ts +45 -41
- package/src/__tests__/parallel-tool.benchmark.test.ts +108 -58
- package/src/__tests__/parser.test.ts +316 -226
- package/src/__tests__/path-classifier.test.ts +24 -25
- package/src/__tests__/path-policy.test.ts +187 -147
- package/src/__tests__/phone.test.ts +36 -36
- package/src/__tests__/platform-move-helper.test.ts +48 -40
- package/src/__tests__/platform-socket-path.test.ts +23 -24
- package/src/__tests__/platform-workspace-migration.test.ts +464 -414
- package/src/__tests__/platform.test.ts +61 -53
- package/src/__tests__/playbook-execution.test.ts +397 -265
- package/src/__tests__/playbook-tools.test.ts +267 -196
- package/src/__tests__/prebuilt-home-base-seed.test.ts +30 -27
- package/src/__tests__/pricing.test.ts +316 -136
- package/src/__tests__/profile-compiler.test.ts +206 -188
- package/src/__tests__/provider-commit-message-generator.test.ts +114 -106
- package/src/__tests__/provider-error-scenarios.test.ts +212 -158
- package/src/__tests__/provider-fail-open-selection.test.ts +51 -44
- package/src/__tests__/provider-registry-ollama.test.ts +13 -9
- package/src/__tests__/provider-streaming.benchmark.test.ts +232 -183
- package/src/__tests__/proxy-approval-callback.test.ts +180 -119
- package/src/__tests__/public-ingress-urls.test.ts +112 -94
- package/src/__tests__/qdrant-manager.test.ts +147 -98
- package/src/__tests__/ratelimit.test.ts +152 -82
- package/src/__tests__/recording-handler.test.ts +273 -151
- package/src/__tests__/recording-intent-fallback.test.ts +94 -75
- package/src/__tests__/recording-intent-handler.test.ts +0 -1
- package/src/__tests__/recording-intent.test.ts +578 -379
- package/src/__tests__/recording-state-machine.test.ts +530 -316
- package/src/__tests__/recurrence-engine-rruleset.test.ts +150 -92
- package/src/__tests__/recurrence-engine.test.ts +81 -41
- package/src/__tests__/recurrence-types.test.ts +63 -44
- package/src/__tests__/relay-server.test.ts +2131 -1602
- package/src/__tests__/reminder-store.test.ts +158 -80
- package/src/__tests__/reminder.test.ts +113 -109
- package/src/__tests__/remote-skill-policy.test.ts +96 -72
- package/src/__tests__/request-file-tool.test.ts +74 -67
- package/src/__tests__/response-tier.test.ts +131 -74
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +167 -145
- package/src/__tests__/runtime-events-sse.test.ts +0 -1
- package/src/__tests__/sandbox-diagnostics.test.ts +66 -56
- package/src/__tests__/sandbox-host-parity.test.ts +377 -301
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +213 -161
- package/src/__tests__/schedule-store.test.ts +268 -205
- package/src/__tests__/schedule-tools.test.ts +702 -524
- package/src/__tests__/scheduler-recurrence.test.ts +240 -130
- package/src/__tests__/scoped-approval-grants.test.ts +258 -168
- package/src/__tests__/scoped-grant-security-matrix.test.ts +160 -146
- package/src/__tests__/script-proxy-certs.test.ts +38 -35
- package/src/__tests__/script-proxy-connect-tunnel.test.ts +71 -46
- package/src/__tests__/script-proxy-decision-trace.test.ts +161 -84
- package/src/__tests__/script-proxy-http-forwarder.test.ts +146 -129
- package/src/__tests__/script-proxy-injection-runtime.test.ts +139 -113
- package/src/__tests__/script-proxy-mitm-handler.test.ts +226 -142
- package/src/__tests__/script-proxy-policy-runtime.test.ts +126 -86
- package/src/__tests__/script-proxy-policy.test.ts +308 -153
- package/src/__tests__/script-proxy-rewrite-specificity.test.ts +74 -62
- package/src/__tests__/script-proxy-router.test.ts +111 -77
- package/src/__tests__/script-proxy-session-manager.test.ts +156 -113
- package/src/__tests__/script-proxy-session-runtime.test.ts +28 -24
- package/src/__tests__/secret-allowlist.test.ts +105 -90
- package/src/__tests__/secret-ingress-handler.test.ts +41 -30
- package/src/__tests__/secret-onetime-send.test.ts +67 -50
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +35 -31
- package/src/__tests__/secret-response-routing.test.ts +50 -41
- package/src/__tests__/secret-scanner-executor.test.ts +152 -111
- package/src/__tests__/secret-scanner.test.ts +495 -413
- package/src/__tests__/secure-keys.test.ts +132 -121
- package/src/__tests__/send-endpoint-busy.test.ts +0 -1
- package/src/__tests__/send-notification-tool.test.ts +43 -42
- package/src/__tests__/sensitive-output-placeholders.test.ts +72 -64
- package/src/__tests__/sequence-store.test.ts +335 -167
- package/src/__tests__/server-history-render.test.ts +341 -202
- package/src/__tests__/session-abort-tool-results.test.ts +133 -70
- package/src/__tests__/session-confirmation-signals.test.ts +252 -160
- package/src/__tests__/session-conflict-gate.test.ts +775 -585
- package/src/__tests__/session-error.test.ts +222 -191
- package/src/__tests__/session-evictor.test.ts +79 -62
- package/src/__tests__/session-init.benchmark.test.ts +170 -108
- package/src/__tests__/session-load-history-repair.test.ts +273 -139
- package/src/__tests__/session-messaging-secret-redirect.test.ts +130 -90
- package/src/__tests__/session-pre-run-repair.test.ts +106 -59
- package/src/__tests__/session-profile-injection.test.ts +198 -130
- package/src/__tests__/session-provider-retry-repair.test.ts +223 -141
- package/src/__tests__/session-queue.test.ts +624 -321
- package/src/__tests__/session-runtime-assembly.test.ts +425 -329
- package/src/__tests__/session-runtime-workspace.test.ts +69 -61
- package/src/__tests__/session-skill-tools.test.ts +973 -678
- package/src/__tests__/session-slash-known.test.ts +185 -133
- package/src/__tests__/session-slash-queue.test.ts +147 -81
- package/src/__tests__/session-slash-unknown.test.ts +135 -90
- package/src/__tests__/session-surfaces-task-progress.test.ts +122 -87
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +338 -177
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +63 -40
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +60 -37
- package/src/__tests__/session-tool-setup-tools-disabled.test.ts +28 -26
- package/src/__tests__/session-undo.test.ts +43 -30
- package/src/__tests__/session-workspace-cache-state.test.ts +108 -67
- package/src/__tests__/session-workspace-injection.test.ts +245 -117
- package/src/__tests__/session-workspace-tool-tracking.test.ts +260 -93
- package/src/__tests__/shared-filesystem-errors.test.ts +47 -47
- package/src/__tests__/shell-credential-ref.test.ts +126 -90
- package/src/__tests__/shell-identity.test.ts +134 -111
- package/src/__tests__/shell-parser-fuzz.test.ts +263 -179
- package/src/__tests__/shell-parser-property.test.ts +435 -288
- package/src/__tests__/shell-tool-proxy-mode.test.ts +142 -70
- package/src/__tests__/size-guard.test.ts +42 -44
- package/src/__tests__/skill-feature-flags-integration.test.ts +79 -52
- package/src/__tests__/skill-feature-flags.test.ts +75 -47
- package/src/__tests__/skill-include-graph.test.ts +143 -148
- package/src/__tests__/skill-load-feature-flag.test.ts +94 -59
- package/src/__tests__/skill-load-tool.test.ts +371 -199
- package/src/__tests__/skill-projection-feature-flag.test.ts +131 -88
- package/src/__tests__/skill-projection.benchmark.test.ts +93 -65
- package/src/__tests__/skill-script-runner-host.test.ts +460 -250
- package/src/__tests__/skill-script-runner-sandbox.test.ts +168 -108
- package/src/__tests__/skill-script-runner.test.ts +115 -74
- package/src/__tests__/skill-tool-factory.test.ts +140 -96
- package/src/__tests__/skill-tool-manifest.test.ts +306 -210
- package/src/__tests__/skill-version-hash.test.ts +70 -56
- package/src/__tests__/skills.test.ts +0 -1
- package/src/__tests__/slack-channel-config.test.ts +127 -84
- package/src/__tests__/slack-skill.test.ts +60 -47
- package/src/__tests__/slash-commands-catalog.test.ts +37 -31
- package/src/__tests__/slash-commands-parser.test.ts +71 -64
- package/src/__tests__/slash-commands-resolver.test.ts +143 -107
- package/src/__tests__/slash-commands-rewrite.test.ts +22 -22
- package/src/__tests__/speaker-identification.test.ts +28 -25
- package/src/__tests__/starter-bundle.test.ts +27 -23
- package/src/__tests__/starter-task-flow.test.ts +67 -52
- package/src/__tests__/subagent-manager-notify.test.ts +154 -108
- package/src/__tests__/subagent-tools.test.ts +311 -270
- package/src/__tests__/subagent-types.test.ts +40 -40
- package/src/__tests__/surface-mutex-cleanup.test.ts +42 -30
- package/src/__tests__/swarm-dag-pathological.test.ts +122 -111
- package/src/__tests__/swarm-orchestrator.test.ts +135 -101
- package/src/__tests__/swarm-plan-validator.test.ts +125 -73
- package/src/__tests__/swarm-recursion.test.ts +58 -46
- package/src/__tests__/swarm-router-planner.test.ts +99 -74
- package/src/__tests__/swarm-session-integration.test.ts +148 -91
- package/src/__tests__/swarm-tool.test.ts +65 -45
- package/src/__tests__/swarm-worker-backend.test.ts +59 -45
- package/src/__tests__/swarm-worker-runner.test.ts +133 -118
- package/src/__tests__/system-prompt.test.ts +290 -256
- package/src/__tests__/task-compiler.test.ts +176 -120
- package/src/__tests__/task-management-tools.test.ts +561 -456
- package/src/__tests__/task-memory-cleanup.test.ts +627 -362
- package/src/__tests__/task-runner.test.ts +117 -94
- package/src/__tests__/task-scheduler.test.ts +113 -84
- package/src/__tests__/task-tools.test.ts +349 -264
- package/src/__tests__/terminal-sandbox.test.ts +138 -108
- package/src/__tests__/terminal-tools.test.ts +350 -305
- package/src/__tests__/thread-seed-composer.test.ts +307 -180
- package/src/__tests__/tool-approval-handler.test.ts +238 -137
- package/src/__tests__/tool-audit-listener.test.ts +69 -69
- package/src/__tests__/tool-domain-event-publisher.test.ts +142 -132
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +153 -146
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +136 -105
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +355 -239
- package/src/__tests__/tool-executor-redaction.test.ts +112 -109
- package/src/__tests__/tool-executor-shell-integration.test.ts +130 -79
- package/src/__tests__/tool-executor.test.ts +1274 -674
- package/src/__tests__/tool-grant-request-escalation.test.ts +401 -283
- package/src/__tests__/tool-metrics-listener.test.ts +97 -85
- package/src/__tests__/tool-notification-listener.test.ts +42 -25
- package/src/__tests__/tool-permission-simulate-handler.test.ts +137 -113
- package/src/__tests__/tool-policy.test.ts +44 -25
- package/src/__tests__/tool-profiling-listener.test.ts +99 -93
- package/src/__tests__/tool-result-truncation.test.ts +5 -4
- package/src/__tests__/tool-trace-listener.test.ts +131 -111
- package/src/__tests__/top-level-renderer.test.ts +62 -58
- package/src/__tests__/top-level-scanner.test.ts +68 -64
- package/src/__tests__/trace-emitter.test.ts +56 -56
- package/src/__tests__/trust-context-guards.test.ts +65 -65
- package/src/__tests__/trust-store.test.ts +1239 -806
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +3 -2
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -2
- package/src/__tests__/trusted-contact-verification.test.ts +251 -231
- package/src/__tests__/turn-commit.test.ts +259 -200
- package/src/__tests__/twilio-provider.test.ts +140 -126
- package/src/__tests__/twilio-rest.test.ts +22 -18
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +0 -1
- package/src/__tests__/twilio-routes-twiml.test.ts +55 -55
- package/src/__tests__/twilio-routes.test.ts +0 -1
- package/src/__tests__/twitter-auth-handler.test.ts +184 -139
- package/src/__tests__/twitter-cli-error-shaping.test.ts +88 -73
- package/src/__tests__/twitter-cli-routing.test.ts +146 -99
- package/src/__tests__/twitter-oauth-client.test.ts +82 -65
- package/src/__tests__/update-bulletin-format.test.ts +69 -66
- package/src/__tests__/update-bulletin-state.test.ts +66 -60
- package/src/__tests__/update-bulletin.test.ts +150 -114
- package/src/__tests__/update-template-contract.test.ts +15 -10
- package/src/__tests__/url-safety.test.ts +288 -265
- package/src/__tests__/user-reference.test.ts +32 -32
- package/src/__tests__/view-image-tool.test.ts +118 -96
- package/src/__tests__/voice-invite-redemption.test.ts +111 -106
- package/src/__tests__/voice-quality.test.ts +117 -102
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +204 -146
- package/src/__tests__/voice-session-bridge.test.ts +351 -216
- package/src/__tests__/weather-skill-regression.test.ts +170 -120
- package/src/__tests__/web-fetch.test.ts +664 -526
- package/src/__tests__/web-search.test.ts +379 -213
- package/src/__tests__/work-item-output.test.ts +90 -53
- package/src/__tests__/workspace-git-service.test.ts +437 -356
- package/src/__tests__/workspace-heartbeat-service.test.ts +125 -91
- package/src/__tests__/workspace-lifecycle.test.ts +98 -64
- package/src/__tests__/workspace-policy.test.ts +139 -71
- package/src/commands/__tests__/cc-command-registry.test.ts +142 -134
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +48 -39
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +25 -10
- package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +0 -1
- package/src/config/bundled-skills/messaging/SKILL.md +4 -3
- package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +15 -5
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +16 -5
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +34 -32
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/env.ts +3 -4
- package/src/memory/db-connection.ts +16 -10
- package/src/messaging/providers/gmail/adapter.ts +10 -3
- package/src/messaging/providers/gmail/client.ts +280 -72
- package/src/runtime/auth/__tests__/context.test.ts +75 -65
- package/src/runtime/auth/__tests__/credential-service.test.ts +137 -114
- package/src/runtime/auth/__tests__/guard-tests.test.ts +84 -90
- package/src/runtime/auth/__tests__/ipc-auth-context.test.ts +40 -40
- package/src/runtime/auth/__tests__/middleware.test.ts +80 -74
- package/src/runtime/auth/__tests__/policy.test.ts +9 -9
- package/src/runtime/auth/__tests__/route-policy.test.ts +76 -65
- package/src/runtime/auth/__tests__/scopes.test.ts +68 -60
- package/src/runtime/auth/__tests__/subject.test.ts +54 -54
- package/src/runtime/auth/__tests__/token-service.test.ts +115 -108
- package/src/runtime/auth/scopes.ts +3 -0
- package/src/runtime/auth/token-service.ts +4 -1
- package/src/runtime/auth/types.ts +2 -1
- package/src/runtime/http-server.ts +2 -1
- package/src/security/secure-keys.ts +103 -53
- package/src/tools/browser/__tests__/auth-cache.test.ts +69 -63
- package/src/tools/browser/__tests__/auth-detector.test.ts +218 -157
- package/src/tools/browser/__tests__/jit-auth.test.ts +83 -99
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect,test } from
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
type AssistantAttachmentDraft,
|
|
@@ -12,47 +12,47 @@ import {
|
|
|
12
12
|
MAX_ASSISTANT_ATTACHMENT_BYTES,
|
|
13
13
|
MAX_ASSISTANT_ATTACHMENTS,
|
|
14
14
|
validateDrafts,
|
|
15
|
-
} from
|
|
15
|
+
} from "../daemon/assistant-attachments.js";
|
|
16
16
|
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
18
|
// estimateBase64Bytes
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
|
|
21
|
-
describe(
|
|
22
|
-
test(
|
|
23
|
-
expect(estimateBase64Bytes(
|
|
21
|
+
describe("estimateBase64Bytes", () => {
|
|
22
|
+
test("returns 0 for empty string", () => {
|
|
23
|
+
expect(estimateBase64Bytes("")).toBe(0);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
test(
|
|
26
|
+
test("handles no-padding base64", () => {
|
|
27
27
|
// "abc" → base64 "YWJj" (4 chars, 0 padding → 3 bytes)
|
|
28
|
-
expect(estimateBase64Bytes(
|
|
28
|
+
expect(estimateBase64Bytes("YWJj")).toBe(3);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
test(
|
|
31
|
+
test("handles single-pad base64", () => {
|
|
32
32
|
// "ab" → base64 "YWI=" (4 chars, 1 padding → 2 bytes)
|
|
33
|
-
expect(estimateBase64Bytes(
|
|
33
|
+
expect(estimateBase64Bytes("YWI=")).toBe(2);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
test(
|
|
36
|
+
test("handles double-pad base64", () => {
|
|
37
37
|
// "a" → base64 "YQ==" (4 chars, 2 padding → 1 byte)
|
|
38
|
-
expect(estimateBase64Bytes(
|
|
38
|
+
expect(estimateBase64Bytes("YQ==")).toBe(1);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
test(
|
|
41
|
+
test("estimates correctly for longer strings", () => {
|
|
42
42
|
// 12 base64 chars, no padding → 9 bytes
|
|
43
|
-
expect(estimateBase64Bytes(
|
|
43
|
+
expect(estimateBase64Bytes("SGVsbG8gV29y")).toBe(9);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
test(
|
|
46
|
+
test("trims trailing whitespace and newlines before estimating", () => {
|
|
47
47
|
// "a" → base64 "YQ==" → 1 byte; trailing newline should not affect result
|
|
48
|
-
expect(estimateBase64Bytes(
|
|
49
|
-
expect(estimateBase64Bytes(
|
|
50
|
-
expect(estimateBase64Bytes(
|
|
48
|
+
expect(estimateBase64Bytes("YQ==\n")).toBe(1);
|
|
49
|
+
expect(estimateBase64Bytes("YQ==\r\n")).toBe(1);
|
|
50
|
+
expect(estimateBase64Bytes(" YQ== ")).toBe(1);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
test(
|
|
53
|
+
test("handles embedded line breaks in base64 (e.g. PEM-style)", () => {
|
|
54
54
|
// "YWJj" split across lines should still yield 3 bytes
|
|
55
|
-
expect(estimateBase64Bytes(
|
|
55
|
+
expect(estimateBase64Bytes("YW\nJj")).toBe(3);
|
|
56
56
|
});
|
|
57
57
|
});
|
|
58
58
|
|
|
@@ -60,39 +60,39 @@ describe('estimateBase64Bytes', () => {
|
|
|
60
60
|
// inferMimeType
|
|
61
61
|
// ---------------------------------------------------------------------------
|
|
62
62
|
|
|
63
|
-
describe(
|
|
64
|
-
test(
|
|
65
|
-
expect(inferMimeType(
|
|
66
|
-
expect(inferMimeType(
|
|
67
|
-
expect(inferMimeType(
|
|
68
|
-
expect(inferMimeType(
|
|
69
|
-
expect(inferMimeType(
|
|
70
|
-
expect(inferMimeType(
|
|
63
|
+
describe("inferMimeType", () => {
|
|
64
|
+
test("infers image types", () => {
|
|
65
|
+
expect(inferMimeType("photo.png")).toBe("image/png");
|
|
66
|
+
expect(inferMimeType("photo.jpg")).toBe("image/jpeg");
|
|
67
|
+
expect(inferMimeType("photo.jpeg")).toBe("image/jpeg");
|
|
68
|
+
expect(inferMimeType("sticker.webp")).toBe("image/webp");
|
|
69
|
+
expect(inferMimeType("icon.gif")).toBe("image/gif");
|
|
70
|
+
expect(inferMimeType("diagram.svg")).toBe("image/svg+xml");
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
test(
|
|
74
|
-
expect(inferMimeType(
|
|
75
|
-
expect(inferMimeType(
|
|
76
|
-
expect(inferMimeType(
|
|
77
|
-
expect(inferMimeType(
|
|
78
|
-
expect(inferMimeType(
|
|
73
|
+
test("infers document types", () => {
|
|
74
|
+
expect(inferMimeType("report.pdf")).toBe("application/pdf");
|
|
75
|
+
expect(inferMimeType("data.json")).toBe("application/json");
|
|
76
|
+
expect(inferMimeType("notes.txt")).toBe("text/plain");
|
|
77
|
+
expect(inferMimeType("readme.md")).toBe("text/markdown");
|
|
78
|
+
expect(inferMimeType("table.csv")).toBe("text/csv");
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
test(
|
|
82
|
-
expect(inferMimeType(
|
|
83
|
-
expect(inferMimeType(
|
|
81
|
+
test("is case-insensitive on extension", () => {
|
|
82
|
+
expect(inferMimeType("PHOTO.PNG")).toBe("image/png");
|
|
83
|
+
expect(inferMimeType("Report.PDF")).toBe("application/pdf");
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
test(
|
|
87
|
-
expect(inferMimeType(
|
|
86
|
+
test("returns octet-stream for unknown extension", () => {
|
|
87
|
+
expect(inferMimeType("file.xyz")).toBe("application/octet-stream");
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
test(
|
|
91
|
-
expect(inferMimeType(
|
|
90
|
+
test("returns octet-stream for no extension", () => {
|
|
91
|
+
expect(inferMimeType("Makefile")).toBe("application/octet-stream");
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
test(
|
|
95
|
-
expect(inferMimeType(
|
|
94
|
+
test("uses last extension for double-dotted names", () => {
|
|
95
|
+
expect(inferMimeType("archive.tar.gz")).toBe("application/gzip");
|
|
96
96
|
});
|
|
97
97
|
});
|
|
98
98
|
|
|
@@ -100,23 +100,23 @@ describe('inferMimeType', () => {
|
|
|
100
100
|
// classifyKind
|
|
101
101
|
// ---------------------------------------------------------------------------
|
|
102
102
|
|
|
103
|
-
describe(
|
|
104
|
-
test(
|
|
105
|
-
expect(classifyKind(
|
|
106
|
-
expect(classifyKind(
|
|
107
|
-
expect(classifyKind(
|
|
103
|
+
describe("classifyKind", () => {
|
|
104
|
+
test("classifies image mime types as image", () => {
|
|
105
|
+
expect(classifyKind("image/png")).toBe("image");
|
|
106
|
+
expect(classifyKind("image/jpeg")).toBe("image");
|
|
107
|
+
expect(classifyKind("image/webp")).toBe("image");
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
-
test(
|
|
111
|
-
expect(classifyKind(
|
|
112
|
-
expect(classifyKind(
|
|
113
|
-
expect(classifyKind(
|
|
110
|
+
test("classifies video mime types as video", () => {
|
|
111
|
+
expect(classifyKind("video/mp4")).toBe("video");
|
|
112
|
+
expect(classifyKind("video/webm")).toBe("video");
|
|
113
|
+
expect(classifyKind("video/quicktime")).toBe("video");
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
test(
|
|
117
|
-
expect(classifyKind(
|
|
118
|
-
expect(classifyKind(
|
|
119
|
-
expect(classifyKind(
|
|
116
|
+
test("classifies non-image non-video mime types as document", () => {
|
|
117
|
+
expect(classifyKind("application/pdf")).toBe("document");
|
|
118
|
+
expect(classifyKind("text/plain")).toBe("document");
|
|
119
|
+
expect(classifyKind("application/octet-stream")).toBe("document");
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
122
|
|
|
@@ -124,49 +124,65 @@ describe('classifyKind', () => {
|
|
|
124
124
|
// validateDrafts
|
|
125
125
|
// ---------------------------------------------------------------------------
|
|
126
126
|
|
|
127
|
-
function makeDraft(
|
|
127
|
+
function makeDraft(
|
|
128
|
+
overrides: Partial<AssistantAttachmentDraft> = {},
|
|
129
|
+
): AssistantAttachmentDraft {
|
|
128
130
|
return {
|
|
129
|
-
sourceType:
|
|
130
|
-
filename:
|
|
131
|
-
mimeType:
|
|
132
|
-
dataBase64:
|
|
131
|
+
sourceType: "sandbox_file",
|
|
132
|
+
filename: "test.txt",
|
|
133
|
+
mimeType: "text/plain",
|
|
134
|
+
dataBase64: "dGVzdA==",
|
|
133
135
|
sizeBytes: 4,
|
|
134
|
-
kind:
|
|
136
|
+
kind: "document",
|
|
135
137
|
...overrides,
|
|
136
138
|
};
|
|
137
139
|
}
|
|
138
140
|
|
|
139
|
-
describe(
|
|
140
|
-
test(
|
|
141
|
-
const drafts = [
|
|
141
|
+
describe("validateDrafts", () => {
|
|
142
|
+
test("accepts drafts within limits", () => {
|
|
143
|
+
const drafts = [
|
|
144
|
+
makeDraft({ filename: "a.txt" }),
|
|
145
|
+
makeDraft({ filename: "b.txt" }),
|
|
146
|
+
];
|
|
142
147
|
const result = validateDrafts(drafts);
|
|
143
148
|
expect(result.accepted).toHaveLength(2);
|
|
144
149
|
expect(result.warnings).toHaveLength(0);
|
|
145
150
|
});
|
|
146
151
|
|
|
147
|
-
test(
|
|
148
|
-
const drafts = [
|
|
152
|
+
test("rejects oversized attachments", () => {
|
|
153
|
+
const drafts = [
|
|
154
|
+
makeDraft({
|
|
155
|
+
filename: "big.bin",
|
|
156
|
+
sizeBytes: MAX_ASSISTANT_ATTACHMENT_BYTES + 1,
|
|
157
|
+
}),
|
|
158
|
+
];
|
|
149
159
|
const result = validateDrafts(drafts);
|
|
150
160
|
expect(result.accepted).toHaveLength(0);
|
|
151
161
|
expect(result.warnings).toHaveLength(1);
|
|
152
|
-
expect(result.warnings[0]).toContain(
|
|
153
|
-
expect(result.warnings[0]).toContain(
|
|
162
|
+
expect(result.warnings[0]).toContain("big.bin");
|
|
163
|
+
expect(result.warnings[0]).toContain("exceeds");
|
|
154
164
|
});
|
|
155
165
|
|
|
156
|
-
test(
|
|
157
|
-
const drafts = Array.from(
|
|
158
|
-
|
|
166
|
+
test("truncates beyond max count", () => {
|
|
167
|
+
const drafts = Array.from(
|
|
168
|
+
{ length: MAX_ASSISTANT_ATTACHMENTS + 2 },
|
|
169
|
+
(_, i) => makeDraft({ filename: `file-${i}.txt` }),
|
|
159
170
|
);
|
|
160
171
|
const result = validateDrafts(drafts);
|
|
161
172
|
expect(result.accepted).toHaveLength(MAX_ASSISTANT_ATTACHMENTS);
|
|
162
173
|
expect(result.warnings).toHaveLength(2);
|
|
163
|
-
expect(result.warnings[0]).toContain(
|
|
164
|
-
|
|
174
|
+
expect(result.warnings[0]).toContain(
|
|
175
|
+
`file-${MAX_ASSISTANT_ATTACHMENTS}.txt`,
|
|
176
|
+
);
|
|
177
|
+
expect(result.warnings[0]).toContain("exceeded maximum");
|
|
165
178
|
});
|
|
166
179
|
|
|
167
|
-
test(
|
|
180
|
+
test("rejects oversized before applying count cap", () => {
|
|
168
181
|
const drafts = [
|
|
169
|
-
makeDraft({
|
|
182
|
+
makeDraft({
|
|
183
|
+
filename: "big.bin",
|
|
184
|
+
sizeBytes: MAX_ASSISTANT_ATTACHMENT_BYTES + 1,
|
|
185
|
+
}),
|
|
170
186
|
...Array.from({ length: MAX_ASSISTANT_ATTACHMENTS }, (_, i) =>
|
|
171
187
|
makeDraft({ filename: `ok-${i}.txt` }),
|
|
172
188
|
),
|
|
@@ -175,10 +191,10 @@ describe('validateDrafts', () => {
|
|
|
175
191
|
// big.bin rejected for size; all MAX_ASSISTANT_ATTACHMENTS ok files accepted
|
|
176
192
|
expect(result.accepted).toHaveLength(MAX_ASSISTANT_ATTACHMENTS);
|
|
177
193
|
expect(result.warnings).toHaveLength(1);
|
|
178
|
-
expect(result.warnings[0]).toContain(
|
|
194
|
+
expect(result.warnings[0]).toContain("big.bin");
|
|
179
195
|
});
|
|
180
196
|
|
|
181
|
-
test(
|
|
197
|
+
test("returns empty accepted for empty input", () => {
|
|
182
198
|
const result = validateDrafts([]);
|
|
183
199
|
expect(result.accepted).toHaveLength(0);
|
|
184
200
|
expect(result.warnings).toHaveLength(0);
|
|
@@ -189,22 +205,27 @@ describe('validateDrafts', () => {
|
|
|
189
205
|
// cleanAssistantContent
|
|
190
206
|
// ---------------------------------------------------------------------------
|
|
191
207
|
|
|
192
|
-
describe(
|
|
193
|
-
test(
|
|
208
|
+
describe("cleanAssistantContent", () => {
|
|
209
|
+
test("strips directives from text blocks and returns them", () => {
|
|
194
210
|
const content = [
|
|
195
|
-
{
|
|
211
|
+
{
|
|
212
|
+
type: "text",
|
|
213
|
+
text: 'Here is the file:\n<vellum-attachment path="out.png" />',
|
|
214
|
+
},
|
|
196
215
|
];
|
|
197
216
|
const result = cleanAssistantContent(content);
|
|
198
217
|
|
|
199
218
|
expect(result.directives).toHaveLength(1);
|
|
200
|
-
expect(result.directives[0].path).toBe(
|
|
201
|
-
expect((result.cleanedContent[0] as { text: string }).text).toBe(
|
|
219
|
+
expect(result.directives[0].path).toBe("out.png");
|
|
220
|
+
expect((result.cleanedContent[0] as { text: string }).text).toBe(
|
|
221
|
+
"Here is the file:",
|
|
222
|
+
);
|
|
202
223
|
});
|
|
203
224
|
|
|
204
|
-
test(
|
|
225
|
+
test("leaves non-text blocks unchanged", () => {
|
|
205
226
|
const content = [
|
|
206
|
-
{ type:
|
|
207
|
-
{ type:
|
|
227
|
+
{ type: "tool_use", id: "t1", name: "read", input: {} },
|
|
228
|
+
{ type: "text", text: '<vellum-attachment path="x.pdf" />' },
|
|
208
229
|
];
|
|
209
230
|
const result = cleanAssistantContent(content);
|
|
210
231
|
|
|
@@ -212,33 +233,31 @@ describe('cleanAssistantContent', () => {
|
|
|
212
233
|
expect(result.directives).toHaveLength(1);
|
|
213
234
|
});
|
|
214
235
|
|
|
215
|
-
test(
|
|
236
|
+
test("accumulates warnings for malformed tags", () => {
|
|
216
237
|
const content = [
|
|
217
|
-
{ type:
|
|
238
|
+
{ type: "text", text: '<vellum-attachment source="bad" path="x.txt" />' },
|
|
218
239
|
];
|
|
219
240
|
const result = cleanAssistantContent(content);
|
|
220
241
|
|
|
221
242
|
expect(result.directives).toHaveLength(0);
|
|
222
243
|
expect(result.warnings).toHaveLength(1);
|
|
223
|
-
expect(result.warnings[0]).toContain(
|
|
244
|
+
expect(result.warnings[0]).toContain("invalid source");
|
|
224
245
|
});
|
|
225
246
|
|
|
226
|
-
test(
|
|
227
|
-
const content = [
|
|
228
|
-
{ type: 'text', text: ' Hello\n\n\n\nWorld ' },
|
|
229
|
-
];
|
|
247
|
+
test("preserves whitespace in plain text blocks without directives", () => {
|
|
248
|
+
const content = [{ type: "text", text: " Hello\n\n\n\nWorld " }];
|
|
230
249
|
const result = cleanAssistantContent(content);
|
|
231
250
|
|
|
232
251
|
expect(result.directives).toHaveLength(0);
|
|
233
252
|
expect(result.warnings).toHaveLength(0);
|
|
234
253
|
// Text should be returned exactly as-is — no trimming or blank-line collapsing
|
|
235
|
-
expect((result.cleanedContent[0] as { text: string }).text).toBe(
|
|
254
|
+
expect((result.cleanedContent[0] as { text: string }).text).toBe(
|
|
255
|
+
" Hello\n\n\n\nWorld ",
|
|
256
|
+
);
|
|
236
257
|
});
|
|
237
258
|
|
|
238
|
-
test(
|
|
239
|
-
const content = [
|
|
240
|
-
{ type: 'thinking', thinking: 'hmm', signature: 'sig' },
|
|
241
|
-
];
|
|
259
|
+
test("handles content with no text blocks", () => {
|
|
260
|
+
const content = [{ type: "thinking", thinking: "hmm", signature: "sig" }];
|
|
242
261
|
const result = cleanAssistantContent(content);
|
|
243
262
|
|
|
244
263
|
expect(result.directives).toHaveLength(0);
|
|
@@ -251,72 +270,76 @@ describe('cleanAssistantContent', () => {
|
|
|
251
270
|
// drainDirectiveDisplayBuffer
|
|
252
271
|
// ---------------------------------------------------------------------------
|
|
253
272
|
|
|
254
|
-
describe(
|
|
255
|
-
test(
|
|
273
|
+
describe("drainDirectiveDisplayBuffer", () => {
|
|
274
|
+
test("strips complete valid directives from streamed text", () => {
|
|
256
275
|
const input = 'Before <vellum-attachment path="out.png" /> After';
|
|
257
276
|
const result = drainDirectiveDisplayBuffer(input);
|
|
258
|
-
expect(result.emitText).toBe(
|
|
259
|
-
expect(result.bufferedRemainder).toBe(
|
|
277
|
+
expect(result.emitText).toBe("Before After");
|
|
278
|
+
expect(result.bufferedRemainder).toBe("");
|
|
260
279
|
});
|
|
261
280
|
|
|
262
|
-
test(
|
|
281
|
+
test("preserves invalid directives as plain text", () => {
|
|
263
282
|
const input = 'Bad <vellum-attachment source="bad" path="x.txt" /> tag';
|
|
264
283
|
const result = drainDirectiveDisplayBuffer(input);
|
|
265
284
|
expect(result.emitText).toBe(input);
|
|
266
|
-
expect(result.bufferedRemainder).toBe(
|
|
285
|
+
expect(result.bufferedRemainder).toBe("");
|
|
267
286
|
});
|
|
268
287
|
|
|
269
|
-
test(
|
|
270
|
-
const first = drainDirectiveDisplayBuffer(
|
|
271
|
-
|
|
272
|
-
|
|
288
|
+
test("buffers incomplete directives until completion", () => {
|
|
289
|
+
const first = drainDirectiveDisplayBuffer(
|
|
290
|
+
'Start <vellum-attachment path="file',
|
|
291
|
+
);
|
|
292
|
+
expect(first.emitText).toBe("Start ");
|
|
293
|
+
expect(first.bufferedRemainder).toContain("<vellum-attachment");
|
|
273
294
|
|
|
274
|
-
const second = drainDirectiveDisplayBuffer(
|
|
275
|
-
|
|
276
|
-
|
|
295
|
+
const second = drainDirectiveDisplayBuffer(
|
|
296
|
+
first.bufferedRemainder + '.png" /> done',
|
|
297
|
+
);
|
|
298
|
+
expect(second.emitText).toBe(" done");
|
|
299
|
+
expect(second.bufferedRemainder).toBe("");
|
|
277
300
|
});
|
|
278
301
|
|
|
279
302
|
test('buffers trailing partial prefix "<" split across chunks', () => {
|
|
280
|
-
const result = drainDirectiveDisplayBuffer(
|
|
281
|
-
expect(result.emitText).toBe(
|
|
282
|
-
expect(result.bufferedRemainder).toBe(
|
|
303
|
+
const result = drainDirectiveDisplayBuffer("Hello world<");
|
|
304
|
+
expect(result.emitText).toBe("Hello world");
|
|
305
|
+
expect(result.bufferedRemainder).toBe("<");
|
|
283
306
|
});
|
|
284
307
|
|
|
285
308
|
test('buffers trailing partial prefix "<vel" split across chunks', () => {
|
|
286
|
-
const result = drainDirectiveDisplayBuffer(
|
|
287
|
-
expect(result.emitText).toBe(
|
|
288
|
-
expect(result.bufferedRemainder).toBe(
|
|
309
|
+
const result = drainDirectiveDisplayBuffer("Some text<vel");
|
|
310
|
+
expect(result.emitText).toBe("Some text");
|
|
311
|
+
expect(result.bufferedRemainder).toBe("<vel");
|
|
289
312
|
});
|
|
290
313
|
|
|
291
314
|
test('buffers trailing partial prefix "<vellum-attachmen" (one char short)', () => {
|
|
292
|
-
const result = drainDirectiveDisplayBuffer(
|
|
293
|
-
expect(result.emitText).toBe(
|
|
294
|
-
expect(result.bufferedRemainder).toBe(
|
|
315
|
+
const result = drainDirectiveDisplayBuffer("Data <vellum-attachmen");
|
|
316
|
+
expect(result.emitText).toBe("Data ");
|
|
317
|
+
expect(result.bufferedRemainder).toBe("<vellum-attachmen");
|
|
295
318
|
});
|
|
296
319
|
|
|
297
320
|
test('does not buffer trailing "<" that is not a prefix of the tag', () => {
|
|
298
321
|
// "<x" does not match any prefix of "<vellum-attachment"
|
|
299
|
-
const result = drainDirectiveDisplayBuffer(
|
|
300
|
-
expect(result.emitText).toBe(
|
|
301
|
-
expect(result.bufferedRemainder).toBe(
|
|
322
|
+
const result = drainDirectiveDisplayBuffer("Hello<x");
|
|
323
|
+
expect(result.emitText).toBe("Hello<x");
|
|
324
|
+
expect(result.bufferedRemainder).toBe("");
|
|
302
325
|
});
|
|
303
326
|
|
|
304
|
-
test(
|
|
305
|
-
const first = drainDirectiveDisplayBuffer(
|
|
306
|
-
expect(first.emitText).toBe(
|
|
307
|
-
expect(first.bufferedRemainder).toBe(
|
|
327
|
+
test("partial prefix reassembles into a complete directive across chunks", () => {
|
|
328
|
+
const first = drainDirectiveDisplayBuffer("Before <vellum-at");
|
|
329
|
+
expect(first.emitText).toBe("Before ");
|
|
330
|
+
expect(first.bufferedRemainder).toBe("<vellum-at");
|
|
308
331
|
|
|
309
332
|
const second = drainDirectiveDisplayBuffer(
|
|
310
333
|
first.bufferedRemainder + 'tachment path="out.png" /> After',
|
|
311
334
|
);
|
|
312
|
-
expect(second.emitText).toBe(
|
|
313
|
-
expect(second.bufferedRemainder).toBe(
|
|
335
|
+
expect(second.emitText).toBe(" After");
|
|
336
|
+
expect(second.bufferedRemainder).toBe("");
|
|
314
337
|
});
|
|
315
338
|
|
|
316
|
-
test(
|
|
317
|
-
const result = drainDirectiveDisplayBuffer(
|
|
318
|
-
expect(result.emitText).toBe(
|
|
319
|
-
expect(result.bufferedRemainder).toBe(
|
|
339
|
+
test("emits everything when text has no partial prefix", () => {
|
|
340
|
+
const result = drainDirectiveDisplayBuffer("No special chars here");
|
|
341
|
+
expect(result.emitText).toBe("No special chars here");
|
|
342
|
+
expect(result.bufferedRemainder).toBe("");
|
|
320
343
|
});
|
|
321
344
|
});
|
|
322
345
|
|
|
@@ -324,50 +347,58 @@ describe('drainDirectiveDisplayBuffer', () => {
|
|
|
324
347
|
// contentBlocksToDrafts
|
|
325
348
|
// ---------------------------------------------------------------------------
|
|
326
349
|
|
|
327
|
-
describe(
|
|
328
|
-
test(
|
|
350
|
+
describe("contentBlocksToDrafts", () => {
|
|
351
|
+
test("converts image content block to draft", () => {
|
|
329
352
|
const blocks = [
|
|
330
353
|
{
|
|
331
|
-
type:
|
|
332
|
-
source: { type:
|
|
354
|
+
type: "image",
|
|
355
|
+
source: { type: "base64", media_type: "image/png", data: "iVBORw0K" },
|
|
333
356
|
},
|
|
334
357
|
];
|
|
335
358
|
const drafts = contentBlocksToDrafts(blocks);
|
|
336
359
|
|
|
337
360
|
expect(drafts).toHaveLength(1);
|
|
338
|
-
expect(drafts[0].sourceType).toBe(
|
|
339
|
-
expect(drafts[0].filename).toBe(
|
|
340
|
-
expect(drafts[0].mimeType).toBe(
|
|
341
|
-
expect(drafts[0].kind).toBe(
|
|
361
|
+
expect(drafts[0].sourceType).toBe("tool_block");
|
|
362
|
+
expect(drafts[0].filename).toBe("tool-output.png");
|
|
363
|
+
expect(drafts[0].mimeType).toBe("image/png");
|
|
364
|
+
expect(drafts[0].kind).toBe("image");
|
|
342
365
|
});
|
|
343
366
|
|
|
344
|
-
test(
|
|
367
|
+
test("converts file content block to draft", () => {
|
|
345
368
|
const blocks = [
|
|
346
369
|
{
|
|
347
|
-
type:
|
|
348
|
-
source: {
|
|
370
|
+
type: "file",
|
|
371
|
+
source: {
|
|
372
|
+
type: "base64",
|
|
373
|
+
media_type: "application/pdf",
|
|
374
|
+
data: "JVBER",
|
|
375
|
+
filename: "report.pdf",
|
|
376
|
+
},
|
|
349
377
|
},
|
|
350
378
|
];
|
|
351
379
|
const drafts = contentBlocksToDrafts(blocks);
|
|
352
380
|
|
|
353
381
|
expect(drafts).toHaveLength(1);
|
|
354
|
-
expect(drafts[0].sourceType).toBe(
|
|
355
|
-
expect(drafts[0].filename).toBe(
|
|
356
|
-
expect(drafts[0].kind).toBe(
|
|
382
|
+
expect(drafts[0].sourceType).toBe("tool_block");
|
|
383
|
+
expect(drafts[0].filename).toBe("report.pdf");
|
|
384
|
+
expect(drafts[0].kind).toBe("document");
|
|
357
385
|
});
|
|
358
386
|
|
|
359
|
-
test(
|
|
387
|
+
test("skips non-image/file blocks", () => {
|
|
360
388
|
const blocks = [
|
|
361
|
-
{ type:
|
|
362
|
-
{
|
|
389
|
+
{ type: "text", text: "hello" },
|
|
390
|
+
{
|
|
391
|
+
type: "image",
|
|
392
|
+
source: { type: "base64", media_type: "image/jpeg", data: "/9j/" },
|
|
393
|
+
},
|
|
363
394
|
];
|
|
364
395
|
const drafts = contentBlocksToDrafts(blocks);
|
|
365
396
|
|
|
366
397
|
expect(drafts).toHaveLength(1);
|
|
367
|
-
expect(drafts[0].mimeType).toBe(
|
|
398
|
+
expect(drafts[0].mimeType).toBe("image/jpeg");
|
|
368
399
|
});
|
|
369
400
|
|
|
370
|
-
test(
|
|
401
|
+
test("returns empty for empty input", () => {
|
|
371
402
|
expect(contentBlocksToDrafts([])).toHaveLength(0);
|
|
372
403
|
});
|
|
373
404
|
});
|
|
@@ -376,18 +407,18 @@ describe('contentBlocksToDrafts', () => {
|
|
|
376
407
|
// deduplicateDrafts
|
|
377
408
|
// ---------------------------------------------------------------------------
|
|
378
409
|
|
|
379
|
-
describe(
|
|
380
|
-
test(
|
|
410
|
+
describe("validateDrafts with reversed tool drafts", () => {
|
|
411
|
+
test("most recent tool screenshots win the attachment cap when reversed before validation", () => {
|
|
381
412
|
// Simulate a browser session producing many screenshots chronologically.
|
|
382
413
|
// After reversing, the most recent (highest index) should appear first
|
|
383
414
|
// and win the MAX_ASSISTANT_ATTACHMENTS cap.
|
|
384
415
|
const totalScreenshots = MAX_ASSISTANT_ATTACHMENTS + 3;
|
|
385
416
|
const toolDrafts = Array.from({ length: totalScreenshots }, (_, i) =>
|
|
386
417
|
makeDraft({
|
|
387
|
-
sourceType:
|
|
418
|
+
sourceType: "tool_block",
|
|
388
419
|
filename: `screenshot-step-${i}.png`,
|
|
389
|
-
mimeType:
|
|
390
|
-
kind:
|
|
420
|
+
mimeType: "image/png",
|
|
421
|
+
kind: "image",
|
|
391
422
|
dataBase64: `data${i}`,
|
|
392
423
|
}),
|
|
393
424
|
);
|
|
@@ -399,74 +430,111 @@ describe('validateDrafts with reversed tool drafts', () => {
|
|
|
399
430
|
expect(result.accepted).toHaveLength(MAX_ASSISTANT_ATTACHMENTS);
|
|
400
431
|
|
|
401
432
|
// The accepted drafts should be the most recent screenshots (highest step numbers)
|
|
402
|
-
const acceptedFilenames = result.accepted.map(d => d.filename);
|
|
433
|
+
const acceptedFilenames = result.accepted.map((d) => d.filename);
|
|
403
434
|
for (let i = 0; i < MAX_ASSISTANT_ATTACHMENTS; i++) {
|
|
404
|
-
expect(acceptedFilenames[i]).toBe(
|
|
435
|
+
expect(acceptedFilenames[i]).toBe(
|
|
436
|
+
`screenshot-step-${totalScreenshots - 1 - i}.png`,
|
|
437
|
+
);
|
|
405
438
|
}
|
|
406
439
|
|
|
407
440
|
// The oldest screenshots should be dropped
|
|
408
441
|
expect(result.warnings).toHaveLength(3);
|
|
409
|
-
expect(result.warnings[0]).toContain(
|
|
442
|
+
expect(result.warnings[0]).toContain("screenshot-step-2.png");
|
|
410
443
|
});
|
|
411
444
|
});
|
|
412
445
|
|
|
413
|
-
describe(
|
|
414
|
-
test(
|
|
415
|
-
const d = makeDraft({
|
|
416
|
-
|
|
446
|
+
describe("deduplicateDrafts", () => {
|
|
447
|
+
test("removes exact duplicates with same filename and content", () => {
|
|
448
|
+
const d = makeDraft({
|
|
449
|
+
filename: "same.txt",
|
|
450
|
+
dataBase64: "AAAA".repeat(20),
|
|
451
|
+
});
|
|
452
|
+
const result = deduplicateDrafts([
|
|
453
|
+
d,
|
|
454
|
+
{ ...d },
|
|
455
|
+
makeDraft({ filename: "other.txt" }),
|
|
456
|
+
]);
|
|
417
457
|
|
|
418
458
|
expect(result).toHaveLength(2);
|
|
419
|
-
expect(result[0].filename).toBe(
|
|
420
|
-
expect(result[1].filename).toBe(
|
|
459
|
+
expect(result[0].filename).toBe("same.txt");
|
|
460
|
+
expect(result[1].filename).toBe("other.txt");
|
|
421
461
|
});
|
|
422
462
|
|
|
423
|
-
test(
|
|
463
|
+
test("drops tool_block duplicate when directive has same content", () => {
|
|
424
464
|
// Simulates directive tag ("chart.png") + auto-converted tool block
|
|
425
465
|
// ("tool-output.png") for the same file data — the directive draft
|
|
426
466
|
// should win because it appears first in the merged array.
|
|
427
|
-
const data =
|
|
428
|
-
const directive = makeDraft({
|
|
429
|
-
|
|
467
|
+
const data = "AAAA".repeat(20);
|
|
468
|
+
const directive = makeDraft({
|
|
469
|
+
sourceType: "sandbox_file",
|
|
470
|
+
filename: "chart.png",
|
|
471
|
+
dataBase64: data,
|
|
472
|
+
});
|
|
473
|
+
const toolBlock = makeDraft({
|
|
474
|
+
sourceType: "tool_block",
|
|
475
|
+
filename: "tool-output.png",
|
|
476
|
+
dataBase64: data,
|
|
477
|
+
});
|
|
430
478
|
const result = deduplicateDrafts([directive, toolBlock]);
|
|
431
479
|
|
|
432
480
|
expect(result).toHaveLength(1);
|
|
433
|
-
expect(result[0].filename).toBe(
|
|
481
|
+
expect(result[0].filename).toBe("chart.png");
|
|
434
482
|
});
|
|
435
483
|
|
|
436
|
-
test(
|
|
484
|
+
test("keeps non-tool drafts with different filenames but identical content", () => {
|
|
437
485
|
// Two distinct files that happen to have the same bytes (e.g. empty
|
|
438
486
|
// files, or before.json / after.json with identical content) should
|
|
439
487
|
// both be kept.
|
|
440
|
-
const data =
|
|
441
|
-
const d1 = makeDraft({
|
|
442
|
-
|
|
488
|
+
const data = "AAAA".repeat(20);
|
|
489
|
+
const d1 = makeDraft({
|
|
490
|
+
sourceType: "sandbox_file",
|
|
491
|
+
filename: "before.json",
|
|
492
|
+
dataBase64: data,
|
|
493
|
+
});
|
|
494
|
+
const d2 = makeDraft({
|
|
495
|
+
sourceType: "sandbox_file",
|
|
496
|
+
filename: "after.json",
|
|
497
|
+
dataBase64: data,
|
|
498
|
+
});
|
|
443
499
|
const result = deduplicateDrafts([d1, d2]);
|
|
444
500
|
|
|
445
501
|
expect(result).toHaveLength(2);
|
|
446
|
-
expect(result[0].filename).toBe(
|
|
447
|
-
expect(result[1].filename).toBe(
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
test(
|
|
451
|
-
const d1 = makeDraft({
|
|
452
|
-
|
|
502
|
+
expect(result[0].filename).toBe("before.json");
|
|
503
|
+
expect(result[1].filename).toBe("after.json");
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
test("keeps drafts with same filename but different content", () => {
|
|
507
|
+
const d1 = makeDraft({
|
|
508
|
+
filename: "file.txt",
|
|
509
|
+
dataBase64: "AAAA".repeat(20),
|
|
510
|
+
});
|
|
511
|
+
const d2 = makeDraft({
|
|
512
|
+
filename: "file.txt",
|
|
513
|
+
dataBase64: "BBBB".repeat(20),
|
|
514
|
+
});
|
|
453
515
|
const result = deduplicateDrafts([d1, d2]);
|
|
454
516
|
|
|
455
517
|
expect(result).toHaveLength(2);
|
|
456
518
|
});
|
|
457
519
|
|
|
458
|
-
test(
|
|
520
|
+
test("keeps drafts with same filename and same prefix but different content", () => {
|
|
459
521
|
// Shared 64-char prefix but different suffix — old prefix-based dedup
|
|
460
522
|
// would incorrectly drop the second draft.
|
|
461
|
-
const sharedPrefix =
|
|
462
|
-
const d1 = makeDraft({
|
|
463
|
-
|
|
523
|
+
const sharedPrefix = "A".repeat(64);
|
|
524
|
+
const d1 = makeDraft({
|
|
525
|
+
filename: "tool-output.png",
|
|
526
|
+
dataBase64: sharedPrefix + "XXXX",
|
|
527
|
+
});
|
|
528
|
+
const d2 = makeDraft({
|
|
529
|
+
filename: "tool-output.png",
|
|
530
|
+
dataBase64: sharedPrefix + "YYYY",
|
|
531
|
+
});
|
|
464
532
|
const result = deduplicateDrafts([d1, d2]);
|
|
465
533
|
|
|
466
534
|
expect(result).toHaveLength(2);
|
|
467
535
|
});
|
|
468
536
|
|
|
469
|
-
test(
|
|
537
|
+
test("returns empty for empty input", () => {
|
|
470
538
|
expect(deduplicateDrafts([])).toHaveLength(0);
|
|
471
539
|
});
|
|
472
540
|
});
|