@vellumai/assistant 0.9.0 → 0.10.0-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +18 -34
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +28 -22
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +976 -63
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/access-request-card-view.test.ts +98 -0
- package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
- package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-compiler.test.ts +15 -1
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/cancel-clears-processing.test.ts +89 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -4
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
- package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
- package/src/__tests__/config-loader-backfill.test.ts +268 -27
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +0 -6
- package/src/__tests__/contacts-tools.test.ts +29 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +58 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-lifecycle.test.ts +7 -9
- package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
- package/src/__tests__/conversation-title-service.test.ts +62 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +4 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +4 -13
- package/src/__tests__/disk-pressure-policy.test.ts +12 -0
- package/src/__tests__/disk-usage.test.ts +65 -0
- package/src/__tests__/dynamic-page-surface.test.ts +152 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-flag-listener.test.ts +110 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +3 -35
- package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/headless-browser-mode.test.ts +10 -0
- package/src/__tests__/headless-browser-navigate.test.ts +8 -3
- package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
- package/src/__tests__/host-browser-proxy.test.ts +87 -0
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/injector-v3-suppression.test.ts +27 -20
- package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
- package/src/__tests__/invite-redemption-service.test.ts +4 -7
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +30 -23
- package/src/__tests__/llm-resolver.test.ts +70 -24
- package/src/__tests__/llm-schema.test.ts +1 -0
- package/src/__tests__/managed-profile-guard.test.ts +163 -4
- package/src/__tests__/mcp-health-check.test.ts +6 -7
- package/src/__tests__/media-stream-server-integration.test.ts +317 -13
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/path-policy.test.ts +34 -0
- package/src/__tests__/persona-resolver.test.ts +49 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/plugin-api-provider.test.ts +24 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
- package/src/__tests__/reaction-persistence.test.ts +150 -29
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/relay-server.test.ts +285 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
- package/src/__tests__/skill-execute-input.test.ts +51 -1
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/skills.test.ts +51 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
- package/src/__tests__/subagent-tools.test.ts +266 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +100 -3
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
- package/src/__tests__/tool-audit-listener.test.ts +7 -7
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +8 -10
- package/src/__tests__/twilio-routes.test.ts +81 -1
- package/src/__tests__/voice-invite-redemption.test.ts +2 -3
- package/src/__tests__/weak-open-model.test.ts +30 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
- package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
- package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
- package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
- package/src/agent/loop.ts +49 -29
- package/src/api/README.md +6 -6
- package/src/api/events/tool-result.ts +6 -0
- package/src/api/events/workflow-completed.ts +53 -0
- package/src/api/events/workflow-leaf-finished.ts +38 -0
- package/src/api/events/workflow-leaf-started.ts +35 -0
- package/src/api/events/workflow-progress.ts +32 -0
- package/src/api/events/workflow-started.ts +31 -0
- package/src/api/index.ts +40 -0
- package/src/api/responses/conversation-message.ts +28 -4
- package/src/api/responses/home.ts +26 -4
- package/src/api/responses/workflow-journal.ts +53 -0
- package/src/approvals/guardian-card-withdrawal.ts +145 -0
- package/src/approvals/guardian-decision-primitive.ts +26 -3
- package/src/approvals/guardian-request-resolvers.ts +183 -80
- package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
- package/src/calls/call-pointer-messages.ts +10 -4
- package/src/calls/channel-admission-reader.ts +104 -0
- package/src/calls/guardian-dispatch.ts +17 -45
- package/src/calls/media-stream-server.ts +84 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/relay-server.ts +66 -0
- package/src/calls/relay-setup-router.ts +82 -1
- package/src/calls/twilio-routes.ts +17 -8
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/clients.ts +3 -0
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
- package/src/cli/commands/memory/index.ts +30 -0
- package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
- package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
- package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
- package/src/cli/commands/oauth/status.test.ts +36 -0
- package/src/cli/commands/oauth/status.ts +23 -3
- package/src/cli/commands/plugins.ts +197 -4
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/inspect-plugin.ts +12 -1
- package/src/cli/lib/install-from-github.ts +105 -17
- package/src/cli/lib/merge-plugin-tree.ts +328 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/plugin-surfaces.ts +104 -0
- package/src/cli/lib/upgrade-plugin.ts +298 -10
- package/src/cli/program.ts +2 -6
- package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
- package/src/config/assistant-feature-flags.ts +22 -7
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
- package/src/config/bundled-skills/messaging/SKILL.md +6 -4
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/workflows/SKILL.md +14 -8
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +15 -2
- package/src/config/feature-flag-registry.json +46 -31
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/llm-resolver.ts +3 -0
- package/src/config/loader.ts +4 -0
- package/src/config/memory-v3-gate.ts +11 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schema.ts +8 -6
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/channels.ts +11 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/llm.ts +31 -0
- package/src/config/schemas/memory-lifecycle.ts +3 -7
- package/src/config/schemas/memory-v3.ts +6 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/services.ts +18 -0
- package/src/config/seed-inference-profiles.ts +109 -44
- package/src/config/skills.ts +21 -0
- package/src/config/sync-gated-profiles.ts +220 -0
- package/src/contacts/contact-store.ts +89 -106
- package/src/contacts/contacts-write.ts +5 -22
- package/src/contacts/types.ts +0 -1
- package/src/context/compactor.ts +88 -54
- package/src/context/strip-injections.ts +58 -10
- package/src/context/token-estimator.ts +1 -1
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
- package/src/daemon/conversation-agent-loop.ts +114 -22
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +3 -5
- package/src/daemon/conversation-process.ts +13 -5
- package/src/daemon/conversation-runtime-assembly.ts +13 -15
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-surfaces.ts +26 -0
- package/src/daemon/conversation-tool-setup.ts +27 -14
- package/src/daemon/conversation.ts +66 -14
- package/src/daemon/disk-pressure-policy.ts +5 -3
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
- package/src/daemon/handlers/config-a2a.ts +0 -2
- package/src/daemon/handlers/config-channels.ts +15 -16
- package/src/daemon/handlers/config-slack-channel.ts +22 -3
- package/src/daemon/handlers/conversations.ts +107 -0
- package/src/daemon/host-browser-proxy.ts +41 -0
- package/src/daemon/lifecycle.ts +55 -27
- package/src/daemon/message-provenance.ts +2 -0
- package/src/daemon/message-types/contacts.ts +0 -1
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/message-types/web-activity.ts +7 -1
- package/src/daemon/message-types/workflows.ts +83 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/daemon/tool-setup-types.ts +4 -0
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +2 -2
- package/src/home/feed-source-enrichment.test.ts +151 -0
- package/src/home/feed-source-enrichment.ts +176 -0
- package/src/home/relationship-state.ts +2 -4
- package/src/instrument.ts +18 -6
- package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
- package/src/ipc/assistant-server.ts +37 -4
- package/src/ipc/gateway-flag-listener.ts +18 -2
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
- package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
- package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
- package/src/memory/auth-fallback-events-store.ts +2 -2
- package/src/memory/auto-analysis-enqueue.ts +3 -5
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/canonical-guardian-store.ts +39 -1
- package/src/memory/conversation-crud.ts +9 -4
- package/src/memory/conversation-key-store.ts +17 -2
- package/src/memory/conversation-title-service.ts +64 -7
- package/src/memory/db-init.ts +17 -17
- package/src/memory/embedding-backend.ts +38 -1
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +54 -1
- package/src/memory/lifecycle-events-store.ts +2 -2
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-enqueue.ts +31 -6
- package/src/memory/memory-retrospective-job.ts +28 -227
- package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
- package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
- package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
- package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
- package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/onboarding-events-store.ts +3 -3
- package/src/memory/schema/contacts.ts +0 -5
- package/src/memory/skill-loaded-events-store.test.ts +7 -15
- package/src/memory/skill-loaded-events-store.ts +2 -2
- package/src/memory/tool-executed-events-store.test.ts +7 -7
- package/src/memory/turn-trace-store.test.ts +736 -0
- package/src/memory/turn-trace-store.ts +364 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
- package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
- package/src/memory/v2/consolidation-job.ts +2 -2
- package/src/memory/v2/skill-content.ts +25 -7
- package/src/memory/v2/skill-store.ts +7 -1
- package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
- package/src/memory/v3-eval/eval-packets.ts +546 -0
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/messaging/providers/slack/api.ts +31 -0
- package/src/messaging/providers/slack/send.test.ts +114 -2
- package/src/messaging/providers/slack/send.ts +30 -7
- package/src/messaging/providers/slack/withdraw.test.ts +200 -0
- package/src/messaging/providers/slack/withdraw.ts +161 -0
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/access-request-copy.ts +72 -59
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +58 -103
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/approval-card-data.ts +333 -0
- package/src/notifications/broadcaster.ts +16 -3
- package/src/notifications/canonical-delivery-recorder.ts +139 -0
- package/src/notifications/copy-composer.ts +3 -3
- package/src/notifications/decision-engine.ts +4 -2
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/guardian-question-mode.ts +10 -0
- package/src/notifications/home-feed-side-effect.ts +7 -16
- package/src/notifications/notification-utils.ts +19 -20
- package/src/notifications/signal.ts +79 -43
- package/src/notifications/types.ts +98 -121
- package/src/oauth/AGENTS.md +5 -24
- package/src/permissions/checker.test.ts +51 -0
- package/src/permissions/checker.ts +185 -26
- package/src/permissions/ipc-risk-types.ts +24 -0
- package/src/permissions/question-prompter.test.ts +27 -0
- package/src/permissions/question-prompter.ts +4 -0
- package/src/platform/client.test.ts +119 -0
- package/src/platform/client.ts +66 -0
- package/src/platform/consent-cache.test.ts +267 -0
- package/src/platform/consent-cache.ts +174 -0
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +33 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
- package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
- package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
- package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
- package/src/plugins/defaults/advisor/config.ts +21 -0
- package/src/plugins/defaults/advisor/consult.ts +93 -0
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/advisor/package.json +14 -0
- package/src/plugins/defaults/advisor/steering.ts +67 -0
- package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
- package/src/plugins/defaults/advisor/transcript.ts +76 -0
- package/src/plugins/defaults/index.ts +60 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
- package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
- package/src/prompts/persona-resolver.ts +14 -4
- package/src/prompts/templates/system-sections.ts +7 -2
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +176 -0
- package/src/providers/atlascloud/client.ts +85 -0
- package/src/providers/fetch-provider-catalog.ts +85 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/model-catalog.ts +58 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
- package/src/providers/openai/chat-completions-provider.ts +7 -0
- package/src/providers/openai/responses-provider.ts +10 -0
- package/src/providers/provider-send-message.ts +11 -3
- package/src/providers/retry.ts +53 -12
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/weak-open-model.ts +22 -0
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/__tests__/client-health.test.ts +44 -0
- package/src/runtime/access-request-helper.ts +21 -53
- package/src/runtime/actor-trust-resolver.ts +59 -63
- package/src/runtime/agent-wake.ts +52 -0
- package/src/runtime/assistant-event-hub.ts +18 -4
- package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/capabilities.test.ts +120 -0
- package/src/runtime/capabilities.ts +197 -0
- package/src/runtime/channel-approval-types.ts +22 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/channel-verification-service.ts +3 -3
- package/src/runtime/client-health.ts +26 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
- package/src/runtime/effective-capabilities.test.ts +128 -0
- package/src/runtime/effective-capabilities.ts +84 -0
- package/src/runtime/guardian-reply-router.ts +106 -21
- package/src/runtime/invite-redemption-service.ts +9 -25
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
- package/src/runtime/migrations/vbundle-builder.ts +49 -20
- package/src/runtime/pending-interactions.ts +15 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/browser-tabs-routes.ts +9 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
- package/src/runtime/routes/client-routes.ts +10 -0
- package/src/runtime/routes/contact-routes.ts +31 -8
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +80 -1
- package/src/runtime/routes/conversation-query-routes.ts +68 -22
- package/src/runtime/routes/conversation-routes.ts +39 -14
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/guardian-approval-interception.ts +14 -73
- package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
- package/src/runtime/routes/home-feed-routes.ts +8 -3
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-message-handler.ts +214 -228
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
- package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
- package/src/runtime/routes/integrations/slack/channel.ts +36 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
- package/src/runtime/routes/mcp-auth-routes.ts +233 -41
- package/src/runtime/routes/memory-eval-routes.ts +87 -0
- package/src/runtime/routes/notification-routes.ts +122 -133
- package/src/runtime/routes/platform-routes.ts +2 -2
- package/src/runtime/routes/plugins-routes.ts +202 -3
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/surface-action-routes.ts +2 -1
- package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
- package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
- package/src/runtime/routes/workflow-routes.test.ts +229 -44
- package/src/runtime/routes/workflow-routes.ts +131 -29
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/runtime/tool-grant-request-helper.ts +18 -16
- package/src/runtime/trust-context-resolver.ts +8 -5
- package/src/schedule/inference-profile.ts +2 -14
- package/src/schedule/schedule-store.ts +1 -1
- package/src/schedule/scheduler-types.ts +5 -1
- package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
- package/src/security/secret-patterns.ts +3 -0
- package/src/subagent/manager.ts +17 -4
- package/src/subagent/types.ts +6 -0
- package/src/telemetry/trace-collection-policy.test.ts +28 -0
- package/src/telemetry/trace-collection-policy.ts +30 -0
- package/src/telemetry/types.ts +89 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
- package/src/telemetry/usage-telemetry-reporter.ts +148 -41
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
- package/src/tools/browser/browser-execution.ts +30 -19
- package/src/tools/document/document-tool.ts +2 -3
- package/src/tools/executor.ts +5 -3
- package/src/tools/host-terminal/host-shell.ts +5 -4
- package/src/tools/memory/register.ts +2 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
- package/src/tools/network/__tests__/web-search.test.ts +143 -0
- package/src/tools/network/web-fetch.ts +372 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/network/web-search.ts +213 -10
- package/src/tools/permission-checker.ts +4 -3
- package/src/tools/registry.ts +20 -0
- package/src/tools/schedule/create.ts +7 -12
- package/src/tools/schedule/update.ts +4 -11
- package/src/tools/shared/filesystem/path-policy.ts +39 -13
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +33 -0
- package/src/tools/subagent/spawn.ts +61 -12
- package/src/tools/terminal/shell.ts +10 -4
- package/src/tools/tool-approval-handler.ts +18 -13
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/types.ts +9 -0
- package/src/tools/ui-surface/definitions.ts +64 -3
- package/src/tools/verification-control-plane-policy.ts +3 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/tools/workflows/run-workflow.ts +1 -0
- package/src/util/disk-usage.ts +78 -23
- package/src/util/platform.ts +10 -3
- package/src/watcher/telemetry.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/engine.test.ts +175 -1
- package/src/workflows/engine.ts +82 -0
- package/src/workflows/journal-store.test.ts +70 -0
- package/src/workflows/journal-store.ts +18 -3
- package/src/workflows/run-manager.test.ts +171 -28
- package/src/workflows/run-manager.ts +66 -24
- package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
- package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
- package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
- package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/notifications/tool-approval-copy.ts +0 -142
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- package/src/tools/credentials/vault.ts +0 -712
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for UsageTelemetryReporter.
|
|
3
3
|
*
|
|
4
|
-
* Covers
|
|
5
|
-
*
|
|
4
|
+
* Covers the authenticated send path (and the skip behavior when credentials
|
|
5
|
+
* are absent or the platform is disabled), watermark advancement, error
|
|
6
|
+
* handling, batch recursion, device ID resolution, and payload shape.
|
|
6
7
|
*/
|
|
7
8
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
8
9
|
|
|
@@ -91,18 +92,83 @@ mock.module("../version.js", () => ({
|
|
|
91
92
|
APP_VERSION: "1.2.3-test",
|
|
92
93
|
}));
|
|
93
94
|
|
|
94
|
-
|
|
95
|
+
const mockGetCachedShareAnalytics = mock(() => true);
|
|
96
|
+
// Owner's `share_diagnostics` consent — one part of the trace-collection gate.
|
|
97
|
+
const mockGetCachedShareDiagnostics = mock(() => false);
|
|
98
|
+
// Owner's accepted diagnostics-consent version — the disclosing-version part of
|
|
99
|
+
// the trace gate. Default to a far-future (unconditionally eligible) version so
|
|
100
|
+
// the consent/flag cases drive eligibility on those axes; the version-specific
|
|
101
|
+
// cases override it with old/empty values.
|
|
102
|
+
const mockGetCachedShareDiagnosticsVersion = mock(() => "2999-01-01");
|
|
103
|
+
|
|
104
|
+
mock.module("../platform/consent-cache.js", () => ({
|
|
105
|
+
getCachedShareAnalytics: mockGetCachedShareAnalytics,
|
|
106
|
+
getCachedShareDiagnostics: mockGetCachedShareDiagnostics,
|
|
107
|
+
getCachedShareDiagnosticsVersion: mockGetCachedShareDiagnosticsVersion,
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
// The `trace-collection` feature flag — the other half of the trace gate.
|
|
111
|
+
const mockIsAssistantFeatureFlagEnabled = mock(
|
|
112
|
+
(_key: string, _config: unknown): boolean => true,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
mock.module("../config/assistant-feature-flags.js", () => ({
|
|
116
|
+
isAssistantFeatureFlagEnabled: mockIsAssistantFeatureFlagEnabled,
|
|
117
|
+
}));
|
|
95
118
|
|
|
119
|
+
// Stub config — the flag checker is mocked, so the contents don't matter; this
|
|
120
|
+
// just keeps `getConfig()` from touching the real loader / filesystem.
|
|
96
121
|
mock.module("../config/loader.js", () => ({
|
|
97
|
-
getConfig: () => ({
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
122
|
+
getConfig: () => ({}),
|
|
123
|
+
}));
|
|
124
|
+
|
|
125
|
+
interface MockTurnTrace {
|
|
126
|
+
schema_version: 1;
|
|
127
|
+
messages: {
|
|
128
|
+
id: string;
|
|
129
|
+
role: string;
|
|
130
|
+
created_at: number;
|
|
131
|
+
content: unknown;
|
|
132
|
+
}[];
|
|
133
|
+
tool_calls: unknown[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Returns a non-null bounded trace by default; individual tests override the
|
|
137
|
+
// return value (including null, for the over-cap / assembly-failure path).
|
|
138
|
+
function defaultBoundedTurnTrace(boundary: {
|
|
139
|
+
conversationId: string;
|
|
140
|
+
userMessageId: string;
|
|
141
|
+
userMessageCreatedAt: number;
|
|
142
|
+
}): MockTurnTrace | null {
|
|
143
|
+
return {
|
|
144
|
+
schema_version: 1,
|
|
145
|
+
messages: [
|
|
146
|
+
{
|
|
147
|
+
id: boundary.userMessageId,
|
|
148
|
+
role: "user",
|
|
149
|
+
created_at: boundary.userMessageCreatedAt,
|
|
150
|
+
content: [{ type: "text", text: "hello" }],
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
tool_calls: [],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const mockAssembleBoundedTurnTrace = mock(defaultBoundedTurnTrace);
|
|
158
|
+
|
|
159
|
+
// Turn-completeness gate. Default: every turn is settled (complete), so the
|
|
160
|
+
// deferral barrier never fires unless a test opts a turn into "in-flight".
|
|
161
|
+
const mockIsTurnSettled = mock(
|
|
162
|
+
(_boundary: {
|
|
163
|
+
conversationId: string;
|
|
164
|
+
userMessageId: string;
|
|
165
|
+
userMessageCreatedAt: number;
|
|
166
|
+
}): boolean => true,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
mock.module("../memory/turn-trace-store.js", () => ({
|
|
170
|
+
assembleBoundedTurnTrace: mockAssembleBoundedTurnTrace,
|
|
171
|
+
isTurnSettled: mockIsTurnSettled,
|
|
106
172
|
}));
|
|
107
173
|
|
|
108
174
|
const mockQueryUnreportedLifecycleEvents = mock(
|
|
@@ -290,7 +356,25 @@ let mockFetch: ReturnType<typeof mock>;
|
|
|
290
356
|
|
|
291
357
|
beforeEach(() => {
|
|
292
358
|
eventIdCounter = 0;
|
|
293
|
-
|
|
359
|
+
// Default consent ON so the happy-path send tests exercise the flush.
|
|
360
|
+
mockGetCachedShareAnalytics.mockReset();
|
|
361
|
+
mockGetCachedShareAnalytics.mockReturnValue(true);
|
|
362
|
+
// Default `share_diagnostics` consent OFF — most tests don't expect a trace;
|
|
363
|
+
// the trace-specific tests opt in explicitly.
|
|
364
|
+
mockGetCachedShareDiagnostics.mockReset();
|
|
365
|
+
mockGetCachedShareDiagnostics.mockReturnValue(false);
|
|
366
|
+
// Default the accepted consent version eligible so trace tests drive the gate
|
|
367
|
+
// via the flag + share_diagnostics knobs; version cases override it.
|
|
368
|
+
mockGetCachedShareDiagnosticsVersion.mockReset();
|
|
369
|
+
mockGetCachedShareDiagnosticsVersion.mockReturnValue("2999-01-01");
|
|
370
|
+
// Default the `trace-collection` flag ON so trace tests can drive eligibility
|
|
371
|
+
// via the consent knob alone; the flag-gating test flips it explicitly.
|
|
372
|
+
mockIsAssistantFeatureFlagEnabled.mockReset();
|
|
373
|
+
mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
|
|
374
|
+
mockAssembleBoundedTurnTrace.mockReset();
|
|
375
|
+
mockAssembleBoundedTurnTrace.mockImplementation(defaultBoundedTurnTrace);
|
|
376
|
+
mockIsTurnSettled.mockReset();
|
|
377
|
+
mockIsTurnSettled.mockReturnValue(true);
|
|
294
378
|
mockGetMemoryCheckpoint.mockReset();
|
|
295
379
|
mockSetMemoryCheckpoint.mockReset();
|
|
296
380
|
mockQueryUnreportedUsageEvents.mockReset();
|
|
@@ -303,7 +387,8 @@ beforeEach(() => {
|
|
|
303
387
|
getDb().delete(authFallbackEvents).run();
|
|
304
388
|
getDb().delete(toolInvocations).run();
|
|
305
389
|
getDb().delete(skillLoadedEvents).run();
|
|
306
|
-
|
|
390
|
+
delete process.env.VELLUM_DISABLE_PLATFORM;
|
|
391
|
+
delete process.env.IS_PLATFORM;
|
|
307
392
|
mockGetPlatformBaseUrl.mockReset();
|
|
308
393
|
mockGetDeviceId.mockReset();
|
|
309
394
|
mockGetDeviceId.mockReturnValue("test-device-id");
|
|
@@ -320,10 +405,23 @@ beforeEach(() => {
|
|
|
320
405
|
Promise.resolve(new Response('{"accepted":0}', { status: 200 })),
|
|
321
406
|
);
|
|
322
407
|
globalThis.fetch = mockFetch as unknown as typeof fetch;
|
|
408
|
+
|
|
409
|
+
// Default to an authenticated client whose fetch delegates to the mockFetch
|
|
410
|
+
// spy, so the existing payload/body assertions keep working. The reporter
|
|
411
|
+
// sends authenticated-only; tests that exercise the no-credentials path
|
|
412
|
+
// override this with `mockPlatformClient = null`.
|
|
413
|
+
mockPlatformClient = {
|
|
414
|
+
baseUrl: "https://test.vellum.ai",
|
|
415
|
+
assistantApiKey: "test-key",
|
|
416
|
+
platformAssistantId: "asst-123",
|
|
417
|
+
fetch: (path: string, init?: RequestInit) => mockFetch(path, init),
|
|
418
|
+
};
|
|
323
419
|
});
|
|
324
420
|
|
|
325
421
|
afterEach(() => {
|
|
326
422
|
globalThis.fetch = originalFetch;
|
|
423
|
+
delete process.env.VELLUM_DISABLE_PLATFORM;
|
|
424
|
+
delete process.env.IS_PLATFORM;
|
|
327
425
|
});
|
|
328
426
|
|
|
329
427
|
// ---------------------------------------------------------------------------
|
|
@@ -354,26 +452,57 @@ describe("UsageTelemetryReporter", () => {
|
|
|
354
452
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
355
453
|
});
|
|
356
454
|
|
|
357
|
-
test("
|
|
455
|
+
test("flush is skipped when no platform credentials are available", async () => {
|
|
456
|
+
// Authenticated-only: with no client, nothing is sent and the watermark is
|
|
457
|
+
// left intact so the backlog ships once credentials resolve.
|
|
358
458
|
mockPlatformClient = null;
|
|
359
|
-
mockGetPlatformBaseUrl.mockReturnValue("https://platform.test.ai");
|
|
360
459
|
|
|
361
460
|
const events = [makeUsageEvent()];
|
|
362
461
|
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
363
|
-
|
|
364
|
-
|
|
462
|
+
|
|
463
|
+
const reporter = new UsageTelemetryReporter();
|
|
464
|
+
await reporter.flush();
|
|
465
|
+
|
|
466
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
467
|
+
const watermarkCalls = mockSetMemoryCheckpoint.mock.calls.filter(
|
|
468
|
+
(c) => c[0] === "telemetry:usage:last_reported_at",
|
|
365
469
|
);
|
|
470
|
+
expect(watermarkCalls.length).toBe(0);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("flush is skipped when VELLUM_DISABLE_PLATFORM is set in local mode", async () => {
|
|
474
|
+
// The platform-disabled toggle suppresses the send. Unlike the opt-out
|
|
475
|
+
// branch, watermarks are NOT advanced, so the backlog ships once the flag
|
|
476
|
+
// is cleared.
|
|
477
|
+
process.env.VELLUM_DISABLE_PLATFORM = "true";
|
|
478
|
+
|
|
479
|
+
const events = [makeUsageEvent()];
|
|
480
|
+
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
481
|
+
|
|
482
|
+
const reporter = new UsageTelemetryReporter();
|
|
483
|
+
// Construction initializes the absent tool_executed watermark; clear that
|
|
484
|
+
// call so the assertion below covers only the flush.
|
|
485
|
+
mockSetMemoryCheckpoint.mockClear();
|
|
486
|
+
await reporter.flush();
|
|
487
|
+
|
|
488
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
489
|
+
expect(mockSetMemoryCheckpoint).not.toHaveBeenCalled();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("VELLUM_DISABLE_PLATFORM is ignored when IS_PLATFORM is set (managed mode)", async () => {
|
|
493
|
+
// Platform-managed assistants always connect to the platform; an inherited
|
|
494
|
+
// VELLUM_DISABLE_PLATFORM must not suppress telemetry for them (matches
|
|
495
|
+
// arePlatformFeaturesEnabled / VellumPlatformClient.create()).
|
|
496
|
+
process.env.IS_PLATFORM = "true";
|
|
497
|
+
process.env.VELLUM_DISABLE_PLATFORM = "true";
|
|
498
|
+
|
|
499
|
+
const events = [makeUsageEvent()];
|
|
500
|
+
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
366
501
|
|
|
367
502
|
const reporter = new UsageTelemetryReporter();
|
|
368
503
|
await reporter.flush();
|
|
369
504
|
|
|
370
505
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
371
|
-
const [url, opts] = mockFetch.mock.calls[0] as [string, RequestInit];
|
|
372
|
-
expect(url).toStartWith("https://platform.test.ai");
|
|
373
|
-
expect(url).toEndWith("/telemetry/ingest/");
|
|
374
|
-
const headers = opts.headers as Record<string, string>;
|
|
375
|
-
expect(headers["Content-Type"]).toBe("application/json");
|
|
376
|
-
expect(headers["X-Telemetry-Token"]).toBeUndefined();
|
|
377
506
|
});
|
|
378
507
|
|
|
379
508
|
test("watermark advances on successful upload", async () => {
|
|
@@ -921,6 +1050,406 @@ describe("UsageTelemetryReporter", () => {
|
|
|
921
1050
|
});
|
|
922
1051
|
});
|
|
923
1052
|
|
|
1053
|
+
// -------------------------------------------------------------------------
|
|
1054
|
+
// Per-turn trace collection (gated on the trace-collection flag AND
|
|
1055
|
+
// share_diagnostics consent)
|
|
1056
|
+
// -------------------------------------------------------------------------
|
|
1057
|
+
|
|
1058
|
+
function singleTurnEvent() {
|
|
1059
|
+
return [
|
|
1060
|
+
{
|
|
1061
|
+
id: "evt-turn-trace",
|
|
1062
|
+
createdAt: 1700000050000,
|
|
1063
|
+
conversationId: "conv-trace",
|
|
1064
|
+
conversationType: "standard",
|
|
1065
|
+
turnIndex: 2,
|
|
1066
|
+
interfaceId: "macos",
|
|
1067
|
+
channelId: "vellum",
|
|
1068
|
+
clientMetadata: null,
|
|
1069
|
+
},
|
|
1070
|
+
];
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
test("attaches the assembled trace when the trace-collection flag, share_diagnostics, and an eligible consent version are all true", async () => {
|
|
1074
|
+
mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
|
|
1075
|
+
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1076
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1077
|
+
mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
|
|
1078
|
+
mockFetch.mockImplementation(() =>
|
|
1079
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1080
|
+
);
|
|
1081
|
+
|
|
1082
|
+
const reporter = new UsageTelemetryReporter();
|
|
1083
|
+
await reporter.flush();
|
|
1084
|
+
|
|
1085
|
+
// The gate consults the `trace-collection` flag.
|
|
1086
|
+
expect(mockIsAssistantFeatureFlagEnabled).toHaveBeenCalledWith(
|
|
1087
|
+
"trace-collection",
|
|
1088
|
+
expect.anything(),
|
|
1089
|
+
);
|
|
1090
|
+
|
|
1091
|
+
// The assembler is called with the turn's (conversationId, id, createdAt)
|
|
1092
|
+
// boundary so the window lines up with the turn event.
|
|
1093
|
+
expect(mockAssembleBoundedTurnTrace).toHaveBeenCalledTimes(1);
|
|
1094
|
+
expect(mockAssembleBoundedTurnTrace.mock.calls[0][0]).toEqual({
|
|
1095
|
+
conversationId: "conv-trace",
|
|
1096
|
+
userMessageId: "evt-turn-trace",
|
|
1097
|
+
userMessageCreatedAt: 1700000050000,
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
1101
|
+
const body = JSON.parse(
|
|
1102
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
1103
|
+
);
|
|
1104
|
+
// Still exactly one event — the single turn event, now carrying `trace`.
|
|
1105
|
+
expect(body.events.length).toBe(1);
|
|
1106
|
+
const turn = body.events[0];
|
|
1107
|
+
expect(turn.type).toBe("turn");
|
|
1108
|
+
expect(turn.daemon_event_id).toBe("evt-turn-trace");
|
|
1109
|
+
expect(turn.trace).toBeDefined();
|
|
1110
|
+
expect(turn.trace.schema_version).toBe(1);
|
|
1111
|
+
expect(turn.trace.messages[0].id).toBe("evt-turn-trace");
|
|
1112
|
+
expect(Array.isArray(turn.trace.tool_calls)).toBe(true);
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
test("omits the trace when share_diagnostics is false even though the flag is on (and still emits the turn event)", async () => {
|
|
1116
|
+
mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
|
|
1117
|
+
mockGetCachedShareDiagnostics.mockReturnValue(false);
|
|
1118
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1119
|
+
mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
|
|
1120
|
+
mockFetch.mockImplementation(() =>
|
|
1121
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1122
|
+
);
|
|
1123
|
+
|
|
1124
|
+
const reporter = new UsageTelemetryReporter();
|
|
1125
|
+
await reporter.flush();
|
|
1126
|
+
|
|
1127
|
+
// Gate off → no assembly at all (no PII touched).
|
|
1128
|
+
expect(mockAssembleBoundedTurnTrace).not.toHaveBeenCalled();
|
|
1129
|
+
|
|
1130
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
1131
|
+
const body = JSON.parse(
|
|
1132
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
1133
|
+
);
|
|
1134
|
+
// The single turn event still flushes — just without a trace.
|
|
1135
|
+
expect(body.events.length).toBe(1);
|
|
1136
|
+
const turn = body.events[0];
|
|
1137
|
+
expect(turn.type).toBe("turn");
|
|
1138
|
+
expect(turn.daemon_event_id).toBe("evt-turn-trace");
|
|
1139
|
+
expect("trace" in turn).toBe(false);
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
test("omits the trace when the accepted consent version predates the disclosure threshold (flag + share_diagnostics on)", async () => {
|
|
1143
|
+
mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
|
|
1144
|
+
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1145
|
+
// Consent recorded under an older version that never disclosed trace
|
|
1146
|
+
// collection → gate closed, mirroring the platform ingest gate.
|
|
1147
|
+
mockGetCachedShareDiagnosticsVersion.mockReturnValue("2000-01-01");
|
|
1148
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1149
|
+
mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
|
|
1150
|
+
mockFetch.mockImplementation(() =>
|
|
1151
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
const reporter = new UsageTelemetryReporter();
|
|
1155
|
+
await reporter.flush();
|
|
1156
|
+
|
|
1157
|
+
// Version below threshold → no assembly at all (no PII touched).
|
|
1158
|
+
expect(mockAssembleBoundedTurnTrace).not.toHaveBeenCalled();
|
|
1159
|
+
|
|
1160
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
1161
|
+
const body = JSON.parse(
|
|
1162
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
1163
|
+
);
|
|
1164
|
+
expect(body.events.length).toBe(1);
|
|
1165
|
+
expect("trace" in body.events[0]).toBe(false);
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
test("omits the trace when the owner never accepted a versioned consent (empty version)", async () => {
|
|
1169
|
+
mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
|
|
1170
|
+
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1171
|
+
// Empty version (never accepted / no-row default where share_diagnostics is
|
|
1172
|
+
// true but unversioned) fails closed.
|
|
1173
|
+
mockGetCachedShareDiagnosticsVersion.mockReturnValue("");
|
|
1174
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1175
|
+
mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
|
|
1176
|
+
mockFetch.mockImplementation(() =>
|
|
1177
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1178
|
+
);
|
|
1179
|
+
|
|
1180
|
+
const reporter = new UsageTelemetryReporter();
|
|
1181
|
+
await reporter.flush();
|
|
1182
|
+
|
|
1183
|
+
expect(mockAssembleBoundedTurnTrace).not.toHaveBeenCalled();
|
|
1184
|
+
const body = JSON.parse(
|
|
1185
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
1186
|
+
);
|
|
1187
|
+
expect(body.events.length).toBe(1);
|
|
1188
|
+
expect("trace" in body.events[0]).toBe(false);
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
test("omits the trace when the trace-collection flag is off even though share_diagnostics is true", async () => {
|
|
1192
|
+
mockIsAssistantFeatureFlagEnabled.mockReturnValue(false);
|
|
1193
|
+
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1194
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1195
|
+
mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
|
|
1196
|
+
mockFetch.mockImplementation(() =>
|
|
1197
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1198
|
+
);
|
|
1199
|
+
|
|
1200
|
+
const reporter = new UsageTelemetryReporter();
|
|
1201
|
+
await reporter.flush();
|
|
1202
|
+
|
|
1203
|
+
// Flag off → gate closed → no assembly at all (no PII touched).
|
|
1204
|
+
expect(mockAssembleBoundedTurnTrace).not.toHaveBeenCalled();
|
|
1205
|
+
|
|
1206
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
1207
|
+
const body = JSON.parse(
|
|
1208
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
1209
|
+
);
|
|
1210
|
+
// The single turn event still flushes — just without a trace.
|
|
1211
|
+
expect(body.events.length).toBe(1);
|
|
1212
|
+
expect("trace" in body.events[0]).toBe(false);
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
test("omits the trace (key absent) when the assembler returns null (over-cap / failure) but still emits the turn event", async () => {
|
|
1216
|
+
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1217
|
+
// Over-cap / assembly-failure path: the bounded assembler returns null.
|
|
1218
|
+
mockAssembleBoundedTurnTrace.mockReturnValueOnce(null);
|
|
1219
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1220
|
+
mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
|
|
1221
|
+
mockFetch.mockImplementation(() =>
|
|
1222
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1223
|
+
);
|
|
1224
|
+
|
|
1225
|
+
const reporter = new UsageTelemetryReporter();
|
|
1226
|
+
await reporter.flush();
|
|
1227
|
+
|
|
1228
|
+
expect(mockAssembleBoundedTurnTrace).toHaveBeenCalledTimes(1);
|
|
1229
|
+
const body = JSON.parse(
|
|
1230
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
1231
|
+
);
|
|
1232
|
+
expect(body.events.length).toBe(1);
|
|
1233
|
+
expect("trace" in body.events[0]).toBe(false);
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
test("no trace is assembled or attached when the whole flush is gated off by share_analytics", async () => {
|
|
1237
|
+
// The analytics gate short-circuits the entire flush; trace assembly must
|
|
1238
|
+
// never run (and nothing is sent) even when the trace gate is fully on
|
|
1239
|
+
// (flag + share_diagnostics both true).
|
|
1240
|
+
mockGetCachedShareAnalytics.mockReturnValue(false);
|
|
1241
|
+
mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
|
|
1242
|
+
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1243
|
+
mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
|
|
1244
|
+
|
|
1245
|
+
const reporter = new UsageTelemetryReporter();
|
|
1246
|
+
await reporter.flush();
|
|
1247
|
+
|
|
1248
|
+
expect(mockAssembleBoundedTurnTrace).not.toHaveBeenCalled();
|
|
1249
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
// -------------------------------------------------------------------------
|
|
1253
|
+
// Trace completeness barrier — don't emit partial traces mid-turn
|
|
1254
|
+
// -------------------------------------------------------------------------
|
|
1255
|
+
|
|
1256
|
+
function turnEvent(id: string, createdAt: number, conversationId: string) {
|
|
1257
|
+
return {
|
|
1258
|
+
id,
|
|
1259
|
+
createdAt,
|
|
1260
|
+
conversationId,
|
|
1261
|
+
conversationType: "standard",
|
|
1262
|
+
turnIndex: 1,
|
|
1263
|
+
interfaceId: "macos",
|
|
1264
|
+
channelId: "vellum",
|
|
1265
|
+
clientMetadata: null,
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
test("a flush during an in-progress turn defers it (no partial trace, watermark held)", async () => {
|
|
1270
|
+
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1271
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1272
|
+
const checkpoints = useStatefulCheckpoints();
|
|
1273
|
+
mockQueryUnreportedTurnEvents.mockReturnValue([
|
|
1274
|
+
turnEvent("evt-inflight", 1700000050000, "conv-1"),
|
|
1275
|
+
]);
|
|
1276
|
+
// The turn's response is still streaming — not settled.
|
|
1277
|
+
mockIsTurnSettled.mockReturnValue(false);
|
|
1278
|
+
|
|
1279
|
+
const reporter = new UsageTelemetryReporter();
|
|
1280
|
+
await reporter.flush();
|
|
1281
|
+
|
|
1282
|
+
// No trace assembled, and the turn is NOT sent (the only event was deferred).
|
|
1283
|
+
expect(mockAssembleBoundedTurnTrace).not.toHaveBeenCalled();
|
|
1284
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
1285
|
+
// The turn watermark must NOT advance past the deferred turn.
|
|
1286
|
+
expect(checkpoints.get("telemetry:turns:last_reported_at")).toBeUndefined();
|
|
1287
|
+
expect(checkpoints.get("telemetry:turns:last_reported_id")).toBeUndefined();
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
test("a later flush emits the COMPLETE trace once the turn settles", async () => {
|
|
1291
|
+
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1292
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1293
|
+
const checkpoints = useStatefulCheckpoints();
|
|
1294
|
+
mockQueryUnreportedTurnEvents.mockReturnValue([
|
|
1295
|
+
turnEvent("evt-inflight", 1700000050000, "conv-1"),
|
|
1296
|
+
]);
|
|
1297
|
+
|
|
1298
|
+
// Flush 1: turn in progress -> deferred, nothing sent.
|
|
1299
|
+
mockIsTurnSettled.mockReturnValue(false);
|
|
1300
|
+
const reporter = new UsageTelemetryReporter();
|
|
1301
|
+
await reporter.flush();
|
|
1302
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
1303
|
+
|
|
1304
|
+
// Flush 2: the response + tool results have landed; the turn is settled and
|
|
1305
|
+
// the full trace assembles. The same (still-unreported) turn now ships.
|
|
1306
|
+
mockIsTurnSettled.mockReturnValue(true);
|
|
1307
|
+
mockAssembleBoundedTurnTrace.mockReturnValue({
|
|
1308
|
+
schema_version: 1,
|
|
1309
|
+
messages: [
|
|
1310
|
+
{
|
|
1311
|
+
id: "evt-inflight",
|
|
1312
|
+
role: "user",
|
|
1313
|
+
created_at: 1700000050000,
|
|
1314
|
+
content: [{ type: "text", text: "do a thing" }],
|
|
1315
|
+
},
|
|
1316
|
+
{
|
|
1317
|
+
id: "asst-1",
|
|
1318
|
+
role: "assistant",
|
|
1319
|
+
created_at: 1700000051000,
|
|
1320
|
+
content: [{ type: "text", text: "done" }],
|
|
1321
|
+
},
|
|
1322
|
+
],
|
|
1323
|
+
tool_calls: [{ id: "ti-1" }],
|
|
1324
|
+
});
|
|
1325
|
+
await reporter.flush();
|
|
1326
|
+
|
|
1327
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
1328
|
+
const body = JSON.parse(
|
|
1329
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
1330
|
+
);
|
|
1331
|
+
expect(body.events.length).toBe(1);
|
|
1332
|
+
const turn = body.events[0];
|
|
1333
|
+
expect(turn.daemon_event_id).toBe("evt-inflight");
|
|
1334
|
+
// The COMPLETE transcript: user message + assistant response + tool call.
|
|
1335
|
+
expect(turn.trace.messages.map((m: { id: string }) => m.id)).toEqual([
|
|
1336
|
+
"evt-inflight",
|
|
1337
|
+
"asst-1",
|
|
1338
|
+
]);
|
|
1339
|
+
expect(turn.trace.tool_calls).toHaveLength(1);
|
|
1340
|
+
// Watermark now advances to the (now-complete) turn.
|
|
1341
|
+
expect(checkpoints.get("telemetry:turns:last_reported_at")).toBe(
|
|
1342
|
+
String(1700000050000),
|
|
1343
|
+
);
|
|
1344
|
+
expect(checkpoints.get("telemetry:turns:last_reported_id")).toBe(
|
|
1345
|
+
"evt-inflight",
|
|
1346
|
+
);
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
test("the final turn of a conversation still gets its complete trace once its own response finishes", async () => {
|
|
1350
|
+
// No successor turn exists; `isTurnSettled` returns true purely because the
|
|
1351
|
+
// conversation is no longer processing. The trace must still be sent (not
|
|
1352
|
+
// deferred forever for lack of a next user turn).
|
|
1353
|
+
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1354
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1355
|
+
mockQueryUnreportedTurnEvents.mockReturnValue([
|
|
1356
|
+
turnEvent("evt-final", 1700000060000, "conv-final"),
|
|
1357
|
+
]);
|
|
1358
|
+
mockIsTurnSettled.mockReturnValue(true);
|
|
1359
|
+
mockFetch.mockImplementation(() =>
|
|
1360
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1361
|
+
);
|
|
1362
|
+
|
|
1363
|
+
const reporter = new UsageTelemetryReporter();
|
|
1364
|
+
await reporter.flush();
|
|
1365
|
+
|
|
1366
|
+
expect(mockAssembleBoundedTurnTrace).toHaveBeenCalledTimes(1);
|
|
1367
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
1368
|
+
const body = JSON.parse(
|
|
1369
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
1370
|
+
);
|
|
1371
|
+
expect(body.events.length).toBe(1);
|
|
1372
|
+
expect(body.events[0].daemon_event_id).toBe("evt-final");
|
|
1373
|
+
expect(body.events[0].trace).toBeDefined();
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
test("barrier: a complete turn ordered AFTER an in-flight turn is also deferred (no watermark skip)", async () => {
|
|
1377
|
+
// The turn watermark is a single monotonic cursor, so a complete turn that
|
|
1378
|
+
// sorts after a deferred in-flight turn cannot be reported without skipping
|
|
1379
|
+
// the deferred one. Both wait; the earlier complete turn (before the
|
|
1380
|
+
// barrier) is reported.
|
|
1381
|
+
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1382
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1383
|
+
const checkpoints = useStatefulCheckpoints();
|
|
1384
|
+
mockQueryUnreportedTurnEvents.mockReturnValue([
|
|
1385
|
+
turnEvent("evt-a-complete", 1700000010000, "conv-a"),
|
|
1386
|
+
turnEvent("evt-b-inflight", 1700000020000, "conv-b"),
|
|
1387
|
+
turnEvent("evt-c-complete", 1700000030000, "conv-c"),
|
|
1388
|
+
]);
|
|
1389
|
+
// Only the middle turn is in-flight.
|
|
1390
|
+
mockIsTurnSettled.mockImplementation(
|
|
1391
|
+
(b) => b.userMessageId !== "evt-b-inflight",
|
|
1392
|
+
);
|
|
1393
|
+
mockFetch.mockImplementation(() =>
|
|
1394
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1395
|
+
);
|
|
1396
|
+
|
|
1397
|
+
const reporter = new UsageTelemetryReporter();
|
|
1398
|
+
await reporter.flush();
|
|
1399
|
+
|
|
1400
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
1401
|
+
const body = JSON.parse(
|
|
1402
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
1403
|
+
);
|
|
1404
|
+
// Only the turn BEFORE the in-flight barrier is reported.
|
|
1405
|
+
expect(
|
|
1406
|
+
body.events.map((e: { daemon_event_id: string }) => e.daemon_event_id),
|
|
1407
|
+
).toEqual(["evt-a-complete"]);
|
|
1408
|
+
// Watermark stops at the last reported turn, NOT the later complete turn —
|
|
1409
|
+
// so the deferred middle turn is never skipped.
|
|
1410
|
+
expect(checkpoints.get("telemetry:turns:last_reported_at")).toBe(
|
|
1411
|
+
String(1700000010000),
|
|
1412
|
+
);
|
|
1413
|
+
expect(checkpoints.get("telemetry:turns:last_reported_id")).toBe(
|
|
1414
|
+
"evt-a-complete",
|
|
1415
|
+
);
|
|
1416
|
+
});
|
|
1417
|
+
|
|
1418
|
+
test("with tracing disabled, an in-progress turn is NOT deferred (reporting timing unchanged)", async () => {
|
|
1419
|
+
// The completeness barrier is trace-eligible-only. With trace collection
|
|
1420
|
+
// off, turn telemetry / turn_raw behavior is unchanged: the turn ships
|
|
1421
|
+
// immediately and `isTurnSettled` is never consulted.
|
|
1422
|
+
mockGetCachedShareDiagnostics.mockReturnValue(false);
|
|
1423
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1424
|
+
const checkpoints = useStatefulCheckpoints();
|
|
1425
|
+
mockQueryUnreportedTurnEvents.mockReturnValue([
|
|
1426
|
+
turnEvent("evt-inflight", 1700000050000, "conv-1"),
|
|
1427
|
+
]);
|
|
1428
|
+
// Even though the turn would be "in-flight", the gate is off so this is
|
|
1429
|
+
// never consulted.
|
|
1430
|
+
mockIsTurnSettled.mockReturnValue(false);
|
|
1431
|
+
mockFetch.mockImplementation(() =>
|
|
1432
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1433
|
+
);
|
|
1434
|
+
|
|
1435
|
+
const reporter = new UsageTelemetryReporter();
|
|
1436
|
+
await reporter.flush();
|
|
1437
|
+
|
|
1438
|
+
expect(mockIsTurnSettled).not.toHaveBeenCalled();
|
|
1439
|
+
// The turn ships immediately, trace-free.
|
|
1440
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
1441
|
+
const body = JSON.parse(
|
|
1442
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
1443
|
+
);
|
|
1444
|
+
expect(body.events.length).toBe(1);
|
|
1445
|
+
expect(body.events[0].daemon_event_id).toBe("evt-inflight");
|
|
1446
|
+
expect("trace" in body.events[0]).toBe(false);
|
|
1447
|
+
// Watermark advances as usual.
|
|
1448
|
+
expect(checkpoints.get("telemetry:turns:last_reported_at")).toBe(
|
|
1449
|
+
String(1700000050000),
|
|
1450
|
+
);
|
|
1451
|
+
});
|
|
1452
|
+
|
|
924
1453
|
test("llm_usage events carry conversation_id, conversation_type, and turn_index", async () => {
|
|
925
1454
|
// Three LLM calls across the spectrum of the new fields:
|
|
926
1455
|
// - tied to a conversation, mid-turn (typical foreground)
|
|
@@ -1001,8 +1530,8 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1001
1530
|
});
|
|
1002
1531
|
});
|
|
1003
1532
|
|
|
1004
|
-
test("flush is skipped and watermarks advanced when
|
|
1005
|
-
|
|
1533
|
+
test("flush is skipped and watermarks advanced when share_analytics consent is off", async () => {
|
|
1534
|
+
mockGetCachedShareAnalytics.mockReturnValue(false);
|
|
1006
1535
|
const events = [makeUsageEvent()];
|
|
1007
1536
|
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
1008
1537
|
mockFetch.mockImplementation(() =>
|
|
@@ -1044,16 +1573,37 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1044
1573
|
}
|
|
1045
1574
|
});
|
|
1046
1575
|
|
|
1047
|
-
test("
|
|
1576
|
+
test("platform disabled takes precedence over consent off — watermarks NOT advanced", async () => {
|
|
1577
|
+
// VELLUM_DISABLE_PLATFORM keeps the consent cache false (the consent
|
|
1578
|
+
// refresh can't create a platform client), so both gates would fire. The
|
|
1579
|
+
// platform-disabled gate runs first and returns without advancing
|
|
1580
|
+
// watermarks, preserving the backlog until the flag is cleared — a
|
|
1581
|
+
// deployment toggle must not be treated as a privacy opt-out.
|
|
1582
|
+
process.env.VELLUM_DISABLE_PLATFORM = "true";
|
|
1583
|
+
mockGetCachedShareAnalytics.mockReturnValue(false);
|
|
1584
|
+
const events = [makeUsageEvent()];
|
|
1585
|
+
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
1586
|
+
|
|
1587
|
+
const reporter = new UsageTelemetryReporter();
|
|
1588
|
+
// Construction initializes the absent tool_executed watermark; clear that
|
|
1589
|
+
// call so the assertion below covers only the flush.
|
|
1590
|
+
mockSetMemoryCheckpoint.mockClear();
|
|
1591
|
+
await reporter.flush();
|
|
1592
|
+
|
|
1593
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
1594
|
+
expect(mockSetMemoryCheckpoint).not.toHaveBeenCalled();
|
|
1595
|
+
});
|
|
1596
|
+
|
|
1597
|
+
test("events sent normally after re-granting share_analytics consent", async () => {
|
|
1048
1598
|
// First flush with opt-out — watermarks advance, nothing sent
|
|
1049
|
-
|
|
1599
|
+
mockGetCachedShareAnalytics.mockReturnValue(false);
|
|
1050
1600
|
const reporter = new UsageTelemetryReporter();
|
|
1051
1601
|
await reporter.flush();
|
|
1052
1602
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
1053
1603
|
mockSetMemoryCheckpoint.mockReset();
|
|
1054
1604
|
|
|
1055
|
-
// Re-
|
|
1056
|
-
|
|
1605
|
+
// Re-grant consent and flush with new events
|
|
1606
|
+
mockGetCachedShareAnalytics.mockReturnValue(true);
|
|
1057
1607
|
const events = [makeUsageEvent({ id: "evt-after-reenable" })];
|
|
1058
1608
|
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
1059
1609
|
mockFetch.mockImplementation(() =>
|
|
@@ -1587,8 +2137,8 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1587
2137
|
|
|
1588
2138
|
// Rows accumulated before any flush ever advanced the watermark — e.g.
|
|
1589
2139
|
// an opt-out period under an older build that gated reporter
|
|
1590
|
-
// construction on
|
|
1591
|
-
// kept writing.
|
|
2140
|
+
// construction on the usage-data opt-out while the always-on audit
|
|
2141
|
+
// listener kept writing.
|
|
1592
2142
|
seedToolInvocation({
|
|
1593
2143
|
id: "ti-opt-out-window",
|
|
1594
2144
|
createdAt: Date.now() - 60_000,
|
|
@@ -1667,7 +2217,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1667
2217
|
// constructs and runs the reporter; the always-on audit listener keeps
|
|
1668
2218
|
// writing rows. Every opted-out flush (5-minute cycle plus the final
|
|
1669
2219
|
// flush in stop()) advances the watermark past them without sending.
|
|
1670
|
-
|
|
2220
|
+
mockGetCachedShareAnalytics.mockReturnValue(false);
|
|
1671
2221
|
const optOutRowCreatedAt = Date.now() - 5_000;
|
|
1672
2222
|
seedToolInvocation({
|
|
1673
2223
|
id: "ti-opt-out-window",
|
|
@@ -1683,7 +2233,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1683
2233
|
|
|
1684
2234
|
// Session 2: the user opts back in and restarts. Only rows recorded
|
|
1685
2235
|
// after the opt-out epoch ship — the opt-out-window row never does.
|
|
1686
|
-
|
|
2236
|
+
mockGetCachedShareAnalytics.mockReturnValue(true);
|
|
1687
2237
|
seedToolInvocation({ id: "ti-after-opt-in", createdAt: advanced + 1000 });
|
|
1688
2238
|
const reporter = new UsageTelemetryReporter();
|
|
1689
2239
|
await reporter.flush();
|
|
@@ -1708,7 +2258,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1708
2258
|
|
|
1709
2259
|
// Opted-out flush: advances the timestamp watermark to Date.now() and
|
|
1710
2260
|
// must also pin the ID watermark to the high-sorting sentinel.
|
|
1711
|
-
|
|
2261
|
+
mockGetCachedShareAnalytics.mockReturnValue(false);
|
|
1712
2262
|
const optedOutReporter = new UsageTelemetryReporter();
|
|
1713
2263
|
await optedOutReporter.flush();
|
|
1714
2264
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
@@ -1726,7 +2276,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1726
2276
|
});
|
|
1727
2277
|
|
|
1728
2278
|
// Re-opt-in: only rows strictly after the opt-out epoch ship.
|
|
1729
|
-
|
|
2279
|
+
mockGetCachedShareAnalytics.mockReturnValue(true);
|
|
1730
2280
|
seedToolInvocation({ id: "ti-after-opt-in", createdAt: watermark + 1000 });
|
|
1731
2281
|
const reporter = new UsageTelemetryReporter();
|
|
1732
2282
|
await reporter.flush();
|