@vellumai/assistant 0.10.8 → 0.10.9-dev.202607152225.ec6fdbf
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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +52 -88
- package/README.md +14 -35
- package/docs/activation-funnel-telemetry.md +20 -18
- package/docs/architecture/scheduling.md +0 -86
- package/docs/runbook-trusted-contacts.md +19 -19
- package/docs/trusted-contact-access.md +70 -69
- package/docs/workspace-tools.md +4 -2
- package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +62 -360
- package/eslint-rules/cli-no-daemon-internals.js +91 -275
- package/eslint.config.mjs +4 -4
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +575 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +687 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +82 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/openapi.yaml +353 -742
- package/package.json +2 -2
- package/scripts/build-test-fixtures.ts +3 -2
- package/scripts/test.ts +2 -7
- package/src/__tests__/activation-early-marking.test.ts +1 -8
- package/src/__tests__/actor-token-service.test.ts +0 -7
- package/src/__tests__/add-message-preassigned-id.test.ts +1 -1
- package/src/__tests__/add-message-skip-indexing.test.ts +164 -0
- package/src/__tests__/agent-image-optimize.test.ts +104 -1
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +5 -15
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +0 -6
- package/src/__tests__/agent-loop-override-profile.test.ts +48 -50
- package/src/__tests__/agent-loop-pushes-post-hook-prompt.test.ts +4 -17
- package/src/__tests__/agent-loop-set-system-prompt.test.ts +3 -14
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -4
- package/src/__tests__/agent-wake-override-profile.test.ts +40 -71
- package/src/__tests__/always-loaded-tools-guard.test.ts +4 -7
- package/src/__tests__/annotate-activity-metadata.test.ts +4 -31
- package/src/__tests__/annotate-risk-options.test.ts +4 -31
- package/src/__tests__/anthropic-provider.test.ts +11 -0
- package/src/__tests__/app-asset-bytes.test.ts +72 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
- package/src/__tests__/app-bundler.test.ts +4 -20
- package/src/__tests__/app-conversation-ids-backfill.test.ts +1 -8
- package/src/__tests__/app-executors.test.ts +2 -49
- package/src/__tests__/app-routes-csp.test.ts +126 -157
- package/src/__tests__/approval-cascade.test.ts +0 -82
- package/src/__tests__/approval-interception-trust-gates.test.ts +0 -7
- package/src/__tests__/approval-primitive.test.ts +1 -9
- package/src/__tests__/approval-routes-http.test.ts +5 -63
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +1 -19
- package/src/__tests__/assistant-events-sse-hardening.test.ts +4 -21
- package/src/__tests__/attachment-stored-path-annotation.test.ts +1 -19
- package/src/__tests__/attachment-upload-trusted-source.test.ts +0 -13
- package/src/__tests__/attachments-store-heic-normalize.test.ts +0 -13
- package/src/__tests__/attachments-store.test.ts +5 -19
- package/src/__tests__/audit-log-rotation.test.ts +1 -9
- package/src/__tests__/auth-fallback-events-store.test.ts +43 -59
- package/src/__tests__/available-skills.test.ts +7 -15
- package/src/__tests__/avatar-e2e.test.ts +0 -19
- package/src/__tests__/avatar-generator.test.ts +0 -9
- package/src/__tests__/background-shell-bash.test.ts +0 -20
- package/src/__tests__/background-shell-host-bash.test.ts +0 -34
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -43
- package/src/__tests__/browser-fill-credential.test.ts +0 -7
- package/src/__tests__/browser-manager.test.ts +1 -8
- package/src/__tests__/browser-skill-endstate.test.ts +1 -5
- package/src/__tests__/btw-routes.test.ts +7 -30
- package/src/__tests__/build-persisted-content.test.ts +96 -29
- package/src/__tests__/bundle-scanner.test.ts +27 -1
- package/src/__tests__/call-controller.test.ts +96 -149
- package/src/__tests__/call-conversation-messages.test.ts +2 -9
- package/src/__tests__/call-domain.test.ts +21 -56
- package/src/__tests__/call-pointer-message-composer.test.ts +0 -8
- package/src/__tests__/call-pointer-messages.test.ts +1 -8
- package/src/__tests__/call-recovery.test.ts +4 -11
- package/src/__tests__/call-routes-http.test.ts +12 -47
- package/src/__tests__/call-site-routing-provider.test.ts +5 -16
- package/src/__tests__/call-start-guardian-guard.test.ts +0 -10
- package/src/__tests__/call-state.test.ts +1 -8
- package/src/__tests__/call-store.test.ts +4 -11
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -32
- package/src/__tests__/catalog-cache.test.ts +0 -8
- package/src/__tests__/catalog-files.test.ts +0 -8
- package/src/__tests__/catalog-install-normalize.test.ts +0 -7
- package/src/__tests__/ces-rpc-credential-backend.test.ts +0 -7
- package/src/__tests__/channel-approval-routes.test.ts +23 -34
- package/src/__tests__/channel-approvals.test.ts +0 -7
- package/src/__tests__/channel-delivery-store.test.ts +6 -17
- package/src/__tests__/channel-guardian.test.ts +0 -7
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +15 -20
- package/src/__tests__/channel-readiness-routes.test.ts +41 -67
- package/src/__tests__/channel-readiness-service.test.ts +13 -45
- package/src/__tests__/channel-readiness-slack-remote.test.ts +5 -22
- package/src/__tests__/channel-reply-delivery.test.ts +18 -5
- package/src/__tests__/channel-retry-sweep.test.ts +0 -7
- package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
- package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
- package/src/__tests__/checker.test.ts +0 -24
- package/src/__tests__/clawhub-files.test.ts +0 -8
- package/src/__tests__/clawhub.test.ts +0 -7
- package/src/__tests__/clear-all-lexical.test.ts +0 -6
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -0
- package/src/__tests__/client-os-metadata-persistence.test.ts +0 -5
- package/src/__tests__/compaction-events.test.ts +26 -99
- package/src/__tests__/compaction-ledger-store.test.ts +1 -7
- package/src/__tests__/compaction-summary-head-persisted-count.test.ts +0 -27
- package/src/__tests__/compaction-trail-store.test.ts +1 -19
- package/src/__tests__/compaction.benchmark.test.ts +2 -6
- package/src/__tests__/compactor-call-site-logging.test.ts +0 -21
- package/src/__tests__/compactor-fixed-boundary.test.ts +0 -21
- package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -10
- package/src/__tests__/compactor-low-watermark-cut.test.ts +0 -21
- package/src/__tests__/compactor-media-strip.test.ts +0 -21
- package/src/__tests__/compactor-preserved-tail-count.test.ts +0 -21
- package/src/__tests__/compactor-summary-call-truncation.test.ts +0 -21
- package/src/__tests__/compactor-web-search-strip.test.ts +0 -21
- package/src/__tests__/config-get-vision-flag.test.ts +24 -53
- package/src/__tests__/config-loader-backfill.test.ts +9 -25
- package/src/__tests__/config-loader-platform-defaults.test.ts +0 -21
- package/src/__tests__/config-schema-cmd.test.ts +10 -11
- package/src/__tests__/config-schema.test.ts +221 -275
- package/src/__tests__/config-set-route.test.ts +32 -62
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +44 -83
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -16
- package/src/__tests__/config-watcher.test.ts +2 -17
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +57 -63
- package/src/__tests__/contact-store-interaction-info.test.ts +1 -8
- package/src/__tests__/contact-store-user-file.test.ts +1 -8
- package/src/__tests__/contacts-tools.test.ts +21 -26
- package/src/__tests__/contacts-write.test.ts +1 -8
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -6
- package/src/__tests__/context-window-manager-compact-retry.test.ts +0 -27
- package/src/__tests__/context-window-manager-overflow-rung.test.ts +0 -21
- package/src/__tests__/conversation-abort-tool-results.test.ts +9 -90
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +5 -41
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +16 -54
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +14 -79
- package/src/__tests__/conversation-agent-loop.test.ts +213 -136
- package/src/__tests__/conversation-app-control-instantiation.test.ts +10 -16
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +0 -43
- package/src/__tests__/conversation-attachments.test.ts +0 -17
- package/src/__tests__/conversation-attention-store.test.ts +1 -9
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -8
- package/src/__tests__/conversation-clear-safety.test.ts +20 -20
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -82
- package/src/__tests__/conversation-crud-enabled-plugins.test.ts +1 -7
- package/src/__tests__/conversation-crud-inference-profile.test.ts +1 -7
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -7
- package/src/__tests__/conversation-disk-view-integration.test.ts +1 -26
- package/src/__tests__/conversation-disk-view.test.ts +1 -26
- package/src/__tests__/conversation-enabledplugins-route.test.ts +3 -21
- package/src/__tests__/conversation-error.test.ts +21 -7
- package/src/__tests__/conversation-fork-crud.test.ts +35 -44
- package/src/__tests__/conversation-fork-retrospective.test.ts +9 -24
- package/src/__tests__/conversation-fork-route.test.ts +0 -32
- package/src/__tests__/conversation-history-web-search.test.ts +20 -17
- package/src/__tests__/conversation-inference-profile-list.test.ts +0 -17
- package/src/__tests__/conversation-inference-profile-route.test.ts +2 -24
- package/src/__tests__/conversation-init.benchmark.test.ts +3 -56
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -33
- package/src/__tests__/conversation-lifecycle.test.ts +63 -103
- package/src/__tests__/conversation-list-source.test.ts +0 -18
- package/src/__tests__/conversation-load-history-repair.test.ts +39 -91
- package/src/__tests__/conversation-load-history-stripped.test.ts +12 -57
- package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +10 -20
- package/src/__tests__/conversation-message-id-uuidv7.test.ts +1 -18
- package/src/__tests__/conversation-pairing.test.ts +0 -7
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +0 -9
- package/src/__tests__/conversation-process-callsite.test.ts +15 -97
- package/src/__tests__/conversation-provider-retry-repair.test.ts +3 -84
- package/src/__tests__/conversation-queue.test.ts +12 -108
- package/src/__tests__/conversation-routes-disk-view.test.ts +8 -60
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +5 -63
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +13 -22
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +5 -64
- package/src/__tests__/conversation-routes-slash-commands.test.ts +19 -87
- package/src/__tests__/conversation-runtime-assembly.test.ts +34 -37
- package/src/__tests__/conversation-seed-composer.test.ts +3 -3
- package/src/__tests__/conversation-skill-tools.test.ts +19 -32
- package/src/__tests__/conversation-slash-queue.test.ts +3 -90
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -87
- package/src/__tests__/conversation-speed-override.test.ts +19 -94
- package/src/__tests__/conversation-starter-routes.test.ts +0 -7
- package/src/__tests__/conversation-store.test.ts +72 -66
- package/src/__tests__/conversation-summarize-route.test.ts +0 -11
- package/src/__tests__/conversation-summarize-up-to.test.ts +4 -89
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +131 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +36 -36
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +4 -2
- package/src/__tests__/conversation-title-service.test.ts +0 -7
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +37 -60
- package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +17 -17
- package/src/__tests__/conversation-unread-route.test.ts +0 -7
- package/src/__tests__/conversation-usage.test.ts +21 -71
- package/src/__tests__/conversation-wait-for-idle.test.ts +0 -82
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -45
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -88
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -88
- package/src/__tests__/conversations-defer-cli.test.ts +3 -0
- package/src/__tests__/conversations-import-system-filter.test.ts +0 -17
- package/src/__tests__/credential-broker-browser-fill.test.ts +0 -9
- package/src/__tests__/credential-broker-server-use.test.ts +0 -9
- package/src/__tests__/credential-health-service.test.ts +14 -31
- package/src/__tests__/credential-metadata-store.test.ts +0 -8
- package/src/__tests__/credential-prompt-route.test.ts +9 -15
- package/src/__tests__/credential-resolve.test.ts +0 -8
- package/src/__tests__/credential-routes.test.ts +250 -0
- package/src/__tests__/credential-security-invariants.test.ts +4 -8
- package/src/__tests__/credential-token-resolver.test.ts +0 -9
- package/src/__tests__/cross-provider-web-search.test.ts +12 -18
- package/src/__tests__/daemon-assistant-events.test.ts +1 -9
- package/src/__tests__/daemon-credential-client.test.ts +0 -7
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +1 -8
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +1 -7
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +0 -8
- package/src/__tests__/db-readiness-http-gate.test.ts +0 -18
- package/src/__tests__/db-rename-inference-profile-snake-case-migration.test.ts +1 -7
- package/src/__tests__/db-test-helpers.ts +10 -3
- package/src/__tests__/delete-managed-skill-tool.test.ts +0 -7
- package/src/__tests__/delete-propagation.test.ts +0 -7
- package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -7
- package/src/__tests__/device-id.test.ts +1 -9
- package/src/__tests__/disk-pressure-guard.test.ts +0 -4
- package/src/__tests__/disk-pressure-routes.test.ts +0 -4
- package/src/__tests__/disk-pressure-tools.test.ts +4 -42
- package/src/__tests__/dm-backfill.test.ts +0 -7
- package/src/__tests__/dm-persistence.test.ts +0 -5
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +1 -8
- package/src/__tests__/drain-requeue-on-contention.test.ts +1 -8
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -43
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -27
- package/src/__tests__/edit-propagation.test.ts +1 -8
- package/src/__tests__/email-invite-adapter.test.ts +8 -33
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +0 -8
- package/src/__tests__/emit-event-signal.test.ts +1 -1
- package/src/__tests__/emit-signal-routing-intent.test.ts +0 -7
- package/src/__tests__/empty-state-greeting-cache.test.ts +9 -9
- package/src/__tests__/encrypted-store.test.ts +0 -9
- package/src/__tests__/ensure-conversation-exists.test.ts +1 -18
- package/src/__tests__/events-client-registration.test.ts +1 -19
- package/src/__tests__/events-dev-bypass-actor.test.ts +0 -7
- package/src/__tests__/events-tail-route.test.ts +1 -8
- package/src/__tests__/file-list-tool.test.ts +22 -15
- package/src/__tests__/followup-tools.test.ts +1 -16
- package/src/__tests__/gateway-only-enforcement.test.ts +3 -44
- package/src/__tests__/get-skill-detail-audit.test.ts +0 -7
- package/src/__tests__/google-calendar-watcher.test.ts +175 -0
- package/src/__tests__/guardian-action-sweep.test.ts +0 -7
- package/src/__tests__/guardian-binding-drift-heal.test.ts +0 -7
- package/src/__tests__/guardian-card-withdrawal.test.ts +60 -64
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +172 -135
- package/src/__tests__/guardian-dispatch.test.ts +58 -157
- package/src/__tests__/guardian-expiry-notifier.test.ts +56 -48
- package/src/__tests__/guardian-gateway-sim.ts +626 -0
- package/src/__tests__/guardian-outbound-http.test.ts +0 -7
- package/src/__tests__/guardian-routing-invariants.test.ts +229 -220
- package/src/__tests__/guardian-routing-state.test.ts +2 -9
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -17
- package/src/__tests__/guardian-wait-controller.test.ts +44 -43
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +26 -91
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +18 -103
- package/src/__tests__/headless-browser-interactions.test.ts +0 -7
- package/src/__tests__/headless-browser-mode.test.ts +0 -9
- package/src/__tests__/headless-browser-navigate.test.ts +0 -7
- package/src/__tests__/headless-browser-read-tools.test.ts +0 -7
- package/src/__tests__/headless-browser-snapshot.test.ts +0 -7
- package/src/__tests__/heartbeat-disk-pressure.test.ts +9 -38
- package/src/__tests__/heartbeat-service.test.ts +83 -106
- package/src/__tests__/helpers/channel-test-adapter.ts +10 -0
- package/src/__tests__/helpers/gateway-guardian-requests-store-bridge.ts +155 -0
- package/src/__tests__/helpers/set-config.ts +40 -0
- package/src/__tests__/host-bash-proxy.test.ts +18 -34
- package/src/__tests__/host-browser-proxy.test.ts +15 -20
- package/src/__tests__/host-shell-tool.test.ts +2 -34
- package/src/__tests__/http-conversation-lineage.test.ts +3 -19
- package/src/__tests__/http-user-message-parity.test.ts +3 -95
- package/src/__tests__/identity-routes.test.ts +1 -9
- package/src/__tests__/image-recovery-hook.test.ts +36 -0
- package/src/__tests__/inbound-admitted-introduction-nudge.test.ts +6 -12
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -7
- package/src/__tests__/inbound-slack-persistence.test.ts +0 -5
- package/src/__tests__/inbound-trust-verdict.test.ts +2 -8
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
- package/src/__tests__/inference-profile-reaper.test.ts +1 -7
- package/src/__tests__/inference-profile-session-handler.test.ts +4 -30
- package/src/__tests__/inference-profile-session-ipc.test.ts +3 -20
- package/src/__tests__/ingress-url-consistency.test.ts +1 -23
- package/src/__tests__/injector-background-turn.test.ts +10 -24
- package/src/__tests__/injector-chain.test.ts +4 -13
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +19 -17
- package/src/__tests__/inline-command-runner.test.ts +0 -34
- package/src/__tests__/inline-skill-load-permissions.test.ts +1 -30
- package/src/__tests__/install-skill-routing.test.ts +7 -6
- package/src/__tests__/integration-status.test.ts +25 -17
- package/src/__tests__/intent-routing.test.ts +1 -50
- package/src/__tests__/internal-telemetry-routes.test.ts +41 -22
- package/src/__tests__/interrupted-turn-reconciler.test.ts +1 -8
- package/src/__tests__/introduction-card-resolver.test.ts +108 -117
- package/src/__tests__/invite-redeemed-ipc.test.ts +0 -7
- package/src/__tests__/invite-routes-http.test.ts +12 -23
- package/src/__tests__/jobs-store-has-pending-job.test.ts +59 -0
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +1 -14
- package/src/__tests__/jobs-store-skill-card-upsert.test.ts +191 -0
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +1 -14
- package/src/__tests__/lexical-index-dual-write.test.ts +0 -6
- package/src/__tests__/list-all-apps.test.ts +200 -0
- package/src/__tests__/list-messages-attachments.test.ts +11 -20
- package/src/__tests__/list-messages-background-tool-completion.test.ts +6 -18
- package/src/__tests__/list-messages-client-message-id.test.ts +4 -16
- package/src/__tests__/list-messages-hidden-metadata.test.ts +6 -18
- package/src/__tests__/list-messages-page-latest.test.ts +3 -19
- package/src/__tests__/list-messages-queued.test.ts +1 -18
- package/src/__tests__/list-messages-tool-merge.test.ts +7 -18
- package/src/__tests__/llm-context-resolution.test.ts +32 -56
- package/src/__tests__/llm-context-route-provider.test.ts +1 -8
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +1 -19
- package/src/__tests__/llm-request-log-call-site.test.ts +1 -19
- package/src/__tests__/llm-request-log-clickhouse-routing.test.ts +114 -0
- package/src/__tests__/llm-request-log-clickhouse-writer-guards.test.ts +108 -0
- package/src/__tests__/llm-request-log-disabled-writes.test.ts +11 -30
- package/src/__tests__/llm-request-log-sink-clickhouse.test.ts +205 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +1 -22
- package/src/__tests__/llm-request-log-source-factory.test.ts +23 -69
- package/src/__tests__/llm-request-log-turn-query.test.ts +1 -19
- package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
- package/src/__tests__/llm-resolver.test.ts +342 -602
- package/src/__tests__/llm-schema.test.ts +79 -37
- package/src/__tests__/llm-usage-store.test.ts +1 -7
- package/src/__tests__/log-export-routes.test.ts +0 -7
- package/src/__tests__/log-export-workspace.test.ts +0 -7
- package/src/__tests__/managed-profile-guard.test.ts +125 -137
- package/src/__tests__/managed-skill-lifecycle.test.ts +6 -50
- package/src/__tests__/managed-speech-defaults.test.ts +138 -0
- package/src/__tests__/managed-store.test.ts +1 -16
- package/src/__tests__/mcp-auth-routes.test.ts +15 -26
- package/src/__tests__/mcp-config-secret-boundary.test.ts +49 -91
- package/src/__tests__/mcp-health-check.test.ts +14 -20
- package/src/__tests__/media-generate-image.test.ts +25 -32
- package/src/__tests__/media-stream-output.test.ts +0 -9
- package/src/__tests__/media-stream-server-integration.test.ts +19 -27
- package/src/__tests__/media-stream-stt-session.test.ts +16 -29
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +6 -19
- package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -12
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +10 -29
- package/src/__tests__/memory-recall-log-store.test.ts +1 -19
- package/src/__tests__/memory-retrieval-hook.test.ts +0 -3
- package/src/__tests__/memory-upsert-concurrency.test.ts +12 -35
- package/src/__tests__/memory-v2-static-injector.test.ts +5 -17
- package/src/__tests__/messages-after-tiebreaker.test.ts +1 -7
- package/src/__tests__/messaging-send-tool.test.ts +67 -7
- package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -18
- package/src/__tests__/migration-export-http.test.ts +10 -23
- package/src/__tests__/migration-export-streaming.test.ts +0 -18
- package/src/__tests__/migration-export-to-gcs.test.ts +0 -19
- package/src/__tests__/migration-import-commit-http.test.ts +11 -23
- package/src/__tests__/migration-import-from-gcs.test.ts +0 -19
- package/src/__tests__/migration-import-from-url.test.ts +0 -19
- package/src/__tests__/migration-import-preflight-http.test.ts +10 -22
- package/src/__tests__/migration-jobs-status.test.ts +8 -23
- package/src/__tests__/migration-validate-http.test.ts +2 -19
- package/src/__tests__/mtime-cache.test.ts +116 -2
- package/src/__tests__/native-web-search.test.ts +0 -21
- package/src/__tests__/non-member-access-request.test.ts +102 -93
- package/src/__tests__/notification-broadcaster.test.ts +32 -21
- package/src/__tests__/notification-candidate-guardian-context.test.ts +36 -34
- package/src/__tests__/notification-decision-fallback.test.ts +0 -7
- package/src/__tests__/notification-decision-identity.test.ts +0 -7
- package/src/__tests__/notification-decision-recipient-context.test.ts +0 -7
- package/src/__tests__/notification-deep-link.test.ts +0 -7
- package/src/__tests__/notification-guardian-path.test.ts +48 -93
- package/src/__tests__/notification-platform-adapter.test.ts +0 -7
- package/src/__tests__/notification-routing-intent.test.ts +1 -9
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -9
- package/src/__tests__/notification-telegram-adapter.test.ts +0 -7
- package/src/__tests__/oauth-cli.test.ts +0 -19
- package/src/__tests__/oauth-commands-routes.test.ts +28 -43
- package/src/__tests__/oauth-connect-orchestrator.test.ts +17 -26
- package/src/__tests__/oauth-connect-routes.test.ts +19 -18
- package/src/__tests__/oauth-provider-profiles.test.ts +3 -9
- package/src/__tests__/oauth-provider-visibility.test.ts +0 -7
- package/src/__tests__/oauth-store.test.ts +0 -7
- package/src/__tests__/oauth2-gateway-transport.test.ts +41 -43
- package/src/__tests__/openai-responses-prompt-cache.test.ts +355 -0
- package/src/__tests__/openai-responses-provider.test.ts +98 -9
- package/src/__tests__/openrouter-responses-transport.test.ts +170 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +13 -46
- package/src/__tests__/outlook-email-watcher.test.ts +0 -9
- package/src/__tests__/outlook-messaging-provider.test.ts +71 -0
- package/src/__tests__/path-policy.test.ts +0 -7
- package/src/__tests__/persist-media-references.test.ts +1 -6
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -3
- package/src/__tests__/persist-unsendable-image.test.ts +39 -7
- package/src/__tests__/persistence-secret-redaction.test.ts +7 -28
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -46
- package/src/__tests__/platform-callback-registration.test.ts +10 -9
- package/src/__tests__/platform-proxy-context.test.ts +0 -8
- package/src/__tests__/playbook-execution.test.ts +0 -14
- package/src/__tests__/playbook-tools.test.ts +0 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +12 -35
- package/src/__tests__/plugin-app-serve-routes.test.ts +315 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -3
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -2
- package/src/__tests__/plugin-types.test.ts +0 -7
- package/src/__tests__/prechat-onboarding-contract.test.ts +0 -27
- package/src/__tests__/pricing.test.ts +45 -0
- package/src/__tests__/process-message-background-slack.test.ts +7 -21
- package/src/__tests__/process-message-display-content.test.ts +37 -6
- package/src/__tests__/processing-flag-persist-failure.test.ts +0 -44
- package/src/__tests__/provider-commit-message-generator.test.ts +57 -63
- package/src/__tests__/provider-error-scenarios.test.ts +5 -6
- package/src/__tests__/provider-platform-proxy-integration.test.ts +21 -57
- package/src/__tests__/provider-registry-ollama.test.ts +8 -18
- package/src/__tests__/provider-send-message-override-profile.test.ts +28 -39
- package/src/__tests__/provider-streaming.benchmark.test.ts +1 -6
- package/src/__tests__/provider-usage-tracking.test.ts +13 -35
- package/src/__tests__/proxy-approval-callback.test.ts +0 -16
- package/src/__tests__/prune-old-conversations-job.test.ts +29 -21
- package/src/__tests__/prune-old-tool-invocations-job.test.ts +1 -9
- package/src/__tests__/public-ingress-urls.test.ts +1 -24
- package/src/__tests__/published-app-updater.test.ts +22 -16
- package/src/__tests__/qdrant-collection-migration.test.ts +0 -7
- package/src/__tests__/qdrant-manager.test.ts +0 -8
- package/src/__tests__/ratelimit.test.ts +1 -8
- package/src/__tests__/reaction-persistence.test.ts +18 -24
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +0 -14
- package/src/__tests__/recording-handler.test.ts +0 -27
- package/src/__tests__/request-secret-standalone-channel-gate.test.ts +4 -16
- package/src/__tests__/require-fresh-approval.test.ts +0 -45
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +24 -36
- package/src/__tests__/retry-prompt-cache-key.test.ts +123 -0
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +45 -61
- package/src/__tests__/retry-thinking-tool-choice.test.ts +60 -80
- package/src/__tests__/retry-verbosity-normalization.test.ts +27 -37
- package/src/__tests__/reveal-success-registry.test.ts +123 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +113 -0
- package/src/__tests__/{scheduler-worker-standdown.test.ts → run-due-schedules.test.ts} +5 -77
- package/src/__tests__/runtime-attachment-metadata.test.ts +5 -18
- package/src/__tests__/runtime-events-sse-bilingual.test.ts +1 -19
- package/src/__tests__/runtime-events-sse-parity.test.ts +1 -20
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -19
- package/src/__tests__/runtime-events-sse.test.ts +1 -20
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +306 -5
- package/src/__tests__/schedule-retry.test.ts +22 -23
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +18 -42
- package/src/__tests__/schedule-routes.test.ts +2 -150
- package/src/__tests__/schedule-store.test.ts +0 -8
- package/src/__tests__/schedule-tools.test.ts +1 -16
- package/src/__tests__/scheduler-disk-pressure.test.ts +0 -26
- package/src/__tests__/scheduler-recurrence.test.ts +43 -76
- package/src/__tests__/scheduler-reuse-conversation.test.ts +19 -35
- package/src/__tests__/scheduler-wake.test.ts +8 -24
- package/src/__tests__/scoped-approval-grants.test.ts +1 -9
- package/src/__tests__/scoped-grant-security-matrix.test.ts +1 -9
- package/src/__tests__/search-skills-unified.test.ts +0 -6
- package/src/__tests__/secret-allowlist.test.ts +1 -8
- package/src/__tests__/secret-fixtures.ts +9 -0
- package/src/__tests__/secret-ingress-channel.test.ts +4 -32
- package/src/__tests__/secret-ingress-cli.test.ts +3 -37
- package/src/__tests__/secret-ingress-http.test.ts +15 -78
- package/src/__tests__/secret-ingress.test.ts +14 -31
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +4 -10
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +3 -9
- package/src/__tests__/secret-routes-platform-proxy.test.ts +3 -40
- package/src/__tests__/secure-keys-managed-failover.test.ts +0 -7
- package/src/__tests__/secure-keys.test.ts +0 -9
- package/src/__tests__/send-endpoint-busy.test.ts +47 -91
- package/src/__tests__/sequence-store.test.ts +0 -9
- package/src/__tests__/server-history-render.test.ts +29 -8
- package/src/__tests__/set-permission-mode.test.ts +8 -16
- package/src/__tests__/settings-routes.test.ts +0 -7
- package/src/__tests__/shell-credential-ref.test.ts +0 -18
- package/src/__tests__/shell-observability.test.ts +0 -21
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -34
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -27
- package/src/__tests__/skill-load-inline-command.test.ts +0 -25
- package/src/__tests__/skill-load-inline-includes.test.ts +0 -25
- package/src/__tests__/skill-load-plugin-scope.test.ts +0 -24
- package/src/__tests__/skill-projection-feature-flag.test.ts +14 -33
- package/src/__tests__/skill-projection.benchmark.test.ts +14 -19
- package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -39
- package/src/__tests__/skills-file-content-endpoint.test.ts +0 -7
- package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -14
- package/src/__tests__/slack-channel-config.test.ts +24 -79
- package/src/__tests__/slack-inbound-verification.test.ts +5 -13
- package/src/__tests__/{slack-reaction-canonical-approval.test.ts → slack-reaction-guardian-approval.test.ts} +18 -74
- package/src/__tests__/slack-share-routes.test.ts +0 -1
- package/src/__tests__/slim-skill-category.test.ts +0 -14
- package/src/__tests__/stale-approval-dedup.test.ts +0 -7
- package/src/__tests__/subagent-call-site-routing.test.ts +72 -111
- package/src/__tests__/subagent-detail.test.ts +1 -1
- package/src/__tests__/subagent-fork-prompt-role.test.ts +0 -12
- package/src/__tests__/subagent-notify-parent.test.ts +4 -6
- package/src/__tests__/subagent-spawn-and-await.test.ts +0 -18
- package/src/__tests__/subagent-tool-filtering.test.ts +21 -31
- package/src/__tests__/subagent-tool-gate-mode.test.ts +6 -32
- package/src/__tests__/subagent-tools.test.ts +92 -67
- package/src/__tests__/suggestion-routes.test.ts +28 -42
- package/src/__tests__/summarize-boundary.test.ts +2 -1
- package/src/__tests__/system-prompt-ask-mode.test.ts +0 -25
- package/src/__tests__/system-prompt.test.ts +0 -33
- package/src/__tests__/task-memory-cleanup.test.ts +18 -192
- package/src/__tests__/telegram-bot-username-resolution.test.ts +24 -48
- package/src/__tests__/telegram-config.test.ts +0 -8
- package/src/__tests__/telemetry-routes.test.ts +100 -0
- package/src/__tests__/telephony-credential-preflight.test.ts +7 -11
- package/src/__tests__/telephony-tts-guard.test.ts +38 -21
- package/src/__tests__/terminal-tools.test.ts +0 -21
- package/src/__tests__/thread-backfill.test.ts +0 -7
- package/src/__tests__/title-generate-hook.test.ts +11 -10
- package/src/__tests__/token-manager.test.ts +0 -9
- package/src/__tests__/tool-approval-handler.test.ts +0 -8
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +5 -33
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +3 -23
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -48
- package/src/__tests__/tool-executor.test.ts +3 -49
- package/src/__tests__/tool-grant-request-escalation.test.ts +51 -153
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -29
- package/src/__tests__/tool-profiler.test.ts +72 -1
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +0 -29
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
- package/src/__tests__/tool-start-timestamp.test.ts +4 -31
- package/src/__tests__/tools-get-route.test.ts +2 -1
- package/src/__tests__/trust-context-guards.test.ts +19 -7
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +75 -79
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +14 -18
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -7
- package/src/__tests__/trusted-contact-verification.test.ts +1 -9
- package/src/__tests__/turn-boundary-resolution.test.ts +1 -19
- package/src/__tests__/turn-events-store.test.ts +1 -7
- package/src/__tests__/turn-outcome-read.test.ts +1 -7
- package/src/__tests__/twilio-config.test.ts +11 -28
- package/src/__tests__/twilio-provider.test.ts +2 -13
- package/src/__tests__/twilio-routes-twiml.test.ts +1 -8
- package/src/__tests__/twilio-routes.test.ts +52 -103
- package/src/__tests__/twilio-validation.test.ts +5 -22
- package/src/__tests__/ui-file-upload-surface.test.ts +6 -9
- package/src/__tests__/usage-attribution.test.ts +25 -56
- package/src/__tests__/usage-cache-backfill-migration.test.ts +8 -15
- package/src/__tests__/usage-routes.test.ts +1 -8
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -18
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -24
- package/src/__tests__/vercel-config.test.ts +0 -7
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -45
- package/src/__tests__/voice-approval-resolver-relay.test.ts +106 -75
- package/src/__tests__/voice-config-update.test.ts +114 -21
- package/src/__tests__/voice-ingress-preflight.test.ts +16 -15
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -30
- package/src/__tests__/voice-session-bridge-processing-wait.test.ts +1 -17
- package/src/__tests__/voice-session-bridge.test.ts +14 -46
- package/src/__tests__/web-search-backend-failure.test.ts +0 -21
- package/src/__tests__/whatsapp-invite-adapter.test.ts +11 -30
- package/src/__tests__/workspace-git-service.test.ts +801 -2
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +1 -26
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -26
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +1 -26
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +1 -9
- package/src/a2a/__tests__/task-store.test.ts +0 -9
- package/src/acp/__tests__/session-manager-persistence.test.ts +1 -8
- package/src/acp/__tests__/session-manager-resume.test.ts +0 -7
- package/src/acp/__tests__/session-manager-startup.test.ts +1 -8
- package/src/agent/image-optimize.ts +80 -0
- package/src/agent/loop.ts +20 -5
- package/src/api/events/open-conversation.test.ts +64 -0
- package/src/api/events/open-conversation.ts +33 -0
- package/src/api/index.ts +6 -0
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-card-withdrawal.ts +26 -19
- package/src/approvals/guardian-decision-primitive.ts +172 -102
- package/src/approvals/guardian-expiry-notifier.ts +20 -19
- package/src/approvals/guardian-request-resolvers.ts +305 -275
- package/src/apps/app-store.ts +320 -15
- package/src/background-wake/background-wake-routes.test.ts +2 -3
- package/src/background-wake/next-wake.test.ts +23 -20
- package/src/background-wake/wake-intent-hooks.test.ts +0 -10
- package/src/bundler/app-bundler.ts +34 -48
- package/src/bundler/app-compiler.ts +39 -4
- package/src/bundler/bundle-scanner.ts +13 -0
- package/src/bundler/manifest.ts +1 -1
- package/src/calls/__tests__/call-setup-router.test.ts +84 -88
- package/src/calls/__tests__/fish-audio-client.test.ts +6 -11
- package/src/calls/__tests__/voice-control-protocol.test.ts +47 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +151 -16
- package/src/calls/__tests__/voice-triage-escalate.test.ts +128 -0
- package/src/calls/access-request-wait.ts +35 -35
- package/src/calls/call-controller.ts +48 -45
- package/src/calls/call-setup-flow.ts +2 -2
- package/src/calls/call-setup-router.ts +1 -24
- package/src/calls/guardian-dispatch.ts +47 -30
- package/src/calls/guardian-wait-controller.ts +31 -18
- package/src/calls/media-turn-detector.ts +11 -1
- package/src/calls/telephony-credential-preflight.ts +19 -1
- package/src/calls/telephony-tts-capability.ts +18 -1
- package/src/calls/voice-control-protocol.ts +12 -0
- package/src/calls/voice-session-bridge.ts +110 -15
- package/src/calls/voice-triage-escalate.ts +136 -0
- package/src/channels/__tests__/gateway-guardian-requests.test.ts +669 -0
- package/src/channels/gateway-guardian-requests.ts +410 -0
- package/src/channels/types.ts +10 -5
- package/src/cli/AGENTS.md +77 -51
- package/src/cli/__tests__/cli-command-help-coverage.test.ts +29 -0
- package/src/cli/commands/__tests__/conversations-import.test.ts +23 -24
- package/src/cli/commands/__tests__/gateway.test.ts +55 -0
- package/src/cli/commands/__tests__/inference-models.test.ts +3 -0
- package/src/cli/commands/__tests__/inference-profiles.test.ts +3 -0
- package/src/cli/commands/__tests__/inference-providers.test.ts +3 -0
- package/src/cli/commands/__tests__/inference-send.test.ts +0 -10
- package/src/cli/commands/__tests__/inference-session.test.ts +4 -11
- package/src/cli/commands/__tests__/ui.test.ts +2 -2
- package/src/cli/commands/apps.help.ts +46 -0
- package/src/cli/commands/apps.ts +51 -0
- package/src/cli/commands/avatar.help.ts +229 -0
- package/src/cli/commands/avatar.ts +58 -250
- package/src/cli/commands/backup.help.ts +183 -0
- package/src/cli/commands/backup.ts +107 -276
- package/src/cli/commands/bash.help.ts +38 -0
- package/src/cli/commands/bash.ts +51 -75
- package/src/cli/commands/browser.help.ts +193 -0
- package/src/cli/commands/browser.ts +234 -347
- package/src/cli/commands/cache.help.ts +123 -0
- package/src/cli/commands/cache.ts +72 -166
- package/src/cli/commands/changelog.help.ts +59 -0
- package/src/cli/commands/changelog.ts +29 -61
- package/src/cli/commands/channel-verification-sessions.help.ts +178 -0
- package/src/cli/commands/channel-verification-sessions.ts +71 -226
- package/src/cli/commands/channels/index.help.ts +67 -0
- package/src/cli/commands/channels/index.ts +55 -105
- package/src/cli/commands/clients.help.ts +72 -0
- package/src/cli/commands/clients.ts +100 -153
- package/src/cli/commands/completions.help.ts +33 -0
- package/src/cli/commands/completions.ts +43 -66
- package/src/cli/commands/config.help.ts +126 -0
- package/src/cli/commands/config.ts +59 -174
- package/src/cli/commands/contacts.help.ts +327 -0
- package/src/cli/commands/contacts.ts +318 -603
- package/src/cli/commands/conversations-defer.ts +141 -202
- package/src/cli/commands/conversations-import.ts +80 -113
- package/src/cli/commands/conversations.help.ts +364 -0
- package/src/cli/commands/conversations.ts +143 -328
- package/src/cli/commands/credentials.help.ts +290 -0
- package/src/cli/commands/credentials.ts +271 -487
- package/src/cli/commands/db/index.help.ts +27 -0
- package/src/cli/commands/db/index.ts +7 -3
- package/src/cli/commands/db/refresh.ts +131 -0
- package/src/cli/commands/db/repair-step-conversation-backfill.ts +19 -9
- package/src/cli/commands/db/repair.ts +41 -43
- package/src/cli/commands/db/status.ts +79 -83
- package/src/cli/commands/domain.help.ts +34 -0
- package/src/cli/commands/domain.ts +10 -15
- package/src/cli/commands/email.help.ts +176 -0
- package/src/cli/commands/email.ts +323 -412
- package/src/cli/commands/gateway.help.ts +115 -0
- package/src/cli/commands/gateway.ts +105 -123
- package/src/cli/commands/image-generation.help.ts +72 -0
- package/src/cli/commands/image-generation.ts +13 -62
- package/src/cli/commands/inference-callsites.ts +34 -49
- package/src/cli/commands/inference-models.ts +6 -21
- package/src/cli/commands/inference-profiles.ts +95 -156
- package/src/cli/commands/inference-providers-default.ts +27 -42
- package/src/cli/commands/inference-providers.ts +25 -87
- package/src/cli/commands/inference-session.ts +189 -249
- package/src/cli/commands/inference.help.ts +485 -0
- package/src/cli/commands/inference.ts +104 -165
- package/src/cli/commands/keys.help.ts +46 -0
- package/src/cli/commands/keys.ts +37 -71
- package/src/cli/commands/mcp.help.ts +167 -0
- package/src/cli/commands/mcp.ts +123 -267
- package/src/cli/commands/memory/__tests__/memory-items.test.ts +4 -0
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +4 -0
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +4 -0
- package/src/cli/commands/memory/__tests__/worker.test.ts +12 -5
- package/src/cli/commands/memory/index.help.ts +814 -0
- package/src/cli/commands/memory/index.ts +5 -27
- package/src/cli/commands/memory/items.ts +257 -404
- package/src/cli/commands/memory/memory-retrospective.ts +31 -71
- package/src/cli/commands/memory/memory-v2-compare-render.ts +2 -2
- package/src/cli/commands/memory/memory-v2.ts +435 -646
- package/src/cli/commands/memory/memory-v3.ts +158 -283
- package/src/cli/commands/memory/nodes.ts +154 -249
- package/src/cli/commands/memory/worker.ts +83 -128
- package/src/cli/commands/monitoring.help.ts +57 -0
- package/src/cli/commands/monitoring.ts +17 -41
- package/src/cli/commands/notifications.help.ts +293 -0
- package/src/cli/commands/notifications.ts +420 -657
- package/src/cli/commands/oauth/apps.ts +182 -315
- package/src/cli/commands/oauth/connect.ts +4 -35
- package/src/cli/commands/oauth/disconnect.ts +44 -71
- package/src/cli/commands/oauth/index.help.ts +855 -0
- package/src/cli/commands/oauth/index.ts +5 -31
- package/src/cli/commands/oauth/mode.ts +5 -28
- package/src/cli/commands/oauth/ping.ts +44 -66
- package/src/cli/commands/oauth/providers.ts +413 -772
- package/src/cli/commands/oauth/request.ts +6 -35
- package/src/cli/commands/oauth/shared.ts +1 -1
- package/src/cli/commands/oauth/status.test.ts +6 -6
- package/src/cli/commands/oauth/status.ts +45 -62
- package/src/cli/commands/oauth/token.ts +32 -71
- package/src/cli/commands/pending.help.ts +26 -0
- package/src/cli/commands/pending.ts +77 -78
- package/src/cli/commands/platform/__tests__/connect.test.ts +3 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +4 -4
- package/src/cli/commands/platform/__tests__/status.test.ts +0 -90
- package/src/cli/commands/platform/connect.ts +10 -21
- package/src/cli/commands/platform/disconnect.ts +11 -26
- package/src/cli/commands/platform/index.help.ts +187 -0
- package/src/cli/commands/platform/index.ts +22 -169
- package/src/cli/commands/plugins.help.ts +247 -0
- package/src/cli/commands/plugins.ts +356 -437
- package/src/cli/commands/ps.help.ts +26 -0
- package/src/cli/commands/ps.ts +5 -17
- package/src/cli/commands/routes.help.ts +72 -0
- package/src/cli/commands/routes.ts +78 -125
- package/src/cli/commands/schedules-worker.ts +70 -138
- package/src/cli/commands/schedules.help.ts +514 -0
- package/src/cli/commands/schedules.ts +494 -871
- package/src/cli/commands/sequence.help.ts +183 -0
- package/src/cli/commands/sequence.ts +181 -343
- package/src/cli/commands/skills.help.ts +159 -0
- package/src/cli/commands/skills.ts +230 -368
- package/src/cli/commands/status.help.ts +32 -0
- package/src/cli/commands/status.ts +38 -3
- package/src/cli/commands/stt.help.ts +52 -0
- package/src/cli/commands/stt.ts +9 -47
- package/src/cli/commands/telemetry.help.ts +14 -0
- package/src/cli/commands/telemetry.ts +19 -17
- package/src/cli/commands/tools-run.ts +30 -45
- package/src/cli/commands/tools.help.ts +81 -0
- package/src/cli/commands/tools.ts +43 -83
- package/src/cli/commands/trust.help.ts +43 -0
- package/src/cli/commands/trust.ts +82 -109
- package/src/cli/commands/tts.help.ts +88 -0
- package/src/cli/commands/tts.ts +116 -188
- package/src/cli/commands/ui.help.ts +213 -0
- package/src/cli/commands/ui.ts +363 -380
- package/src/cli/commands/usage.help.ts +147 -0
- package/src/cli/commands/usage.ts +87 -206
- package/src/cli/commands/watchers.help.ts +211 -0
- package/src/cli/commands/watchers.ts +261 -417
- package/src/cli/commands/webhooks.help.ts +95 -0
- package/src/cli/commands/webhooks.ts +44 -115
- package/src/cli/index.help.ts +91 -0
- package/src/cli/lib/__tests__/global-json-option.test.ts +97 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +144 -4
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +96 -132
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +65 -0
- package/src/cli/lib/__tests__/plugin-catalog-platform.test.ts +167 -0
- package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +197 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +349 -240
- package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +31 -0
- package/src/cli/lib/__tests__/plugin-icon-file.test.ts +50 -1
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +94 -0
- package/src/cli/lib/__tests__/plugins-install-offline.test.ts +147 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +193 -309
- package/src/cli/lib/bundled-marketplace.json +439 -0
- package/src/cli/lib/cli-command-help.ts +83 -10
- package/src/cli/lib/global-json-option.ts +41 -0
- package/src/cli/lib/install-from-github.ts +69 -2
- package/src/cli/lib/plugin-catalog-cache.ts +22 -50
- package/src/cli/lib/plugin-catalog-local.ts +71 -0
- package/src/cli/lib/plugin-catalog-platform.ts +117 -0
- package/src/cli/lib/plugin-catalog-resolve.ts +72 -0
- package/src/cli/lib/plugin-details.ts +100 -34
- package/src/cli/lib/plugin-fingerprint.ts +18 -7
- package/src/cli/lib/plugin-icon-file.ts +38 -26
- package/src/cli/lib/plugin-marketplace.ts +15 -2
- package/src/cli/lib/publish-plugin.ts +3 -3
- package/src/cli/lib/search-plugins.ts +48 -111
- package/src/cli/program.ts +7 -0
- package/src/cli.ts +22 -13
- package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
- package/src/config/__tests__/default-provider.test.ts +0 -21
- package/src/config/__tests__/deployment-context-defaults.test.ts +0 -21
- package/src/config/__tests__/feature-flag-registry-bundled.test.ts +2 -2
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +0 -21
- package/src/config/__tests__/loader-last-known-good.test.ts +0 -21
- package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +0 -21
- package/src/config/__tests__/platform-rehydration.test.ts +125 -0
- package/src/config/__tests__/profile-materialization.test.ts +1 -88
- package/src/config/__tests__/seed-default-provider.test.ts +0 -21
- package/src/config/__tests__/sync-gated-profiles.test.ts +0 -21
- package/src/config/bundled-skills/app-builder/SKILL.md +24 -1
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +30 -0
- package/src/config/bundled-skills/contacts/SKILL.md +1 -1
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +4 -2
- package/src/config/bundled-skills/messaging/TOOLS.json +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +28 -20
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/bundled-skills/schedule/SKILL.md +0 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/TOOLS.json +5 -3
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +84 -10
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +0 -7
- package/src/config/call-site-defaults.ts +3 -3
- package/src/config/feature-flag-registry.json +15 -63
- package/src/config/llm-resolver.ts +88 -475
- package/src/config/managed-speech-defaults.ts +115 -0
- package/src/config/platform-rehydration.ts +76 -0
- package/src/config/profile-materialization.ts +11 -11
- package/src/config/schema.ts +93 -129
- package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
- package/src/config/schemas/__tests__/stt.test.ts +37 -0
- package/src/config/schemas/live-voice.ts +10 -4
- package/src/config/schemas/llm.ts +51 -15
- package/src/config/schemas/memory-lifecycle.ts +1 -13
- package/src/config/schemas/memory-retrospective.ts +8 -0
- package/src/config/schemas/memory.ts +0 -4
- package/src/config/schemas/stt.ts +33 -6
- package/src/config/schemas/tts.ts +53 -6
- package/src/config/schemas/workspace-git.ts +8 -0
- package/src/contacts/contact-store.ts +3 -58
- package/src/contacts/types.ts +1 -1
- package/src/conversations/__tests__/message-consolidation.test.ts +6 -5
- package/src/conversations/message-consolidation.ts +34 -112
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +64 -7
- package/src/daemon/__tests__/conversation-tool-setup-plugin-scope.test.ts +3 -7
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +8 -6
- package/src/daemon/__tests__/embedding-reconcile.test.ts +0 -11
- package/src/daemon/__tests__/inference-profile-notification.test.ts +5 -11
- package/src/daemon/__tests__/inflight-message-content.test.ts +219 -0
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +5 -27
- package/src/daemon/__tests__/route-conversation-post.test.ts +158 -0
- package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +0 -6
- package/src/daemon/__tests__/web-search-status-text.test.ts +0 -29
- package/src/daemon/app-source-watcher.ts +17 -23
- package/src/daemon/chat-credential-redaction.ts +1365 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +742 -77
- package/src/daemon/conversation-agent-loop.ts +10 -17
- package/src/daemon/conversation-error.ts +53 -16
- package/src/daemon/conversation-history.ts +6 -12
- package/src/daemon/conversation-messaging.ts +13 -1
- package/src/daemon/conversation-process.ts +16 -11
- package/src/daemon/conversation-runtime-assembly.ts +148 -52
- package/src/daemon/conversation-surfaces.ts +217 -100
- package/src/daemon/conversation-tool-setup.ts +59 -112
- package/src/daemon/conversation-turn-finalize.ts +18 -0
- package/src/daemon/conversation.ts +35 -66
- package/src/daemon/event-loop-watchdog.test.ts +1 -6
- package/src/daemon/external-plugins-bootstrap.ts +20 -74
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -7
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +1 -8
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +1 -8
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +1 -8
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +1 -8
- package/src/daemon/handlers/conversation-history.ts +6 -4
- package/src/daemon/handlers/conversations.ts +19 -9
- package/src/daemon/handlers/shared.ts +24 -1
- package/src/daemon/inflight-message-content.ts +228 -0
- package/src/daemon/lifecycle.ts +21 -31
- package/src/daemon/message-protocol.ts +2 -7
- package/src/daemon/message-provenance.ts +17 -8
- package/src/daemon/message-types/conversations.ts +6 -17
- package/src/daemon/message-types/host-ui-snapshot.ts +44 -0
- package/src/daemon/message-types/inbox.ts +3 -46
- package/src/daemon/orphan-reaper.test.ts +1 -6
- package/src/daemon/process-message.ts +9 -0
- package/src/daemon/providers-setup.ts +5 -75
- package/src/daemon/route-conversation-post.ts +158 -0
- package/src/daemon/shutdown-handlers.ts +11 -17
- package/src/daemon/sqlite-corruption-watchdog.test.ts +0 -6
- package/src/documents/document-comments-store.test.ts +1 -7
- package/src/export/__tests__/transcript-formatter.test.ts +8 -8
- package/src/export/formatter.ts +8 -17
- package/src/export/transcript-formatter.ts +4 -1
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +0 -9
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -57
- package/src/home/__tests__/home-content-refresh.test.ts +0 -7
- package/src/home/__tests__/suggested-prompts.test.ts +0 -11
- package/src/ipc/__tests__/contacts-info-ipc.test.ts +12 -10
- package/src/ipc/__tests__/email-ipc.test.ts +90 -0
- package/src/ipc/assistant-server.ts +10 -22
- package/src/ipc/routes/__tests__/guardian-label-ipc-routes.test.ts +74 -0
- package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +0 -7
- package/src/ipc/routes/guardian-label-ipc-routes.ts +42 -0
- package/src/live-voice/__tests__/live-voice-archive.test.ts +1 -8
- package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +89 -49
- package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
- package/src/live-voice/__tests__/live-voice-resume-registry.test.ts +58 -0
- package/src/live-voice/__tests__/live-voice-resume-turn.test.ts +280 -0
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +337 -0
- package/src/live-voice/__tests__/live-voice-tts.test.ts +0 -4
- package/src/live-voice/__tests__/live-voice-vad.test.ts +432 -12
- package/src/live-voice/__tests__/protocol.test.ts +122 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +0 -41
- package/src/live-voice/live-voice-credential-preflight.ts +27 -3
- package/src/live-voice/live-voice-resume-registry.ts +54 -0
- package/src/live-voice/live-voice-session.ts +602 -61
- package/src/live-voice/protocol.ts +143 -1
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +3 -7
- package/src/messaging/provider-types.ts +9 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +0 -9
- package/src/messaging/providers/outlook/adapter.ts +17 -2
- package/src/messaging/providers/outlook/client.ts +11 -2
- package/src/messaging/providers/outlook/types.ts +9 -0
- package/src/messaging/providers/slack/__tests__/download.test.ts +1 -8
- package/src/messaging/providers/slack/binding-metadata.test.ts +23 -21
- package/src/messaging/providers/slack/send.test.ts +0 -7
- package/src/messaging/providers/slack/withdraw.test.ts +0 -6
- package/src/messaging/providers/telegram-bot/send.test.ts +0 -7
- package/src/monitoring/__tests__/plugin-source-watch.test.ts +40 -1
- package/src/monitoring/plugin-source-watch.ts +56 -62
- package/src/monitoring/recovery/__tests__/inflight-content.test.ts +166 -0
- package/src/monitoring/recovery/inflight-content.ts +260 -0
- package/src/monitoring/recovery/run-recovery.ts +72 -0
- package/src/monitoring/worker.ts +68 -7
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/README.md +4 -4
- package/src/notifications/__tests__/broadcaster.test.ts +4 -17
- package/src/notifications/__tests__/connected-channels.test.ts +11 -8
- package/src/notifications/__tests__/decision-engine.test.ts +0 -7
- package/src/notifications/__tests__/destination-resolver.test.ts +18 -12
- package/src/notifications/__tests__/deterministic-checks.test.ts +1 -9
- package/src/notifications/adapters/macos.ts +3 -5
- package/src/notifications/adapters/platform.ts +1 -1
- package/src/notifications/broadcaster.ts +13 -4
- package/src/notifications/conversation-candidates.ts +21 -28
- package/src/notifications/copy-composer.ts +0 -6
- package/src/notifications/decision-engine.ts +1 -1
- package/src/notifications/{canonical-delivery-recorder.ts → guardian-delivery-recorder.ts} +47 -33
- package/src/notifications/home-feed-side-effect.ts +0 -2
- package/src/notifications/signal.ts +0 -4
- package/src/oauth/byo-connection.test.ts +0 -9
- package/src/oauth/connection-resolver.test.ts +18 -46
- package/src/oauth/seed-providers.ts +6 -0
- package/src/onboarding/__tests__/onboarding-events-store.test.ts +122 -44
- package/src/onboarding/onboarding-events-store.ts +67 -115
- package/src/onboarding/onboarding-research-events-store.test.ts +222 -0
- package/src/onboarding/onboarding-research-events-store.ts +92 -0
- package/src/permissions/checker.test.ts +10 -24
- package/src/permissions/confirmation-guardian-request.test.ts +94 -0
- package/src/permissions/{confirmation-canonical-request.ts → confirmation-guardian-request.ts} +44 -29
- package/src/permissions/gateway-threshold-reader.test.ts +0 -8
- package/src/permissions/prompter.ts +6 -4
- package/src/permissions/question-prompter.test.ts +7 -14
- package/src/persistence/__tests__/bulk-write-gate.test.ts +1 -7
- package/src/persistence/__tests__/conversation-queries-search.test.ts +0 -7
- package/src/persistence/__tests__/main-db-readonly-probe.ts +80 -0
- package/src/persistence/__tests__/main-db-readonly.test.ts +35 -0
- package/src/persistence/__tests__/message-content-file.test.ts +161 -52
- package/src/persistence/__tests__/raw-query-telemetry.test.ts +74 -0
- package/src/persistence/conversation-crud.ts +175 -45
- package/src/persistence/conversation-disk-view.ts +32 -15
- package/src/persistence/conversation-group-migration.test.ts +1 -8
- package/src/persistence/conversation-title-service.ts +59 -22
- package/src/persistence/db-connection.ts +46 -1
- package/src/persistence/db-singleton.ts +7 -1
- package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +3 -7
- package/src/persistence/embeddings/messages-lexical-index.ts +11 -0
- package/src/persistence/job-handlers/__tests__/message-lexical-backfill.test.ts +0 -7
- package/src/persistence/job-handlers/__tests__/message-lexical-enqueue.test.ts +0 -6
- package/src/persistence/job-handlers/__tests__/message-lexical.test.ts +0 -7
- package/src/persistence/job-handlers/cleanup.ts +30 -16
- package/src/persistence/job-handlers/message-lexical-backfill.ts +8 -5
- package/src/persistence/job-handlers/message-lexical.ts +2 -2
- package/src/persistence/jobs-store.ts +123 -17
- package/src/persistence/lifecycle-events-store.test.ts +84 -0
- package/src/persistence/lifecycle-events-store.ts +27 -52
- package/src/persistence/llm-request-log-sink-clickhouse.ts +325 -0
- package/src/persistence/llm-request-log-store.ts +185 -106
- package/src/persistence/llm-request-log-writer-types.ts +80 -0
- package/src/persistence/llm-usage-store.ts +5 -2
- package/src/persistence/message-content-file.ts +182 -71
- package/src/persistence/message-content.ts +18 -10
- package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
- package/src/persistence/migrations/325-create-config-setting-events.ts +40 -0
- package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +104 -0
- package/src/persistence/migrations/327-create-flush-checkpoints.ts +86 -0
- package/src/persistence/migrations/328-drop-memory-v3-learned-edge-tables.ts +16 -0
- package/src/persistence/migrations/329-move-onboarding-events-to-telemetry-db.test.ts +179 -0
- package/src/persistence/migrations/329-move-onboarding-events-to-telemetry-db.ts +119 -0
- package/src/persistence/migrations/330-move-auth-fallback-events-to-telemetry-db.test.ts +197 -0
- package/src/persistence/migrations/330-move-auth-fallback-events-to-telemetry-db.ts +108 -0
- package/src/persistence/migrations/331-move-lifecycle-events-to-telemetry-db.test.ts +159 -0
- package/src/persistence/migrations/331-move-lifecycle-events-to-telemetry-db.ts +97 -0
- package/src/persistence/migrations/332-move-skill-loaded-events-to-telemetry-db.test.ts +219 -0
- package/src/persistence/migrations/332-move-skill-loaded-events-to-telemetry-db.ts +128 -0
- package/src/persistence/migrations/333-create-telemetry-events-table.test.ts +66 -0
- package/src/persistence/migrations/333-create-telemetry-events-table.ts +43 -0
- package/src/persistence/migrations/334-backfill-telemetry-events-outbox.test.ts +584 -0
- package/src/persistence/migrations/334-backfill-telemetry-events-outbox.ts +386 -0
- package/src/persistence/migrations/335-collapse-memory-embed-backlog.test.ts +181 -0
- package/src/persistence/migrations/335-collapse-memory-embed-backlog.ts +177 -0
- package/src/persistence/migrations/__tests__/327-create-flush-checkpoints.test.ts +124 -0
- package/src/persistence/raw-query.ts +27 -1
- package/src/persistence/schema/calls.ts +0 -23
- package/src/persistence/schema/conversation-starters.ts +31 -0
- package/src/persistence/schema/conversations.ts +3 -3
- package/src/persistence/schema/guardian.ts +0 -150
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/infrastructure.ts +25 -72
- package/src/persistence/schema/memory-core.ts +0 -25
- package/src/persistence/schema/memory-injection.ts +3 -1
- package/src/persistence/schema/tasks.ts +1 -60
- package/src/persistence/steps.ts +22 -0
- package/src/platform/client.test.ts +110 -0
- package/src/platform/client.ts +24 -4
- package/src/platform/consent-cache.test.ts +5 -18
- package/src/platform/consent-cache.ts +8 -6
- package/src/platform/managed-speech.test.ts +218 -0
- package/src/platform/managed-speech.ts +204 -0
- package/src/plugin-api/conversation-turn.ts +250 -0
- package/src/plugin-api/index.ts +21 -0
- package/src/plugin-api/model-profiles.test.ts +29 -28
- package/src/plugin-api/vision-support.test.ts +25 -38
- package/src/plugin-api/vision-support.ts +26 -17
- package/src/plugins/AGENTS.md +8 -0
- package/src/plugins/__tests__/source-fingerprint.test.ts +41 -0
- package/src/plugins/collect-source-versions.ts +77 -0
- package/src/plugins/defaults/image-recovery/detect.ts +32 -10
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +20 -18
- package/src/plugins/defaults/image-recovery/recover.ts +86 -59
- package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +1 -8
- package/src/plugins/defaults/memory/__tests__/clear-stale-processing-flags.test.ts +1 -8
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +1 -8
- package/src/plugins/defaults/memory/__tests__/conversation-row-batch-delete.test.ts +7 -18
- package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +22 -18
- package/src/plugins/defaults/memory/__tests__/embedding-cache.test.ts +0 -6
- package/src/plugins/defaults/memory/__tests__/find-most-recent-retrospective-for.test.ts +1 -8
- package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +7 -18
- package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +29 -39
- package/src/plugins/defaults/memory/__tests__/jobs-worker-memory-disabled.test.ts +11 -26
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +3 -22
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +215 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +0 -7
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +114 -16
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +180 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-skill-card.test.ts +153 -519
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +0 -7
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +6 -17
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -7
- package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +1 -19
- package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +0 -18
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +8 -7
- package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +49 -0
- package/src/{persistence → plugins/defaults/memory}/__tests__/worker-control.test.ts +8 -10
- package/src/plugins/defaults/memory/graph/capability-seed.ts +4 -6
- package/src/plugins/defaults/memory/graph/extraction.ts +5 -1
- package/src/plugins/defaults/memory/graph/graph-search.test.ts +2 -8
- package/src/plugins/defaults/memory/graph/tool-handlers.test.ts +0 -6
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +2 -2
- package/src/plugins/defaults/memory/graph/tools.ts +47 -13
- package/src/plugins/defaults/memory/graph-topology/__tests__/build-memory-graph.test.ts +81 -2
- package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +67 -32
- package/src/plugins/defaults/memory/hooks/shutdown.ts +8 -11
- package/src/plugins/defaults/memory/indexer.ts +2 -1
- package/src/plugins/defaults/memory/job-handlers/backfill.ts +8 -5
- package/src/plugins/defaults/memory/job-handlers/embedding.test.ts +0 -7
- package/src/plugins/defaults/memory/job-handlers/embedding.ts +2 -2
- package/src/plugins/defaults/memory/job-handlers/index-maintenance.ts +7 -2
- package/src/plugins/defaults/memory/jobs/__tests__/embed-concept-page.test.ts +73 -8
- package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +5 -3
- package/src/plugins/defaults/memory/jobs/embed-pkb-file.test.ts +0 -13
- package/src/plugins/defaults/memory/jobs-worker.ts +139 -107
- package/src/plugins/defaults/memory/memory-db.ts +27 -0
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +4 -4
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +56 -180
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
- package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +122 -202
- package/src/plugins/defaults/memory/message-media.ts +2 -2
- package/src/plugins/defaults/memory/paths.ts +3 -2
- package/src/plugins/defaults/memory/pkb/pkb-index.test.ts +0 -12
- package/src/plugins/defaults/memory/pkb/pkb-reconcile.test.ts +0 -9
- package/src/plugins/defaults/memory/pkb/pkb-search.test.ts +0 -6
- package/src/plugins/defaults/memory/routes/__tests__/memory-v2-simulate-route.test.ts +15 -92
- package/src/plugins/defaults/memory/routes/__tests__/memory-worker-routes.test.ts +201 -0
- package/src/plugins/defaults/memory/routes/memory-item-routes.test.ts +7 -21
- package/src/plugins/defaults/memory/routes/memory-v2-routes.ts +3 -8
- package/src/plugins/defaults/memory/routes/memory-worker-routes.ts +162 -0
- package/src/plugins/defaults/memory/startup.ts +6 -7
- package/src/plugins/defaults/memory/task-memory-cleanup.ts +11 -22
- package/src/plugins/defaults/memory/tools.test.ts +40 -8
- package/src/plugins/defaults/memory/tools.ts +12 -2
- package/src/plugins/defaults/memory/v2/__tests__/activation.test.ts +0 -19
- package/src/plugins/defaults/memory/v2/__tests__/backfill-jobs.test.ts +63 -40
- package/src/plugins/defaults/memory/v2/__tests__/cli-command-store.test.ts +61 -130
- package/src/plugins/defaults/memory/v2/__tests__/consolidation-job.test.ts +406 -10
- package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +1 -10
- package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +1 -10
- package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +1 -10
- package/src/plugins/defaults/memory/v2/__tests__/injection-events.test.ts +61 -69
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +0 -19
- package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +11 -10
- package/src/plugins/defaults/memory/v2/__tests__/page-index.test.ts +0 -5
- package/src/plugins/defaults/memory/v2/__tests__/qdrant.test.ts +6 -22
- package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +3 -5
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +7 -20
- package/src/plugins/defaults/memory/v2/__tests__/sim.test.ts +0 -24
- package/src/plugins/defaults/memory/v2/__tests__/skill-content.test.ts +17 -11
- package/src/plugins/defaults/memory/v2/__tests__/skill-store.test.ts +0 -16
- package/src/plugins/defaults/memory/v2/__tests__/static-context.test.ts +0 -10
- package/src/plugins/defaults/memory/v2/__tests__/sweep-job.test.ts +0 -6
- package/src/plugins/defaults/memory/v2/backfill-jobs.ts +5 -2
- package/src/plugins/defaults/memory/v2/cli-command-content.ts +43 -12
- package/src/plugins/defaults/memory/v2/cli-command-store.ts +34 -50
- package/src/plugins/defaults/memory/v2/consolidation-job.ts +216 -10
- package/src/plugins/defaults/memory/v2/harness/router-retriever.ts +1 -8
- package/src/plugins/defaults/memory/v2/injection-events.ts +16 -14
- package/src/plugins/defaults/memory/v2/injection.ts +1 -2
- package/src/plugins/defaults/memory/v2/memory-v2-startup.test.ts +0 -5
- package/src/plugins/defaults/memory/v2/migration.ts +55 -24
- package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
- package/src/plugins/defaults/memory/v2/router.ts +4 -14
- package/src/plugins/defaults/memory/v2/sweep-job.ts +1 -0
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +10 -22
- package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +0 -5
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -20
- package/src/plugins/defaults/memory/v3/__tests__/section-dense-store.test.ts +0 -5
- package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +5 -9
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +51 -31
- package/src/plugins/defaults/memory/v3/prune.test.ts +3 -11
- package/src/{persistence → plugins/defaults/memory}/worker-control.ts +12 -21
- package/src/plugins/defaults/memory/worker.ts +19 -2
- package/src/plugins/mtime-cache.ts +166 -70
- package/src/plugins/plugin-tree-walk.ts +41 -4
- package/src/plugins/source-fingerprint.ts +9 -3
- package/src/plugins/surface-import.ts +28 -0
- package/src/plugins/types.ts +9 -23
- package/src/prompts/__tests__/system-prompt.test.ts +0 -28
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +0 -28
- package/src/providers/__tests__/connection-model-compat.test.ts +6 -19
- package/src/providers/__tests__/count-tokens-forwarding.test.ts +1 -21
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +9 -26
- package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
- package/src/providers/__tests__/retry-callsite.test.ts +240 -235
- package/src/providers/__tests__/satellite-connection-routing.test.ts +11 -36
- package/src/providers/__tests__/vellum-mismatch-routing.test.ts +0 -5
- package/src/providers/anthropic/client.ts +9 -3
- package/src/providers/call-site-routing.ts +7 -3
- package/src/providers/connection-resolution.ts +9 -11
- package/src/providers/content-block-schema.ts +149 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +7 -11
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +1 -15
- package/src/providers/inference/auth.ts +23 -0
- package/src/providers/inference/backfill.ts +25 -9
- package/src/providers/model-catalog.ts +213 -5
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +54 -0
- package/src/providers/openai/chat-completions-provider.ts +13 -2
- package/src/providers/openai/responses-provider.ts +232 -5
- package/src/providers/openrouter/client.ts +53 -0
- package/src/providers/retry.ts +25 -2
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +13 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +211 -74
- package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +408 -0
- package/src/providers/speech-to-text/__tests__/vellum-managed.test.ts +121 -0
- package/src/providers/speech-to-text/__tests__/vellum-speech-relay-connection.test.ts +126 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +167 -37
- package/src/providers/speech-to-text/incremental-batch-stream.ts +413 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +16 -363
- package/src/providers/speech-to-text/provider-catalog.ts +26 -1
- package/src/providers/speech-to-text/resolve.ts +74 -13
- package/src/providers/speech-to-text/vellum-managed-realtime.ts +309 -0
- package/src/providers/speech-to-text/vellum-managed.ts +71 -0
- package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +174 -0
- package/src/providers/types.ts +23 -4
- package/src/providers/vellum-model-routing.test.ts +26 -0
- package/src/providers/vellum-model-routing.ts +30 -0
- package/src/runtime/AGENTS.md +1 -1
- package/src/runtime/__tests__/actor-trust-resolver.test.ts +2 -8
- package/src/runtime/__tests__/agent-wake.test.ts +100 -27
- package/src/runtime/__tests__/background-job-runner.test.ts +22 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +0 -5
- package/src/runtime/__tests__/member-verdict-cache.test.ts +2 -9
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +22 -5
- package/src/runtime/access-request-helper.ts +84 -60
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/agent-wake.ts +75 -26
- package/src/runtime/assistant-event-hub.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +1 -10
- package/src/runtime/auth/__tests__/route-policy.test.ts +0 -7
- package/src/runtime/auth/__tests__/token-service.test.ts +1 -8
- package/src/runtime/auth/same-actor.ts +2 -1
- package/src/runtime/background-job-runner.ts +22 -0
- package/src/runtime/channel-approval-types.ts +1 -1
- package/src/runtime/channel-reply-delivery.ts +38 -19
- package/src/runtime/confirmation-request-guardian-bridge.ts +45 -39
- package/src/runtime/for-chat-mint-registry.ts +118 -0
- package/src/runtime/guardian-action-service.ts +32 -27
- package/src/runtime/guardian-decision-types.ts +2 -2
- package/src/runtime/guardian-reply-router.ts +80 -98
- package/src/runtime/http-server.ts +61 -41
- package/src/runtime/introduction-policy.ts +2 -2
- package/src/runtime/local-actor-identity.ts +19 -7
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/reveal-nonce.ts +49 -0
- package/src/runtime/reveal-success-registry.ts +306 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +0 -7
- package/src/runtime/routes/__tests__/backup-routes.test.ts +195 -125
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +4 -1
- package/src/runtime/routes/__tests__/client-routes.test.ts +0 -7
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +1 -19
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +1 -8
- package/src/runtime/routes/__tests__/contact-routes-merge-relay.test.ts +186 -0
- package/src/runtime/routes/__tests__/contact-routes-update-channel-relay.test.ts +2 -3
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +0 -7
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +5 -27
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +0 -7
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +12 -30
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +59 -56
- package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +0 -7
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +53 -73
- package/src/runtime/routes/__tests__/gateway-status-routes.test.ts +84 -0
- package/src/runtime/routes/__tests__/inference-callsites-routes.test.ts +2 -10
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +58 -90
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -93
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +6 -1
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +0 -8
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +218 -64
- package/src/runtime/routes/__tests__/question-routes.test.ts +2 -4
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +1 -8
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +0 -7
- package/src/runtime/routes/__tests__/schedule-routes-create.test.ts +2 -1
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +16 -56
- package/src/runtime/routes/__tests__/stt-routes.test.ts +0 -9
- package/src/runtime/routes/__tests__/suggest-trust-rule-routes.test.ts +0 -7
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -0
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +354 -1
- package/src/runtime/routes/__tests__/tts-routes.test.ts +0 -11
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +163 -10
- package/src/runtime/routes/__tests__/user-routes-cli.test.ts +154 -0
- package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +0 -7
- package/src/runtime/routes/acp-routes.ts +21 -9
- package/src/runtime/routes/app-management-routes.ts +231 -60
- package/src/runtime/routes/app-routes.ts +130 -83
- package/src/runtime/routes/auth-routes.ts +3 -1
- package/src/runtime/routes/background-wake-routes.ts +0 -1
- package/src/runtime/routes/cache-routes.ts +3 -3
- package/src/runtime/routes/call-routes.ts +5 -1
- package/src/runtime/routes/channel-verification-routes.ts +2 -1
- package/src/runtime/routes/contact-prompt-routes.ts +11 -5
- package/src/runtime/routes/contact-routes.ts +34 -42
- package/src/runtime/routes/conversation-attention-routes.ts +1 -1
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +4 -4
- package/src/runtime/routes/conversation-routes.ts +111 -72
- package/src/runtime/routes/credential-routes.ts +116 -3
- package/src/runtime/routes/email-routes.ts +17 -1
- package/src/runtime/routes/events-routes.ts +1 -0
- package/src/runtime/routes/gateway-status-routes.ts +69 -0
- package/src/runtime/routes/guardian-action-routes.ts +18 -16
- package/src/runtime/routes/guardian-approval-interception.ts +6 -4
- package/src/runtime/routes/guardian-approval-prompt.ts +7 -5
- package/src/runtime/routes/guardian-expiry-sweep.ts +130 -0
- package/src/runtime/routes/inbound-message-handler.ts +9 -33
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +9 -9
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +24 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +0 -7
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -5
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +6 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +5 -1
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +0 -9
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +4 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +3 -12
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +16 -14
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +8 -2
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +15 -7
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -9
- package/src/runtime/routes/index.ts +5 -5
- package/src/runtime/routes/inference-provider-connection-routes.ts +73 -24
- package/src/runtime/routes/inference-send-routes.ts +1 -1
- package/src/runtime/routes/internal-telemetry-routes.ts +11 -2
- package/src/runtime/routes/oauth-connect-routes.ts +27 -7
- package/src/runtime/routes/platform-routes.ts +4 -14
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +0 -4
- package/src/runtime/routes/playground/__tests__/guard.test.ts +0 -4
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +0 -14
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +0 -21
- package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +0 -4
- package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +16 -18
- package/src/runtime/routes/playground/__tests__/state.test.ts +0 -14
- package/src/runtime/routes/playground/seed-conversation.ts +5 -4
- package/src/runtime/routes/playground/seeded-conversations.ts +3 -2
- package/src/runtime/routes/plugins-routes.ts +163 -25
- package/src/runtime/routes/profiler-routes.ts +2 -5
- package/src/runtime/routes/publish-routes.ts +15 -18
- package/src/runtime/routes/question-routes.ts +8 -4
- package/src/runtime/routes/schedule-routes.ts +20 -78
- package/src/runtime/routes/schedule-worker-routes.ts +16 -84
- package/src/runtime/routes/secret-routes.ts +14 -0
- package/src/runtime/routes/settings-routes.ts +21 -25
- package/src/runtime/routes/subagents-routes.ts +17 -18
- package/src/runtime/routes/surface-action-routes.ts +18 -39
- package/src/runtime/routes/surface-content-routes.ts +59 -1
- package/src/runtime/routes/surface-conversation-resolver.ts +148 -4
- package/src/runtime/routes/telemetry-routes.ts +74 -1
- package/src/runtime/routes/tts-routes.ts +9 -5
- package/src/runtime/routes/ui-snapshot-routes.test.ts +219 -0
- package/src/runtime/routes/ui-snapshot-routes.ts +313 -0
- package/src/runtime/routes/user-route-dispatcher.ts +52 -53
- package/src/runtime/routes/user-route-resolution.ts +141 -0
- package/src/runtime/routes/user-routes-cli.ts +56 -35
- package/src/runtime/routes/user-routes.ts +9 -2
- package/src/runtime/routes/vellum-actor-trust.ts +67 -0
- package/src/runtime/services/__tests__/auto-analysis-guard.test.ts +1 -8
- package/src/runtime/tool-grant-request-helper.ts +34 -30
- package/src/runtime/trust-verdict-consumer.ts +4 -6
- package/src/schedule/__tests__/worker-control.test.ts +3 -3
- package/src/schedule/schedule-store.ts +0 -41
- package/src/schedule/scheduler.ts +38 -257
- package/src/schedule/worker-control.ts +11 -21
- package/src/schedule/worker.ts +18 -5
- package/src/security/__tests__/oauth2-device-code.test.ts +12 -60
- package/src/security/__tests__/provider-key-env-fallback.test.ts +0 -10
- package/src/security/auth-fallback-events-store.ts +34 -72
- package/src/security/secret-scanner.ts +26 -1
- package/src/sequence/engine.ts +15 -6
- package/src/services/published-app-updater.ts +6 -11
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +24 -4
- package/src/stt/daemon-batch-transcriber.ts +33 -2
- package/src/stt/stt-stream-session.ts +17 -11
- package/src/stt/types.ts +2 -1
- package/src/subagent/manager.ts +4 -3
- package/src/telemetry/AGENTS.md +44 -0
- package/src/telemetry/__tests__/config-setting-events-store.test.ts +63 -0
- package/src/telemetry/__tests__/config-setting-snapshot.test.ts +107 -0
- package/src/telemetry/__tests__/flush-checkpoints.test.ts +56 -0
- package/src/telemetry/__tests__/outbox-test-harness.ts +83 -0
- package/src/telemetry/__tests__/telemetry-event-fixtures.ts +258 -0
- package/src/telemetry/__tests__/watchdog-events-store.test.ts +32 -122
- package/src/telemetry/config-setting-events-store.ts +40 -0
- package/src/telemetry/config-setting-snapshot.ts +135 -0
- package/src/telemetry/flush-checkpoints.ts +70 -0
- package/src/telemetry/skill-loaded-events-store.test.ts +54 -113
- package/src/telemetry/skill-loaded-events-store.ts +21 -77
- package/src/telemetry/telemetry-event-sources.test.ts +211 -0
- package/src/telemetry/telemetry-event-sources.ts +582 -0
- package/src/telemetry/telemetry-events-outbox.test.ts +330 -0
- package/src/telemetry/telemetry-events-outbox.ts +204 -0
- package/src/telemetry/telemetry-main-db.ts +48 -0
- package/src/telemetry/telemetry-wire-source.json +4 -0
- package/src/telemetry/telemetry-wire-validation.test.ts +157 -0
- package/src/telemetry/telemetry-wire-validation.ts +125 -0
- package/src/telemetry/telemetry-wire.generated.ts +433 -0
- package/src/telemetry/tool-executed-events-store.test.ts +0 -8
- package/src/telemetry/tool-executed-events-store.ts +2 -2
- package/src/telemetry/turn-trace-store.test.ts +86 -7
- package/src/telemetry/turn-trace-store.ts +29 -1
- package/src/telemetry/types.test.ts +38 -0
- package/src/telemetry/types.ts +302 -87
- package/src/telemetry/usage-telemetry-reporter.test.ts +921 -364
- package/src/telemetry/usage-telemetry-reporter.ts +235 -722
- package/src/telemetry/watchdog-direct-emit.test.ts +0 -5
- package/src/telemetry/watchdog-direct-emit.ts +7 -1
- package/src/telemetry/watchdog-events-store.ts +11 -70
- package/src/tools/acp/spawn.test.ts +0 -7
- package/src/tools/apps/definitions.ts +1 -7
- package/src/tools/apps/executors.ts +11 -36
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +11 -24
- package/src/tools/browser/__tests__/browser-status.test.ts +47 -33
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +1 -11
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +75 -58
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +0 -10
- package/src/tools/browser/network-recorder.test.ts +0 -11
- package/src/tools/document/document-comment-tool.test.ts +1 -7
- package/src/tools/host-terminal/host-shell.test.ts +0 -32
- package/src/tools/host-terminal/host-shell.ts +6 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +13 -15
- package/src/tools/network/__tests__/web-search-metadata.test.ts +15 -18
- package/src/tools/network/__tests__/web-search.test.ts +39 -50
- package/src/tools/registry.ts +148 -196
- package/src/tools/skills/find-similar-skills.test.ts +0 -7
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/skills/scaffold-managed.ts +105 -12
- package/src/tools/subagent/read.ts +6 -4
- package/src/tools/subagent/spawn.ts +20 -29
- package/src/tools/terminal/safe-env.ts +7 -2
- package/src/tools/terminal/shell.test.ts +4 -8
- package/src/tools/terminal/shell.ts +5 -2
- package/src/tools/tool-approval-handler.ts +39 -23
- package/src/tools/tool-input-summary.ts +2 -1
- package/src/tools/tool-manifest.ts +20 -6
- package/src/tools/tool-profiler.ts +37 -6
- package/src/tools/ui-surface/definitions.ts +1 -7
- package/src/tools/workflows/run-workflow.test.ts +8 -22
- package/src/tts/__tests__/provider-adapters.test.ts +28 -22
- package/src/tts/__tests__/provider-resolution.test.ts +26 -0
- package/src/tts/__tests__/synthesis-stream.test.ts +6 -6
- package/src/tts/__tests__/vellum-provider.test.ts +251 -0
- package/src/tts/provider-catalog.ts +6 -0
- package/src/tts/providers/__tests__/vellum-tts-socket.test.ts +266 -0
- package/src/tts/providers/vellum-provider.ts +220 -0
- package/src/tts/providers/vellum-tts-socket.ts +377 -0
- package/src/tts/synthesize-text.ts +15 -5
- package/src/tts/tts-config-resolver.ts +2 -7
- package/src/tts/types.ts +1 -0
- package/src/usage/attribution.ts +20 -114
- package/src/util/abort-reasons.ts +1 -3
- package/src/util/image-conversion.ts +23 -5
- package/src/util/logger.ts +8 -3
- package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
- package/src/util/platform.ts +0 -60
- package/src/util/pricing.ts +10 -1
- package/src/util/telemetry-db-path.ts +9 -7
- package/src/watcher/__tests__/telemetry.test.ts +41 -11
- package/src/watcher/providers/google-calendar.ts +81 -49
- package/src/watcher/telemetry.ts +22 -5
- package/src/workflows/engine-integration.test.ts +0 -6
- package/src/workflows/engine.test.ts +1 -9
- package/src/workflows/journal-store.test.ts +1 -9
- package/src/workflows/leaf-runner.test.ts +17 -50
- package/src/workflows/library.test.ts +1 -9
- package/src/workflows/run-manager.test.ts +1 -9
- package/src/workspace/git-service.ts +932 -37
- package/src/workspace/provider-commit-message-generator.ts +6 -4
- package/__tests__/permissions/gateway-threshold-reader.test.ts +0 -450
- package/src/__tests__/canonical-guardian-store.test.ts +0 -928
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -140
- package/src/__tests__/plugin-route-contribution.test.ts +0 -245
- package/src/__tests__/skill-runtime-path.test.ts +0 -63
- package/src/__tests__/task-compiler.test.ts +0 -317
- package/src/__tests__/task-management-tools.test.ts +0 -1128
- package/src/__tests__/task-scheduler.test.ts +0 -786
- package/src/config/schemas/schedules.ts +0 -35
- package/src/contacts/canonical-guardian-store.ts +0 -902
- package/src/daemon/message-types/work-items.ts +0 -243
- package/src/runtime/routes/__tests__/memory-worker-routes.test.ts +0 -306
- package/src/runtime/routes/__tests__/task-routes.test.ts +0 -644
- package/src/runtime/routes/assets/vellum-design-system.css +0 -1959
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +0 -129
- package/src/runtime/routes/inbound-stages/escalation-intercept.test.ts +0 -158
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +0 -187
- package/src/runtime/routes/memory-worker-routes.ts +0 -227
- package/src/runtime/routes/task-routes.ts +0 -380
- package/src/runtime/routes/work-items-routes.test.ts +0 -75
- package/src/runtime/routes/work-items-routes.ts +0 -881
- package/src/runtime/skill-route-registry.ts +0 -131
- package/src/tasks/SPEC.md +0 -132
- package/src/tasks/task-compiler.ts +0 -194
- package/src/tasks/task-runner.ts +0 -119
- package/src/tasks/task-scheduler.ts +0 -22
- package/src/tasks/task-store.ts +0 -153
- package/src/tasks/tool-sanitizer.ts +0 -38
- package/src/tools/apps/registry.ts +0 -16
- package/src/tools/system/register.ts +0 -9
- package/src/tools/tasks/task-delete.ts +0 -121
- package/src/tools/tasks/task-list.ts +0 -56
- package/src/tools/tasks/task-run.ts +0 -122
- package/src/tools/tasks/task-save.ts +0 -64
- package/src/tools/tasks/work-item-enqueue.ts +0 -300
- package/src/tools/tasks/work-item-list.ts +0 -65
- package/src/tools/tasks/work-item-remove.ts +0 -94
- package/src/tools/tasks/work-item-run.ts +0 -99
- package/src/tools/tasks/work-item-update.ts +0 -166
- package/src/tools/ui-surface/registry.ts +0 -14
- package/src/work-items/resolve-required-tools.test.ts +0 -37
- package/src/work-items/resolve-required-tools.ts +0 -26
- package/src/work-items/work-item-runner.ts +0 -187
- package/src/work-items/work-item-store.ts +0 -416
|
@@ -11,17 +11,20 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
11
11
|
// Module mocks (must precede production imports)
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
14
|
+
// Watermark storage fake, injected via the reporter's constructor (see
|
|
15
|
+
// `makeReporter` below) — the real store lives on the telemetry DB and has
|
|
16
|
+
// its own DB-backed test, so it is deliberately NOT module-mocked here.
|
|
17
|
+
const mockGetFlushCheckpoint = mock<(key: string) => string | null>(() => null);
|
|
18
|
+
const mockSetFlushCheckpoint = mock<(key: string, value: string) => void>(
|
|
18
19
|
() => {},
|
|
19
20
|
);
|
|
21
|
+
const mockIsFlushCheckpointStoreAvailable = mock<() => boolean>(() => true);
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const fakeFlushCheckpointStore = {
|
|
24
|
+
isAvailable: () => mockIsFlushCheckpointStoreAvailable(),
|
|
25
|
+
get: (key: string) => mockGetFlushCheckpoint(key),
|
|
26
|
+
set: (key: string, value: string) => mockSetFlushCheckpoint(key, value),
|
|
27
|
+
};
|
|
25
28
|
|
|
26
29
|
const mockQueryUnreportedUsageEvents = mock(
|
|
27
30
|
() =>
|
|
@@ -84,13 +87,6 @@ mock.module("../util/device-id.js", () => ({
|
|
|
84
87
|
getDeviceId: mockGetDeviceId,
|
|
85
88
|
}));
|
|
86
89
|
|
|
87
|
-
mock.module("../util/logger.js", () => ({
|
|
88
|
-
getLogger: () =>
|
|
89
|
-
new Proxy({} as Record<string, unknown>, {
|
|
90
|
-
get: () => () => {},
|
|
91
|
-
}),
|
|
92
|
-
}));
|
|
93
|
-
|
|
94
90
|
mock.module("../version.js", () => ({
|
|
95
91
|
APP_VERSION: "1.2.3-test",
|
|
96
92
|
}));
|
|
@@ -100,7 +96,7 @@ const mockGetCachedShareAnalytics = mock(() => true);
|
|
|
100
96
|
const mockGetCachedShareDiagnostics = mock(() => false);
|
|
101
97
|
// Owner's accepted diagnostics-consent version — the disclosing-version part of
|
|
102
98
|
// the trace gate. Default to a far-future (unconditionally eligible) version so
|
|
103
|
-
// the consent
|
|
99
|
+
// the consent cases drive eligibility on that axis; the version-specific
|
|
104
100
|
// cases override it with old/empty values.
|
|
105
101
|
const mockGetCachedShareDiagnosticsVersion = mock(() => "2999-01-01");
|
|
106
102
|
|
|
@@ -110,28 +106,14 @@ mock.module("../platform/consent-cache.js", () => ({
|
|
|
110
106
|
getCachedShareDiagnosticsVersion: mockGetCachedShareDiagnosticsVersion,
|
|
111
107
|
}));
|
|
112
108
|
|
|
113
|
-
// The `trace-collection` feature flag — the other half of the trace gate.
|
|
114
|
-
const mockIsAssistantFeatureFlagEnabled = mock(
|
|
115
|
-
(_key: string, _config: unknown): boolean => true,
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
mock.module("../config/assistant-feature-flags.js", () => ({
|
|
119
|
-
isAssistantFeatureFlagEnabled: mockIsAssistantFeatureFlagEnabled,
|
|
120
|
-
}));
|
|
121
|
-
|
|
122
|
-
// Stub config — the flag checker is mocked, so the contents don't matter; this
|
|
123
|
-
// just keeps `getConfig()` from touching the real loader / filesystem.
|
|
124
|
-
mock.module("../config/loader.js", () => ({
|
|
125
|
-
getConfig: () => ({}),
|
|
126
|
-
}));
|
|
127
|
-
|
|
128
109
|
interface MockTurnTrace {
|
|
129
|
-
schema_version:
|
|
110
|
+
schema_version: 3;
|
|
130
111
|
messages: {
|
|
131
112
|
id: string;
|
|
132
113
|
role: string;
|
|
133
114
|
created_at: number;
|
|
134
115
|
content: unknown;
|
|
116
|
+
model: string | null;
|
|
135
117
|
}[];
|
|
136
118
|
tool_calls: unknown[];
|
|
137
119
|
system_prompt: string | null;
|
|
@@ -150,13 +132,14 @@ function defaultBoundedTurnTrace(boundary: {
|
|
|
150
132
|
userMessageCreatedAt: number;
|
|
151
133
|
}): MockTurnTrace | null {
|
|
152
134
|
return {
|
|
153
|
-
schema_version:
|
|
135
|
+
schema_version: 3,
|
|
154
136
|
messages: [
|
|
155
137
|
{
|
|
156
138
|
id: boundary.userMessageId,
|
|
157
139
|
role: "user",
|
|
158
140
|
created_at: boundary.userMessageCreatedAt,
|
|
159
141
|
content: [{ type: "text", text: "hello" }],
|
|
142
|
+
model: null,
|
|
160
143
|
},
|
|
161
144
|
],
|
|
162
145
|
tool_calls: [],
|
|
@@ -188,49 +171,11 @@ mock.module("./turn-trace-store.js", () => ({
|
|
|
188
171
|
isTurnSettled: mockIsTurnSettled,
|
|
189
172
|
}));
|
|
190
173
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
createdAt: number;
|
|
197
|
-
}[],
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
mock.module("../persistence/lifecycle-events-store.js", () => ({
|
|
201
|
-
queryUnreportedLifecycleEvents: mockQueryUnreportedLifecycleEvents,
|
|
202
|
-
}));
|
|
203
|
-
|
|
204
|
-
const mockQueryUnreportedOnboardingEvents = mock(
|
|
205
|
-
() =>
|
|
206
|
-
[] as {
|
|
207
|
-
id: string;
|
|
208
|
-
createdAt: number;
|
|
209
|
-
screen: string;
|
|
210
|
-
toolsJson: string | null;
|
|
211
|
-
tasksJson: string | null;
|
|
212
|
-
tone: string | null;
|
|
213
|
-
googleConnected: boolean | null;
|
|
214
|
-
googleScopesJson: string | null;
|
|
215
|
-
priorAssistantsJson: string | null;
|
|
216
|
-
abVariant: string | null;
|
|
217
|
-
sessionId: string | null;
|
|
218
|
-
stepName: string | null;
|
|
219
|
-
stepIndex: number | null;
|
|
220
|
-
completedAt: string | null;
|
|
221
|
-
funnelVersion: string | null;
|
|
222
|
-
}[],
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
mock.module("../onboarding/onboarding-events-store.js", () => ({
|
|
226
|
-
queryUnreportedOnboardingEvents: mockQueryUnreportedOnboardingEvents,
|
|
227
|
-
}));
|
|
228
|
-
|
|
229
|
-
// The auth-fallback, tool-executed, and skill-loaded stores are intentionally
|
|
230
|
-
// NOT mocked — they have their own DB-backed tests, and Bun's `mock.module`
|
|
231
|
-
// is process-global, so mocking them here would leak into those tests when
|
|
232
|
-
// files share an invocation. We seed the real DB instead so every test stays
|
|
233
|
-
// order-independent.
|
|
174
|
+
// The auth-fallback, tool-executed, and skill-loaded stores are
|
|
175
|
+
// intentionally NOT mocked — they have their own DB-backed tests, and Bun's
|
|
176
|
+
// `mock.module` is process-global, so mocking them here would leak into those
|
|
177
|
+
// tests when files share an invocation. We seed the real DB instead so every
|
|
178
|
+
// test stays order-independent.
|
|
234
179
|
|
|
235
180
|
// ---------------------------------------------------------------------------
|
|
236
181
|
// Production import (after mocks)
|
|
@@ -241,12 +186,16 @@ import {
|
|
|
241
186
|
TOOL_INVOCATION_PII_SENTINEL as TOOL_PII_SENTINEL,
|
|
242
187
|
type ToolInvocationSeedSpec,
|
|
243
188
|
} from "../__tests__/test-support/tool-invocation-seed.js";
|
|
244
|
-
import {
|
|
189
|
+
import {
|
|
190
|
+
recordActivationEvent,
|
|
191
|
+
recordOnboardingEvent,
|
|
192
|
+
} from "../onboarding/onboarding-events-store.js";
|
|
193
|
+
import { getDb, getTelemetryDb } from "../persistence/db-connection.js";
|
|
245
194
|
import { initializeDb } from "../persistence/db-init.js";
|
|
195
|
+
import { recordLifecycleEvent } from "../persistence/lifecycle-events-store.js";
|
|
246
196
|
import {
|
|
247
|
-
authFallbackEvents,
|
|
248
197
|
conversations,
|
|
249
|
-
|
|
198
|
+
telemetryEvents,
|
|
250
199
|
toolInvocations,
|
|
251
200
|
} from "../persistence/schema/index.js";
|
|
252
201
|
import { recordAuthFallbackCounts } from "../security/auth-fallback-events-store.js";
|
|
@@ -255,8 +204,37 @@ import {
|
|
|
255
204
|
ACTIVATION_FUNNEL_VERSION,
|
|
256
205
|
buildActivationDaemonEventId,
|
|
257
206
|
} from "./activation-funnel.js";
|
|
207
|
+
import { recordConfigSettingEvent } from "./config-setting-events-store.js";
|
|
258
208
|
import { recordSkillLoadedEvent } from "./skill-loaded-events-store.js";
|
|
259
|
-
import {
|
|
209
|
+
import {
|
|
210
|
+
ALL_TELEMETRY_EVENT_SOURCES,
|
|
211
|
+
DAEMON_TELEMETRY_EVENT_SOURCES,
|
|
212
|
+
MONITOR_TELEMETRY_EVENT_SOURCES,
|
|
213
|
+
outboxSource,
|
|
214
|
+
type TelemetryEventSource,
|
|
215
|
+
} from "./telemetry-event-sources.js";
|
|
216
|
+
import {
|
|
217
|
+
insertTelemetryOutboxEvent,
|
|
218
|
+
queryTelemetryOutboxBatch,
|
|
219
|
+
} from "./telemetry-events-outbox.js";
|
|
220
|
+
import type { OutboxTelemetryEventName, TelemetryEvent } from "./types.js";
|
|
221
|
+
import {
|
|
222
|
+
initToolExecutedWatermarkIfAbsent,
|
|
223
|
+
UsageTelemetryReporter,
|
|
224
|
+
} from "./usage-telemetry-reporter.js";
|
|
225
|
+
import { recordWatchdogEvent } from "./watchdog-events-store.js";
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Construct a reporter wired to the in-memory checkpoint fake. All tests go
|
|
229
|
+
* through this so watermark reads/writes stay assertable via the mocks
|
|
230
|
+
* without process-global module mocking of the real telemetry-DB store.
|
|
231
|
+
*/
|
|
232
|
+
function makeReporter(): UsageTelemetryReporter {
|
|
233
|
+
return new UsageTelemetryReporter(
|
|
234
|
+
ALL_TELEMETRY_EVENT_SOURCES,
|
|
235
|
+
fakeFlushCheckpointStore,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
260
238
|
|
|
261
239
|
await initializeDb();
|
|
262
240
|
|
|
@@ -306,34 +284,6 @@ function makeUsageEvent(
|
|
|
306
284
|
};
|
|
307
285
|
}
|
|
308
286
|
|
|
309
|
-
type OnboardingEventFixture = ReturnType<
|
|
310
|
-
typeof mockQueryUnreportedOnboardingEvents
|
|
311
|
-
>[number];
|
|
312
|
-
|
|
313
|
-
function makeOnboardingEvent(
|
|
314
|
-
overrides: Partial<OnboardingEventFixture> = {},
|
|
315
|
-
): OnboardingEventFixture {
|
|
316
|
-
eventIdCounter += 1;
|
|
317
|
-
return {
|
|
318
|
-
id: `onb-${eventIdCounter}`,
|
|
319
|
-
createdAt: 1700000000000 + eventIdCounter * 1000,
|
|
320
|
-
screen: "tools",
|
|
321
|
-
toolsJson: null,
|
|
322
|
-
tasksJson: null,
|
|
323
|
-
tone: null,
|
|
324
|
-
googleConnected: null,
|
|
325
|
-
googleScopesJson: null,
|
|
326
|
-
priorAssistantsJson: null,
|
|
327
|
-
abVariant: null,
|
|
328
|
-
sessionId: null,
|
|
329
|
-
stepName: null,
|
|
330
|
-
stepIndex: null,
|
|
331
|
-
completedAt: null,
|
|
332
|
-
funnelVersion: null,
|
|
333
|
-
...overrides,
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
287
|
const TOOL_CONVERSATION_ID = "conv-reporter-tool-executed";
|
|
338
288
|
|
|
339
289
|
function seedToolInvocation(
|
|
@@ -355,10 +305,10 @@ function useStatefulCheckpoints(
|
|
|
355
305
|
seed: Record<string, string> = {},
|
|
356
306
|
): Map<string, string> {
|
|
357
307
|
const checkpoints = new Map(Object.entries(seed));
|
|
358
|
-
|
|
308
|
+
mockGetFlushCheckpoint.mockImplementation(
|
|
359
309
|
(key) => checkpoints.get(key) ?? null,
|
|
360
310
|
);
|
|
361
|
-
|
|
311
|
+
mockSetFlushCheckpoint.mockImplementation((key, value) => {
|
|
362
312
|
checkpoints.set(key, value);
|
|
363
313
|
});
|
|
364
314
|
return checkpoints;
|
|
@@ -381,29 +331,22 @@ beforeEach(() => {
|
|
|
381
331
|
mockGetCachedShareDiagnostics.mockReset();
|
|
382
332
|
mockGetCachedShareDiagnostics.mockReturnValue(false);
|
|
383
333
|
// Default the accepted consent version eligible so trace tests drive the gate
|
|
384
|
-
// via the
|
|
334
|
+
// via the share_diagnostics knob; version cases override it.
|
|
385
335
|
mockGetCachedShareDiagnosticsVersion.mockReset();
|
|
386
336
|
mockGetCachedShareDiagnosticsVersion.mockReturnValue("2999-01-01");
|
|
387
|
-
// Default the `trace-collection` flag ON so trace tests can drive eligibility
|
|
388
|
-
// via the consent knob alone; the flag-gating test flips it explicitly.
|
|
389
|
-
mockIsAssistantFeatureFlagEnabled.mockReset();
|
|
390
|
-
mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
|
|
391
337
|
mockAssembleBoundedTurnTrace.mockReset();
|
|
392
338
|
mockAssembleBoundedTurnTrace.mockImplementation(defaultBoundedTurnTrace);
|
|
393
339
|
mockIsTurnSettled.mockReset();
|
|
394
340
|
mockIsTurnSettled.mockReturnValue(true);
|
|
395
|
-
|
|
396
|
-
|
|
341
|
+
mockGetFlushCheckpoint.mockReset();
|
|
342
|
+
mockSetFlushCheckpoint.mockReset();
|
|
343
|
+
mockIsFlushCheckpointStoreAvailable.mockReset();
|
|
344
|
+
mockIsFlushCheckpointStoreAvailable.mockReturnValue(true);
|
|
397
345
|
mockQueryUnreportedUsageEvents.mockReset();
|
|
398
346
|
mockQueryUnreportedTurnEvents.mockReset();
|
|
399
347
|
mockQueryUnreportedTurnEvents.mockReturnValue([]);
|
|
400
|
-
mockQueryUnreportedLifecycleEvents.mockReset();
|
|
401
|
-
mockQueryUnreportedLifecycleEvents.mockReturnValue([]);
|
|
402
|
-
mockQueryUnreportedOnboardingEvents.mockReset();
|
|
403
|
-
mockQueryUnreportedOnboardingEvents.mockReturnValue([]);
|
|
404
|
-
getDb().delete(authFallbackEvents).run();
|
|
405
348
|
getDb().delete(toolInvocations).run();
|
|
406
|
-
|
|
349
|
+
getTelemetryDb()!.delete(telemetryEvents).run();
|
|
407
350
|
delete process.env.VELLUM_DISABLE_PLATFORM;
|
|
408
351
|
delete process.env.IS_PLATFORM;
|
|
409
352
|
mockGetPlatformBaseUrl.mockReset();
|
|
@@ -415,7 +358,7 @@ beforeEach(() => {
|
|
|
415
358
|
mockGetPlatformUserId.mockReturnValue("");
|
|
416
359
|
|
|
417
360
|
// Defaults
|
|
418
|
-
|
|
361
|
+
mockGetFlushCheckpoint.mockReturnValue(null);
|
|
419
362
|
mockGetPlatformBaseUrl.mockReturnValue("https://platform.vellum.ai");
|
|
420
363
|
|
|
421
364
|
mockFetch = mock(() =>
|
|
@@ -459,7 +402,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
459
402
|
const events = [makeUsageEvent(), makeUsageEvent()];
|
|
460
403
|
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
461
404
|
|
|
462
|
-
const reporter =
|
|
405
|
+
const reporter = makeReporter();
|
|
463
406
|
await reporter.flush();
|
|
464
407
|
|
|
465
408
|
expect(clientFetchMock).toHaveBeenCalledTimes(1);
|
|
@@ -477,11 +420,11 @@ describe("UsageTelemetryReporter", () => {
|
|
|
477
420
|
const events = [makeUsageEvent()];
|
|
478
421
|
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
479
422
|
|
|
480
|
-
const reporter =
|
|
423
|
+
const reporter = makeReporter();
|
|
481
424
|
await reporter.flush();
|
|
482
425
|
|
|
483
426
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
484
|
-
const watermarkCalls =
|
|
427
|
+
const watermarkCalls = mockSetFlushCheckpoint.mock.calls.filter(
|
|
485
428
|
(c) => c[0] === "telemetry:usage:last_reported_at",
|
|
486
429
|
);
|
|
487
430
|
expect(watermarkCalls.length).toBe(0);
|
|
@@ -496,14 +439,14 @@ describe("UsageTelemetryReporter", () => {
|
|
|
496
439
|
const events = [makeUsageEvent()];
|
|
497
440
|
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
498
441
|
|
|
499
|
-
const reporter =
|
|
442
|
+
const reporter = makeReporter();
|
|
500
443
|
// Construction initializes the absent tool_executed watermark; clear that
|
|
501
444
|
// call so the assertion below covers only the flush.
|
|
502
|
-
|
|
445
|
+
mockSetFlushCheckpoint.mockClear();
|
|
503
446
|
await reporter.flush();
|
|
504
447
|
|
|
505
448
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
506
|
-
expect(
|
|
449
|
+
expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
|
|
507
450
|
});
|
|
508
451
|
|
|
509
452
|
test("VELLUM_DISABLE_PLATFORM is ignored when IS_PLATFORM is set (managed mode)", async () => {
|
|
@@ -516,7 +459,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
516
459
|
const events = [makeUsageEvent()];
|
|
517
460
|
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
518
461
|
|
|
519
|
-
const reporter =
|
|
462
|
+
const reporter = makeReporter();
|
|
520
463
|
await reporter.flush();
|
|
521
464
|
|
|
522
465
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -532,10 +475,10 @@ describe("UsageTelemetryReporter", () => {
|
|
|
532
475
|
Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
|
|
533
476
|
);
|
|
534
477
|
|
|
535
|
-
const reporter =
|
|
478
|
+
const reporter = makeReporter();
|
|
536
479
|
await reporter.flush();
|
|
537
480
|
|
|
538
|
-
const watermarkCalls =
|
|
481
|
+
const watermarkCalls = mockSetFlushCheckpoint.mock.calls.filter(
|
|
539
482
|
(c) => c[0] === "telemetry:usage:last_reported_at",
|
|
540
483
|
);
|
|
541
484
|
expect(watermarkCalls.length).toBeGreaterThanOrEqual(1);
|
|
@@ -545,7 +488,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
545
488
|
);
|
|
546
489
|
|
|
547
490
|
// The compound cursor ID should also be set to the last event's id
|
|
548
|
-
const idCalls =
|
|
491
|
+
const idCalls = mockSetFlushCheckpoint.mock.calls.filter(
|
|
549
492
|
(c) => c[0] === "telemetry:usage:last_reported_id",
|
|
550
493
|
);
|
|
551
494
|
expect(idCalls.length).toBeGreaterThanOrEqual(1);
|
|
@@ -559,10 +502,10 @@ describe("UsageTelemetryReporter", () => {
|
|
|
559
502
|
Promise.resolve(new Response("error", { status: 500 })),
|
|
560
503
|
);
|
|
561
504
|
|
|
562
|
-
const reporter =
|
|
505
|
+
const reporter = makeReporter();
|
|
563
506
|
await reporter.flush();
|
|
564
507
|
|
|
565
|
-
const watermarkCalls =
|
|
508
|
+
const watermarkCalls = mockSetFlushCheckpoint.mock.calls.filter(
|
|
566
509
|
(c) => c[0] === "telemetry:usage:last_reported_at",
|
|
567
510
|
);
|
|
568
511
|
expect(watermarkCalls.length).toBe(0);
|
|
@@ -575,7 +518,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
575
518
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
576
519
|
);
|
|
577
520
|
|
|
578
|
-
const reporter =
|
|
521
|
+
const reporter = makeReporter();
|
|
579
522
|
|
|
580
523
|
// First flush — should use the per-device ID
|
|
581
524
|
await reporter.flush();
|
|
@@ -598,7 +541,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
598
541
|
test("empty batch makes no HTTP call", async () => {
|
|
599
542
|
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
600
543
|
|
|
601
|
-
const reporter =
|
|
544
|
+
const reporter = makeReporter();
|
|
602
545
|
await reporter.flush();
|
|
603
546
|
|
|
604
547
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
@@ -614,7 +557,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
614
557
|
Promise.resolve(new Response('{"accepted":500}', { status: 200 })),
|
|
615
558
|
);
|
|
616
559
|
|
|
617
|
-
const reporter =
|
|
560
|
+
const reporter = makeReporter();
|
|
618
561
|
await reporter.flush();
|
|
619
562
|
|
|
620
563
|
// MAX_CONSECUTIVE_BATCHES = 10
|
|
@@ -628,7 +571,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
628
571
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
629
572
|
);
|
|
630
573
|
|
|
631
|
-
const reporter =
|
|
574
|
+
const reporter = makeReporter();
|
|
632
575
|
reporter.start();
|
|
633
576
|
|
|
634
577
|
// Wait a tick so start()'s immediate flush settles.
|
|
@@ -662,7 +605,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
662
605
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
663
606
|
);
|
|
664
607
|
|
|
665
|
-
const reporter =
|
|
608
|
+
const reporter = makeReporter();
|
|
666
609
|
await reporter.flush();
|
|
667
610
|
|
|
668
611
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -731,7 +674,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
731
674
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
732
675
|
);
|
|
733
676
|
|
|
734
|
-
const reporter =
|
|
677
|
+
const reporter = makeReporter();
|
|
735
678
|
await reporter.flush();
|
|
736
679
|
|
|
737
680
|
const body = JSON.parse(
|
|
@@ -755,7 +698,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
755
698
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
756
699
|
);
|
|
757
700
|
|
|
758
|
-
const reporter =
|
|
701
|
+
const reporter = makeReporter();
|
|
759
702
|
await reporter.flush();
|
|
760
703
|
|
|
761
704
|
const body = JSON.parse(
|
|
@@ -782,7 +725,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
782
725
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
783
726
|
);
|
|
784
727
|
|
|
785
|
-
const reporter =
|
|
728
|
+
const reporter = makeReporter();
|
|
786
729
|
await reporter.flush();
|
|
787
730
|
|
|
788
731
|
const body = JSON.parse(
|
|
@@ -800,7 +743,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
800
743
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
801
744
|
);
|
|
802
745
|
|
|
803
|
-
const reporter =
|
|
746
|
+
const reporter = makeReporter();
|
|
804
747
|
await reporter.flush();
|
|
805
748
|
|
|
806
749
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -820,7 +763,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
820
763
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
821
764
|
);
|
|
822
765
|
|
|
823
|
-
const reporter =
|
|
766
|
+
const reporter = makeReporter();
|
|
824
767
|
await reporter.flush();
|
|
825
768
|
|
|
826
769
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -838,7 +781,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
838
781
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
839
782
|
);
|
|
840
783
|
|
|
841
|
-
const reporter =
|
|
784
|
+
const reporter = makeReporter();
|
|
842
785
|
await reporter.flush();
|
|
843
786
|
|
|
844
787
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -867,7 +810,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
867
810
|
Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
|
|
868
811
|
);
|
|
869
812
|
|
|
870
|
-
const reporter =
|
|
813
|
+
const reporter = makeReporter();
|
|
871
814
|
await reporter.flush();
|
|
872
815
|
|
|
873
816
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -935,7 +878,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
935
878
|
Promise.resolve(new Response('{"accepted":3}', { status: 200 })),
|
|
936
879
|
);
|
|
937
880
|
|
|
938
|
-
const reporter =
|
|
881
|
+
const reporter = makeReporter();
|
|
939
882
|
await reporter.flush();
|
|
940
883
|
|
|
941
884
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -1014,7 +957,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1014
957
|
Promise.resolve(new Response('{"accepted":4}', { status: 200 })),
|
|
1015
958
|
);
|
|
1016
959
|
|
|
1017
|
-
const reporter =
|
|
960
|
+
const reporter = makeReporter();
|
|
1018
961
|
await reporter.flush();
|
|
1019
962
|
|
|
1020
963
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -1068,8 +1011,8 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1068
1011
|
});
|
|
1069
1012
|
|
|
1070
1013
|
// -------------------------------------------------------------------------
|
|
1071
|
-
// Per-turn trace collection (gated on
|
|
1072
|
-
//
|
|
1014
|
+
// Per-turn trace collection (gated on share_diagnostics consent at an
|
|
1015
|
+
// eligible accepted version)
|
|
1073
1016
|
// -------------------------------------------------------------------------
|
|
1074
1017
|
|
|
1075
1018
|
function singleTurnEvent() {
|
|
@@ -1087,8 +1030,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1087
1030
|
];
|
|
1088
1031
|
}
|
|
1089
1032
|
|
|
1090
|
-
test("attaches the assembled trace when
|
|
1091
|
-
mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
|
|
1033
|
+
test("attaches the assembled trace when share_diagnostics and an eligible consent version are both true", async () => {
|
|
1092
1034
|
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1093
1035
|
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1094
1036
|
mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
|
|
@@ -1096,15 +1038,9 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1096
1038
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1097
1039
|
);
|
|
1098
1040
|
|
|
1099
|
-
const reporter =
|
|
1041
|
+
const reporter = makeReporter();
|
|
1100
1042
|
await reporter.flush();
|
|
1101
1043
|
|
|
1102
|
-
// The gate consults the `trace-collection` flag.
|
|
1103
|
-
expect(mockIsAssistantFeatureFlagEnabled).toHaveBeenCalledWith(
|
|
1104
|
-
"trace-collection",
|
|
1105
|
-
expect.anything(),
|
|
1106
|
-
);
|
|
1107
|
-
|
|
1108
1044
|
// The assembler is called with the turn's (conversationId, id, createdAt)
|
|
1109
1045
|
// boundary so the window lines up with the turn event.
|
|
1110
1046
|
expect(mockAssembleBoundedTurnTrace).toHaveBeenCalledTimes(1);
|
|
@@ -1124,13 +1060,13 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1124
1060
|
expect(turn.type).toBe("turn");
|
|
1125
1061
|
expect(turn.daemon_event_id).toBe("evt-turn-trace");
|
|
1126
1062
|
expect(turn.trace).toBeDefined();
|
|
1127
|
-
expect(turn.trace.schema_version).toBe(
|
|
1063
|
+
expect(turn.trace.schema_version).toBe(3);
|
|
1128
1064
|
expect(turn.trace.messages[0].id).toBe("evt-turn-trace");
|
|
1065
|
+
expect(turn.trace.messages[0].model).toBeNull();
|
|
1129
1066
|
expect(Array.isArray(turn.trace.tool_calls)).toBe(true);
|
|
1130
1067
|
});
|
|
1131
1068
|
|
|
1132
|
-
test("omits the trace when share_diagnostics is false
|
|
1133
|
-
mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
|
|
1069
|
+
test("omits the trace when share_diagnostics is false (and still emits the turn event)", async () => {
|
|
1134
1070
|
mockGetCachedShareDiagnostics.mockReturnValue(false);
|
|
1135
1071
|
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1136
1072
|
mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
|
|
@@ -1138,7 +1074,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1138
1074
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1139
1075
|
);
|
|
1140
1076
|
|
|
1141
|
-
const reporter =
|
|
1077
|
+
const reporter = makeReporter();
|
|
1142
1078
|
await reporter.flush();
|
|
1143
1079
|
|
|
1144
1080
|
// Gate off → no assembly at all (no PII touched).
|
|
@@ -1156,8 +1092,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1156
1092
|
expect("trace" in turn).toBe(false);
|
|
1157
1093
|
});
|
|
1158
1094
|
|
|
1159
|
-
test("omits the trace when the accepted consent version predates the disclosure threshold (
|
|
1160
|
-
mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
|
|
1095
|
+
test("omits the trace when the accepted consent version predates the disclosure threshold (share_diagnostics on)", async () => {
|
|
1161
1096
|
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1162
1097
|
// Consent recorded under an older version that never disclosed trace
|
|
1163
1098
|
// collection → gate closed, mirroring the platform ingest gate.
|
|
@@ -1168,7 +1103,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1168
1103
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1169
1104
|
);
|
|
1170
1105
|
|
|
1171
|
-
const reporter =
|
|
1106
|
+
const reporter = makeReporter();
|
|
1172
1107
|
await reporter.flush();
|
|
1173
1108
|
|
|
1174
1109
|
// Version below threshold → no assembly at all (no PII touched).
|
|
@@ -1183,7 +1118,6 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1183
1118
|
});
|
|
1184
1119
|
|
|
1185
1120
|
test("omits the trace when the owner never accepted a versioned consent (empty version)", async () => {
|
|
1186
|
-
mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
|
|
1187
1121
|
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1188
1122
|
// Empty version (never accepted / no-row default where share_diagnostics is
|
|
1189
1123
|
// true but unversioned) fails closed.
|
|
@@ -1194,7 +1128,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1194
1128
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1195
1129
|
);
|
|
1196
1130
|
|
|
1197
|
-
const reporter =
|
|
1131
|
+
const reporter = makeReporter();
|
|
1198
1132
|
await reporter.flush();
|
|
1199
1133
|
|
|
1200
1134
|
expect(mockAssembleBoundedTurnTrace).not.toHaveBeenCalled();
|
|
@@ -1205,30 +1139,6 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1205
1139
|
expect("trace" in body.events[0]).toBe(false);
|
|
1206
1140
|
});
|
|
1207
1141
|
|
|
1208
|
-
test("omits the trace when the trace-collection flag is off even though share_diagnostics is true", async () => {
|
|
1209
|
-
mockIsAssistantFeatureFlagEnabled.mockReturnValue(false);
|
|
1210
|
-
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1211
|
-
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1212
|
-
mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
|
|
1213
|
-
mockFetch.mockImplementation(() =>
|
|
1214
|
-
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1215
|
-
);
|
|
1216
|
-
|
|
1217
|
-
const reporter = new UsageTelemetryReporter();
|
|
1218
|
-
await reporter.flush();
|
|
1219
|
-
|
|
1220
|
-
// Flag off → gate closed → no assembly at all (no PII touched).
|
|
1221
|
-
expect(mockAssembleBoundedTurnTrace).not.toHaveBeenCalled();
|
|
1222
|
-
|
|
1223
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
1224
|
-
const body = JSON.parse(
|
|
1225
|
-
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
1226
|
-
);
|
|
1227
|
-
// The single turn event still flushes — just without a trace.
|
|
1228
|
-
expect(body.events.length).toBe(1);
|
|
1229
|
-
expect("trace" in body.events[0]).toBe(false);
|
|
1230
|
-
});
|
|
1231
|
-
|
|
1232
1142
|
test("omits the trace (key absent) when the assembler returns null (over-cap / failure) but still emits the turn event", async () => {
|
|
1233
1143
|
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1234
1144
|
// Over-cap / assembly-failure path: the bounded assembler returns null.
|
|
@@ -1239,7 +1149,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1239
1149
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1240
1150
|
);
|
|
1241
1151
|
|
|
1242
|
-
const reporter =
|
|
1152
|
+
const reporter = makeReporter();
|
|
1243
1153
|
await reporter.flush();
|
|
1244
1154
|
|
|
1245
1155
|
expect(mockAssembleBoundedTurnTrace).toHaveBeenCalledTimes(1);
|
|
@@ -1253,13 +1163,12 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1253
1163
|
test("no trace is assembled or attached when the whole flush is gated off by share_analytics", async () => {
|
|
1254
1164
|
// The analytics gate short-circuits the entire flush; trace assembly must
|
|
1255
1165
|
// never run (and nothing is sent) even when the trace gate is fully on
|
|
1256
|
-
// (
|
|
1166
|
+
// (share_diagnostics true at an eligible version).
|
|
1257
1167
|
mockGetCachedShareAnalytics.mockReturnValue(false);
|
|
1258
|
-
mockIsAssistantFeatureFlagEnabled.mockReturnValue(true);
|
|
1259
1168
|
mockGetCachedShareDiagnostics.mockReturnValue(true);
|
|
1260
1169
|
mockQueryUnreportedTurnEvents.mockReturnValue(singleTurnEvent());
|
|
1261
1170
|
|
|
1262
|
-
const reporter =
|
|
1171
|
+
const reporter = makeReporter();
|
|
1263
1172
|
await reporter.flush();
|
|
1264
1173
|
|
|
1265
1174
|
expect(mockAssembleBoundedTurnTrace).not.toHaveBeenCalled();
|
|
@@ -1303,7 +1212,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1303
1212
|
// The turn's response is still streaming — not settled.
|
|
1304
1213
|
mockIsTurnSettled.mockReturnValue(false);
|
|
1305
1214
|
|
|
1306
|
-
const reporter =
|
|
1215
|
+
const reporter = makeReporter();
|
|
1307
1216
|
await reporter.flush();
|
|
1308
1217
|
|
|
1309
1218
|
// No trace assembled, and the turn is NOT sent (the only event was deferred).
|
|
@@ -1324,7 +1233,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1324
1233
|
|
|
1325
1234
|
// Flush 1: turn in progress -> deferred, nothing sent.
|
|
1326
1235
|
mockIsTurnSettled.mockReturnValue(false);
|
|
1327
|
-
const reporter =
|
|
1236
|
+
const reporter = makeReporter();
|
|
1328
1237
|
await reporter.flush();
|
|
1329
1238
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
1330
1239
|
|
|
@@ -1332,19 +1241,21 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1332
1241
|
// the full trace assembles. The same (still-unreported) turn now ships.
|
|
1333
1242
|
mockIsTurnSettled.mockReturnValue(true);
|
|
1334
1243
|
mockAssembleBoundedTurnTrace.mockReturnValue({
|
|
1335
|
-
schema_version:
|
|
1244
|
+
schema_version: 3,
|
|
1336
1245
|
messages: [
|
|
1337
1246
|
{
|
|
1338
1247
|
id: "evt-inflight",
|
|
1339
1248
|
role: "user",
|
|
1340
1249
|
created_at: 1700000050000,
|
|
1341
1250
|
content: [{ type: "text", text: "do a thing" }],
|
|
1251
|
+
model: null,
|
|
1342
1252
|
},
|
|
1343
1253
|
{
|
|
1344
1254
|
id: "asst-1",
|
|
1345
1255
|
role: "assistant",
|
|
1346
1256
|
created_at: 1700000051000,
|
|
1347
1257
|
content: [{ type: "text", text: "done" }],
|
|
1258
|
+
model: "claude-fable-5",
|
|
1348
1259
|
},
|
|
1349
1260
|
],
|
|
1350
1261
|
tool_calls: [{ id: "ti-1" }],
|
|
@@ -1389,7 +1300,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1389
1300
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1390
1301
|
);
|
|
1391
1302
|
|
|
1392
|
-
const reporter =
|
|
1303
|
+
const reporter = makeReporter();
|
|
1393
1304
|
await reporter.flush();
|
|
1394
1305
|
|
|
1395
1306
|
expect(mockAssembleBoundedTurnTrace).toHaveBeenCalledTimes(1);
|
|
@@ -1423,7 +1334,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1423
1334
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1424
1335
|
);
|
|
1425
1336
|
|
|
1426
|
-
const reporter =
|
|
1337
|
+
const reporter = makeReporter();
|
|
1427
1338
|
await reporter.flush();
|
|
1428
1339
|
|
|
1429
1340
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -1460,7 +1371,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1460
1371
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1461
1372
|
);
|
|
1462
1373
|
|
|
1463
|
-
const reporter =
|
|
1374
|
+
const reporter = makeReporter();
|
|
1464
1375
|
await reporter.flush();
|
|
1465
1376
|
|
|
1466
1377
|
expect(mockIsTurnSettled).toHaveBeenCalled();
|
|
@@ -1481,7 +1392,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1481
1392
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1482
1393
|
);
|
|
1483
1394
|
|
|
1484
|
-
const reporter =
|
|
1395
|
+
const reporter = makeReporter();
|
|
1485
1396
|
await reporter.flush();
|
|
1486
1397
|
|
|
1487
1398
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -1520,7 +1431,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1520
1431
|
Promise.resolve(new Response('{"accepted":4}', { status: 200 })),
|
|
1521
1432
|
);
|
|
1522
1433
|
|
|
1523
|
-
const reporter =
|
|
1434
|
+
const reporter = makeReporter();
|
|
1524
1435
|
await reporter.flush();
|
|
1525
1436
|
|
|
1526
1437
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -1562,7 +1473,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1562
1473
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1563
1474
|
);
|
|
1564
1475
|
|
|
1565
|
-
const reporter =
|
|
1476
|
+
const reporter = makeReporter();
|
|
1566
1477
|
await reporter.flush();
|
|
1567
1478
|
|
|
1568
1479
|
const body = JSON.parse(
|
|
@@ -1602,7 +1513,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1602
1513
|
Promise.resolve(new Response('{"accepted":3}', { status: 200 })),
|
|
1603
1514
|
);
|
|
1604
1515
|
|
|
1605
|
-
const reporter =
|
|
1516
|
+
const reporter = makeReporter();
|
|
1606
1517
|
await reporter.flush();
|
|
1607
1518
|
|
|
1608
1519
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -1661,33 +1572,28 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1661
1572
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1662
1573
|
);
|
|
1663
1574
|
|
|
1664
|
-
const reporter =
|
|
1575
|
+
const reporter = makeReporter();
|
|
1665
1576
|
// Construction initializes the absent tool_executed watermark; clear that
|
|
1666
1577
|
// call so the count below covers only the flush's advancement.
|
|
1667
|
-
|
|
1578
|
+
mockSetFlushCheckpoint.mockClear();
|
|
1668
1579
|
await reporter.flush();
|
|
1669
1580
|
|
|
1670
1581
|
// No HTTP call should have been made
|
|
1671
1582
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
1672
1583
|
|
|
1673
|
-
// All
|
|
1674
|
-
// watermarks pinned to the high-sorting sentinel (a truthy
|
|
1675
|
-
// the compound-cursor branch active while closing its
|
|
1676
|
-
// arm against opt-out rows).
|
|
1677
|
-
|
|
1584
|
+
// All 3 watermark-mode timestamp watermarks should have been advanced,
|
|
1585
|
+
// and their 3 ID watermarks pinned to the high-sorting sentinel (a truthy
|
|
1586
|
+
// value keeps the compound-cursor branch active while closing its
|
|
1587
|
+
// same-millisecond arm against opt-out rows). Ack-mode sources
|
|
1588
|
+
// (lifecycle, onboarding, auth_fallback, skill_loaded, watchdog,
|
|
1589
|
+
// config_setting) discard their pending outbox rows instead and never
|
|
1590
|
+
// touch `flush_checkpoints`.
|
|
1591
|
+
expect(mockSetFlushCheckpoint).toHaveBeenCalledTimes(6);
|
|
1678
1592
|
|
|
1679
|
-
const calls =
|
|
1593
|
+
const calls = mockSetFlushCheckpoint.mock.calls;
|
|
1680
1594
|
const keys = calls.map((c) => c[0]);
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
"turns",
|
|
1684
|
-
"lifecycle",
|
|
1685
|
-
"onboarding",
|
|
1686
|
-
"auth_fallback",
|
|
1687
|
-
"tool_executed",
|
|
1688
|
-
"skill_loaded",
|
|
1689
|
-
"watchdog",
|
|
1690
|
-
];
|
|
1595
|
+
expect(keys.some((k) => k.includes("skill_loaded"))).toBe(false);
|
|
1596
|
+
const eventTypes = ["usage", "turns", "tool_executed"];
|
|
1691
1597
|
for (const eventType of eventTypes) {
|
|
1692
1598
|
expect(keys).toContain(`telemetry:${eventType}:last_reported_at`);
|
|
1693
1599
|
const idCall = calls.find(
|
|
@@ -1695,6 +1601,11 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1695
1601
|
);
|
|
1696
1602
|
expect(idCall?.[1]).toBe("ffffffff-ffff-ffff-ffff-ffffffffffff");
|
|
1697
1603
|
}
|
|
1604
|
+
expect(keys).not.toContain("telemetry:auth_fallback:last_reported_at");
|
|
1605
|
+
expect(keys).not.toContain("telemetry:auth_fallback:last_reported_id");
|
|
1606
|
+
expect(keys.some((k) => k.includes(":lifecycle:"))).toBe(false);
|
|
1607
|
+
expect(keys).not.toContain("telemetry:onboarding:last_reported_at");
|
|
1608
|
+
expect(keys).not.toContain("telemetry:onboarding:last_reported_id");
|
|
1698
1609
|
});
|
|
1699
1610
|
|
|
1700
1611
|
test("platform disabled takes precedence over consent off — watermarks NOT advanced", async () => {
|
|
@@ -1708,23 +1619,44 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1708
1619
|
const events = [makeUsageEvent()];
|
|
1709
1620
|
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
1710
1621
|
|
|
1711
|
-
const reporter =
|
|
1622
|
+
const reporter = makeReporter();
|
|
1712
1623
|
// Construction initializes the absent tool_executed watermark; clear that
|
|
1713
1624
|
// call so the assertion below covers only the flush.
|
|
1714
|
-
|
|
1625
|
+
mockSetFlushCheckpoint.mockClear();
|
|
1715
1626
|
await reporter.flush();
|
|
1716
1627
|
|
|
1717
1628
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
1718
|
-
expect(
|
|
1629
|
+
expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
|
|
1630
|
+
});
|
|
1631
|
+
|
|
1632
|
+
test("flush skipped entirely when the flush-checkpoint store is unavailable", async () => {
|
|
1633
|
+
// An unreadable checkpoint store must not be mistaken for cursor 0 —
|
|
1634
|
+
// that would requery and re-ship history from the beginning. Nothing is
|
|
1635
|
+
// sent and no watermark is touched (not even the opt-out sentinel);
|
|
1636
|
+
// the cycle retries once the store is back.
|
|
1637
|
+
mockIsFlushCheckpointStoreAvailable.mockReturnValue(false);
|
|
1638
|
+
const events = [makeUsageEvent()];
|
|
1639
|
+
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
1640
|
+
|
|
1641
|
+
const reporter = makeReporter();
|
|
1642
|
+
// Construction initializes the absent tool_executed watermark; clear
|
|
1643
|
+
// those calls so the assertions below cover only the flush.
|
|
1644
|
+
mockGetFlushCheckpoint.mockClear();
|
|
1645
|
+
mockSetFlushCheckpoint.mockClear();
|
|
1646
|
+
await reporter.flush();
|
|
1647
|
+
|
|
1648
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
1649
|
+
expect(mockGetFlushCheckpoint).not.toHaveBeenCalled();
|
|
1650
|
+
expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
|
|
1719
1651
|
});
|
|
1720
1652
|
|
|
1721
1653
|
test("events sent normally after re-granting share_analytics consent", async () => {
|
|
1722
1654
|
// First flush with opt-out — watermarks advance, nothing sent
|
|
1723
1655
|
mockGetCachedShareAnalytics.mockReturnValue(false);
|
|
1724
|
-
const reporter =
|
|
1656
|
+
const reporter = makeReporter();
|
|
1725
1657
|
await reporter.flush();
|
|
1726
1658
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
1727
|
-
|
|
1659
|
+
mockSetFlushCheckpoint.mockReset();
|
|
1728
1660
|
|
|
1729
1661
|
// Re-grant consent and flush with new events
|
|
1730
1662
|
mockGetCachedShareAnalytics.mockReturnValue(true);
|
|
@@ -1748,9 +1680,9 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1748
1680
|
//
|
|
1749
1681
|
// The envelope's `assistant_version` is upload-time (always the current
|
|
1750
1682
|
// binary). The per-event field is record-time (the binary that was running
|
|
1751
|
-
// when the event was persisted to SQLite).
|
|
1752
|
-
//
|
|
1753
|
-
//
|
|
1683
|
+
// when the event was persisted to SQLite). `llm_usage` events and the
|
|
1684
|
+
// outbox-backed types carry a true record-time value; turn events
|
|
1685
|
+
// (not asserted here) stamp the running binary's
|
|
1754
1686
|
// `APP_VERSION` directly until their respective follow-ups land.
|
|
1755
1687
|
// Nullable llm_usage cases (legacy rows from before migration 267 ran)
|
|
1756
1688
|
// fall back to the running binary's `APP_VERSION` rather than emitting
|
|
@@ -1769,7 +1701,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1769
1701
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1770
1702
|
);
|
|
1771
1703
|
|
|
1772
|
-
const reporter =
|
|
1704
|
+
const reporter = makeReporter();
|
|
1773
1705
|
await reporter.flush();
|
|
1774
1706
|
|
|
1775
1707
|
const body = JSON.parse(
|
|
@@ -1793,7 +1725,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1793
1725
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1794
1726
|
);
|
|
1795
1727
|
|
|
1796
|
-
const reporter =
|
|
1728
|
+
const reporter = makeReporter();
|
|
1797
1729
|
await reporter.flush();
|
|
1798
1730
|
|
|
1799
1731
|
const body = JSON.parse(
|
|
@@ -1830,7 +1762,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1830
1762
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1831
1763
|
);
|
|
1832
1764
|
|
|
1833
|
-
const reporter =
|
|
1765
|
+
const reporter = makeReporter();
|
|
1834
1766
|
await reporter.flush();
|
|
1835
1767
|
|
|
1836
1768
|
const body = JSON.parse(
|
|
@@ -1859,7 +1791,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1859
1791
|
Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
|
|
1860
1792
|
);
|
|
1861
1793
|
|
|
1862
|
-
const reporter =
|
|
1794
|
+
const reporter = makeReporter();
|
|
1863
1795
|
await reporter.flush();
|
|
1864
1796
|
|
|
1865
1797
|
const body = JSON.parse(
|
|
@@ -1891,7 +1823,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1891
1823
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1892
1824
|
);
|
|
1893
1825
|
|
|
1894
|
-
const reporter =
|
|
1826
|
+
const reporter = makeReporter();
|
|
1895
1827
|
await reporter.flush();
|
|
1896
1828
|
|
|
1897
1829
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -1914,7 +1846,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1914
1846
|
expect(typeof body.events[0].daemon_event_id).toBe("string");
|
|
1915
1847
|
});
|
|
1916
1848
|
|
|
1917
|
-
test("auth_fallback
|
|
1849
|
+
test("auth_fallback rows are deleted on success and never watermarked (ack mode)", async () => {
|
|
1918
1850
|
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1919
1851
|
recordAuthFallbackCounts(1700000000000, 1700000001000, [
|
|
1920
1852
|
{
|
|
@@ -1933,59 +1865,66 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1933
1865
|
mockFetch.mockImplementation(() =>
|
|
1934
1866
|
Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
|
|
1935
1867
|
);
|
|
1868
|
+
expect(queryTelemetryOutboxBatch("auth_fallback", 10)).toHaveLength(2);
|
|
1936
1869
|
|
|
1937
|
-
|
|
1938
|
-
// whose watermark should be persisted after a successful upload.
|
|
1939
|
-
const rows = getDb()
|
|
1940
|
-
.select()
|
|
1941
|
-
.from(authFallbackEvents)
|
|
1942
|
-
.orderBy(authFallbackEvents.createdAt, authFallbackEvents.id)
|
|
1943
|
-
.all();
|
|
1944
|
-
const lastRow = rows[rows.length - 1];
|
|
1945
|
-
|
|
1946
|
-
const reporter = new UsageTelemetryReporter();
|
|
1870
|
+
const reporter = makeReporter();
|
|
1947
1871
|
await reporter.flush();
|
|
1948
1872
|
|
|
1949
|
-
|
|
1950
|
-
|
|
1873
|
+
// Both rows shipped, were acknowledged (deleted), and no auth_fallback
|
|
1874
|
+
// watermark was ever written — ack-mode sources bypass flush_checkpoints.
|
|
1875
|
+
const body = JSON.parse(
|
|
1876
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
1951
1877
|
);
|
|
1952
|
-
expect(
|
|
1953
|
-
|
|
1954
|
-
|
|
1878
|
+
expect(
|
|
1879
|
+
body.events.filter((e: { type: string }) => e.type === "auth_fallback"),
|
|
1880
|
+
).toHaveLength(2);
|
|
1881
|
+
expect(queryTelemetryOutboxBatch("auth_fallback", 10)).toHaveLength(0);
|
|
1882
|
+
const authFallbackKeys = mockSetFlushCheckpoint.mock.calls.filter((c) =>
|
|
1883
|
+
String(c[0]).startsWith("telemetry:auth_fallback:"),
|
|
1884
|
+
);
|
|
1885
|
+
expect(authFallbackKeys).toHaveLength(0);
|
|
1886
|
+
});
|
|
1887
|
+
|
|
1888
|
+
test("auth_fallback rows survive a failed POST and re-ship on the next flush", async () => {
|
|
1889
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1890
|
+
recordAuthFallbackCounts(1700000000000, 1700000001000, [
|
|
1891
|
+
{
|
|
1892
|
+
guard: "edge",
|
|
1893
|
+
path: "/v1/messages",
|
|
1894
|
+
failureKind: "missing_authorization",
|
|
1895
|
+
count: 9,
|
|
1896
|
+
},
|
|
1897
|
+
]);
|
|
1898
|
+
mockFetch.mockImplementation(() =>
|
|
1899
|
+
Promise.resolve(new Response("server error", { status: 500 })),
|
|
1955
1900
|
);
|
|
1956
1901
|
|
|
1957
|
-
const
|
|
1958
|
-
|
|
1902
|
+
const reporter = makeReporter();
|
|
1903
|
+
await reporter.flush();
|
|
1904
|
+
expect(queryTelemetryOutboxBatch("auth_fallback", 10)).toHaveLength(1);
|
|
1905
|
+
|
|
1906
|
+
mockFetch.mockImplementation(() =>
|
|
1907
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1959
1908
|
);
|
|
1960
|
-
|
|
1961
|
-
expect(
|
|
1909
|
+
await reporter.flush();
|
|
1910
|
+
expect(queryTelemetryOutboxBatch("auth_fallback", 10)).toHaveLength(0);
|
|
1962
1911
|
});
|
|
1963
1912
|
|
|
1964
1913
|
// -------------------------------------------------------------------------
|
|
1965
1914
|
// Onboarding / activation funnel events
|
|
1966
1915
|
// -------------------------------------------------------------------------
|
|
1967
1916
|
|
|
1968
|
-
test("activation onboarding row
|
|
1917
|
+
test("activation onboarding row flushes with funnel fields + deterministic daemon_event_id, then acks", async () => {
|
|
1969
1918
|
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
1970
1919
|
const sessionId = "sess-activation-1";
|
|
1971
1920
|
const stepName = "activation_moment_1_complete";
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
id: "onb-activation-1",
|
|
1975
|
-
screen: stepName,
|
|
1976
|
-
abVariant: "variant-a",
|
|
1977
|
-
sessionId,
|
|
1978
|
-
stepName,
|
|
1979
|
-
stepIndex: 1,
|
|
1980
|
-
completedAt: "2026-06-06T00:00:00.000Z",
|
|
1981
|
-
funnelVersion: ACTIVATION_FUNNEL_VERSION,
|
|
1982
|
-
}),
|
|
1983
|
-
]);
|
|
1921
|
+
const recorded = recordActivationEvent({ stepName, sessionId });
|
|
1922
|
+
expect(recorded).not.toBeNull();
|
|
1984
1923
|
mockFetch.mockImplementation(() =>
|
|
1985
1924
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
1986
1925
|
);
|
|
1987
1926
|
|
|
1988
|
-
const reporter =
|
|
1927
|
+
const reporter = makeReporter();
|
|
1989
1928
|
await reporter.flush();
|
|
1990
1929
|
|
|
1991
1930
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -1998,69 +1937,66 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1998
1937
|
session_id: sessionId,
|
|
1999
1938
|
step_name: stepName,
|
|
2000
1939
|
step_index: 1,
|
|
2001
|
-
|
|
2002
|
-
funnel_version: "activation_v1_2026_06",
|
|
1940
|
+
funnel_version: ACTIVATION_FUNNEL_VERSION,
|
|
2003
1941
|
daemon_event_id: buildActivationDaemonEventId(sessionId, stepName),
|
|
2004
1942
|
});
|
|
1943
|
+
// The wire id is the deterministic activation id, distinct from the
|
|
1944
|
+
// outbox row id (a UUID) the ack deletes by.
|
|
1945
|
+
expect(body.events[0].daemon_event_id).not.toBe(recorded!.id);
|
|
1946
|
+
expect(queryTelemetryOutboxBatch("onboarding", 10)).toHaveLength(0);
|
|
2005
1947
|
});
|
|
2006
1948
|
|
|
2007
|
-
test("activation
|
|
1949
|
+
test("queued activation row recorded under an older funnel version ships its frozen payload verbatim", async () => {
|
|
2008
1950
|
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
//
|
|
1951
|
+
// The payload (including the deterministic daemon_event_id keyed on the
|
|
1952
|
+
// OLD funnel version) was frozen at record time; a flush after a version
|
|
1953
|
+
// bump must not restamp it with the new binary's constant.
|
|
2012
1954
|
const oldVersion = "activation_v0_2026_05";
|
|
2013
1955
|
expect(oldVersion).not.toBe(ACTIVATION_FUNNEL_VERSION);
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
1956
|
+
const frozen: TelemetryEvent = {
|
|
1957
|
+
type: "onboarding",
|
|
1958
|
+
daemon_event_id: `${oldVersion}:sess-activation-old:activation_moment_1_complete`,
|
|
1959
|
+
recorded_at: 1700000100000,
|
|
1960
|
+
screen: "activation_moment_1_complete",
|
|
1961
|
+
ab_variant: "variant-a",
|
|
1962
|
+
session_id: "sess-activation-old",
|
|
1963
|
+
step_name: "activation_moment_1_complete",
|
|
1964
|
+
step_index: 1,
|
|
1965
|
+
completed_at: "2026-05-01T00:00:00.000Z",
|
|
1966
|
+
funnel_version: oldVersion,
|
|
1967
|
+
assistant_version: "0.9.0-old",
|
|
1968
|
+
};
|
|
1969
|
+
insertTelemetryOutboxEvent({
|
|
1970
|
+
id: "row-activation-old",
|
|
1971
|
+
name: "onboarding",
|
|
1972
|
+
createdAt: 1700000100000,
|
|
1973
|
+
event: frozen,
|
|
1974
|
+
});
|
|
2026
1975
|
mockFetch.mockImplementation(() =>
|
|
2027
1976
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
2028
1977
|
);
|
|
2029
1978
|
|
|
2030
|
-
const reporter =
|
|
1979
|
+
const reporter = makeReporter();
|
|
2031
1980
|
await reporter.flush();
|
|
2032
1981
|
|
|
2033
1982
|
const body = JSON.parse(
|
|
2034
1983
|
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
2035
1984
|
);
|
|
2036
|
-
expect(body.events[
|
|
2037
|
-
funnel_version: oldVersion,
|
|
2038
|
-
daemon_event_id: buildActivationDaemonEventId(
|
|
2039
|
-
sessionId,
|
|
2040
|
-
stepName,
|
|
2041
|
-
oldVersion,
|
|
2042
|
-
),
|
|
2043
|
-
});
|
|
2044
|
-
// Guard: the id must carry the row's version, not the binary constant.
|
|
2045
|
-
expect(body.events[0].daemon_event_id).toBe(
|
|
2046
|
-
`${oldVersion}:${sessionId}:${stepName}`,
|
|
2047
|
-
);
|
|
1985
|
+
expect(body.events).toEqual([frozen]);
|
|
2048
1986
|
});
|
|
2049
1987
|
|
|
2050
|
-
test("legacy onboarding row keeps daemon_event_id
|
|
1988
|
+
test("legacy onboarding row keeps daemon_event_id = row id and omits funnel fields", async () => {
|
|
2051
1989
|
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
}),
|
|
2058
|
-
]);
|
|
1990
|
+
const recorded = recordOnboardingEvent({
|
|
1991
|
+
screen: "tools",
|
|
1992
|
+
tools: ["calendar"],
|
|
1993
|
+
});
|
|
1994
|
+
expect(recorded).not.toBeNull();
|
|
2059
1995
|
mockFetch.mockImplementation(() =>
|
|
2060
1996
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
2061
1997
|
);
|
|
2062
1998
|
|
|
2063
|
-
const reporter =
|
|
1999
|
+
const reporter = makeReporter();
|
|
2064
2000
|
await reporter.flush();
|
|
2065
2001
|
|
|
2066
2002
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -2070,7 +2006,8 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2070
2006
|
expect(body.events.length).toBe(1);
|
|
2071
2007
|
const e = body.events[0];
|
|
2072
2008
|
expect(e.type).toBe("onboarding");
|
|
2073
|
-
expect(e.daemon_event_id).toBe(
|
|
2009
|
+
expect(e.daemon_event_id).toBe(recorded!.id);
|
|
2010
|
+
expect(e.tools).toEqual(["calendar"]);
|
|
2074
2011
|
expect(e.session_id).toBeUndefined();
|
|
2075
2012
|
expect(e.step_name).toBeUndefined();
|
|
2076
2013
|
expect(e.step_index).toBeUndefined();
|
|
@@ -2118,7 +2055,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2118
2055
|
Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
|
|
2119
2056
|
);
|
|
2120
2057
|
|
|
2121
|
-
const reporter =
|
|
2058
|
+
const reporter = makeReporter();
|
|
2122
2059
|
await reporter.flush();
|
|
2123
2060
|
|
|
2124
2061
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -2174,10 +2111,10 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2174
2111
|
Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
|
|
2175
2112
|
);
|
|
2176
2113
|
|
|
2177
|
-
const reporter =
|
|
2114
|
+
const reporter = makeReporter();
|
|
2178
2115
|
await reporter.flush();
|
|
2179
2116
|
|
|
2180
|
-
const watermarkCalls =
|
|
2117
|
+
const watermarkCalls = mockSetFlushCheckpoint.mock.calls.filter(
|
|
2181
2118
|
(c) => c[0] === "telemetry:tool_executed:last_reported_at",
|
|
2182
2119
|
);
|
|
2183
2120
|
expect(watermarkCalls.length).toBeGreaterThanOrEqual(1);
|
|
@@ -2185,7 +2122,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2185
2122
|
String(1700000002000),
|
|
2186
2123
|
);
|
|
2187
2124
|
|
|
2188
|
-
const idCalls =
|
|
2125
|
+
const idCalls = mockSetFlushCheckpoint.mock.calls.filter(
|
|
2189
2126
|
(c) => c[0] === "telemetry:tool_executed:last_reported_id",
|
|
2190
2127
|
);
|
|
2191
2128
|
expect(idCalls.length).toBeGreaterThanOrEqual(1);
|
|
@@ -2197,18 +2134,20 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2197
2134
|
seedToolInvocation({ id: "ti-r1", createdAt: 1700000001000 });
|
|
2198
2135
|
seedToolInvocation({ id: "ti-r2", createdAt: 1700000002000 });
|
|
2199
2136
|
// A previous flush already reported ti-r1.
|
|
2200
|
-
|
|
2137
|
+
mockGetFlushCheckpoint.mockImplementation((key) => {
|
|
2201
2138
|
if (key === "telemetry:tool_executed:last_reported_at") {
|
|
2202
2139
|
return String(1700000001000);
|
|
2203
2140
|
}
|
|
2204
|
-
if (key === "telemetry:tool_executed:last_reported_id")
|
|
2141
|
+
if (key === "telemetry:tool_executed:last_reported_id") {
|
|
2142
|
+
return "ti-r1";
|
|
2143
|
+
}
|
|
2205
2144
|
return null;
|
|
2206
2145
|
});
|
|
2207
2146
|
mockFetch.mockImplementation(() =>
|
|
2208
2147
|
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
2209
2148
|
);
|
|
2210
2149
|
|
|
2211
|
-
const reporter =
|
|
2150
|
+
const reporter = makeReporter();
|
|
2212
2151
|
await reporter.flush();
|
|
2213
2152
|
|
|
2214
2153
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -2229,7 +2168,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2229
2168
|
// predating the reporter) stay behind the watermark.
|
|
2230
2169
|
const checkpoints = useStatefulCheckpoints();
|
|
2231
2170
|
|
|
2232
|
-
const reporter =
|
|
2171
|
+
const reporter = makeReporter();
|
|
2233
2172
|
const rowCreatedAt =
|
|
2234
2173
|
Number(checkpoints.get("telemetry:tool_executed:last_reported_at")) +
|
|
2235
2174
|
1000;
|
|
@@ -2268,7 +2207,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2268
2207
|
createdAt: Date.now() - 60_000,
|
|
2269
2208
|
});
|
|
2270
2209
|
|
|
2271
|
-
const reporter =
|
|
2210
|
+
const reporter = makeReporter();
|
|
2272
2211
|
|
|
2273
2212
|
// The checkpoint is persisted immediately at construction so a crash
|
|
2274
2213
|
// before the first flush can't re-initialize later.
|
|
@@ -2313,7 +2252,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2313
2252
|
// opted-in rows.
|
|
2314
2253
|
seedToolInvocation({ id: "ti-backlog", createdAt: 1700000002000 });
|
|
2315
2254
|
|
|
2316
|
-
const reporter =
|
|
2255
|
+
const reporter = makeReporter();
|
|
2317
2256
|
expect(checkpoints.get("telemetry:tool_executed:last_reported_at")).toBe(
|
|
2318
2257
|
String(1700000001000),
|
|
2319
2258
|
);
|
|
@@ -2347,7 +2286,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2347
2286
|
id: "ti-opt-out-window",
|
|
2348
2287
|
createdAt: optOutRowCreatedAt,
|
|
2349
2288
|
});
|
|
2350
|
-
const optedOutReporter =
|
|
2289
|
+
const optedOutReporter = makeReporter();
|
|
2351
2290
|
await optedOutReporter.stop(); // shutdown path: runs the final flush
|
|
2352
2291
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
2353
2292
|
const advanced = Number(
|
|
@@ -2359,7 +2298,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2359
2298
|
// after the opt-out epoch ship — the opt-out-window row never does.
|
|
2360
2299
|
mockGetCachedShareAnalytics.mockReturnValue(true);
|
|
2361
2300
|
seedToolInvocation({ id: "ti-after-opt-in", createdAt: advanced + 1000 });
|
|
2362
|
-
const reporter =
|
|
2301
|
+
const reporter = makeReporter();
|
|
2363
2302
|
await reporter.flush();
|
|
2364
2303
|
|
|
2365
2304
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -2383,7 +2322,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2383
2322
|
// Opted-out flush: advances the timestamp watermark to Date.now() and
|
|
2384
2323
|
// must also pin the ID watermark to the high-sorting sentinel.
|
|
2385
2324
|
mockGetCachedShareAnalytics.mockReturnValue(false);
|
|
2386
|
-
const optedOutReporter =
|
|
2325
|
+
const optedOutReporter = makeReporter();
|
|
2387
2326
|
await optedOutReporter.flush();
|
|
2388
2327
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
2389
2328
|
const watermark = Number(
|
|
@@ -2402,7 +2341,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2402
2341
|
// Re-opt-in: only rows strictly after the opt-out epoch ship.
|
|
2403
2342
|
mockGetCachedShareAnalytics.mockReturnValue(true);
|
|
2404
2343
|
seedToolInvocation({ id: "ti-after-opt-in", createdAt: watermark + 1000 });
|
|
2405
|
-
const reporter =
|
|
2344
|
+
const reporter = makeReporter();
|
|
2406
2345
|
await reporter.flush();
|
|
2407
2346
|
|
|
2408
2347
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -2414,21 +2353,61 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2414
2353
|
).toEqual(["ti-after-opt-in"]);
|
|
2415
2354
|
});
|
|
2416
2355
|
|
|
2356
|
+
test("a turn-only (daemon) reporter never touches the tool_executed watermark", () => {
|
|
2357
|
+
new UsageTelemetryReporter(
|
|
2358
|
+
DAEMON_TELEMETRY_EVENT_SOURCES,
|
|
2359
|
+
fakeFlushCheckpointStore,
|
|
2360
|
+
);
|
|
2361
|
+
expect(mockGetFlushCheckpoint).not.toHaveBeenCalled();
|
|
2362
|
+
expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
|
|
2363
|
+
});
|
|
2364
|
+
|
|
2365
|
+
test("a monitor reporter re-runs the guarded tool_executed watermark init as a backstop", () => {
|
|
2366
|
+
new UsageTelemetryReporter(
|
|
2367
|
+
MONITOR_TELEMETRY_EVENT_SOURCES,
|
|
2368
|
+
fakeFlushCheckpointStore,
|
|
2369
|
+
);
|
|
2370
|
+
expect(mockGetFlushCheckpoint).toHaveBeenCalledWith(
|
|
2371
|
+
"telemetry:tool_executed:last_reported_at",
|
|
2372
|
+
);
|
|
2373
|
+
expect(mockSetFlushCheckpoint).toHaveBeenCalledTimes(1);
|
|
2374
|
+
});
|
|
2375
|
+
|
|
2376
|
+
test("initToolExecutedWatermarkIfAbsent sets the epoch once and never overwrites", () => {
|
|
2377
|
+
const checkpoints = useStatefulCheckpoints();
|
|
2378
|
+
initToolExecutedWatermarkIfAbsent(fakeFlushCheckpointStore);
|
|
2379
|
+
const epoch = checkpoints.get("telemetry:tool_executed:last_reported_at");
|
|
2380
|
+
expect(epoch).toBeString();
|
|
2381
|
+
|
|
2382
|
+
initToolExecutedWatermarkIfAbsent(fakeFlushCheckpointStore);
|
|
2383
|
+
expect(checkpoints.get("telemetry:tool_executed:last_reported_at")).toBe(
|
|
2384
|
+
epoch!,
|
|
2385
|
+
);
|
|
2386
|
+
|
|
2387
|
+
// A store failure is non-fatal (degraded-DB daemons still start).
|
|
2388
|
+
mockGetFlushCheckpoint.mockImplementation(() => {
|
|
2389
|
+
throw new Error("database disk image is malformed");
|
|
2390
|
+
});
|
|
2391
|
+
expect(() =>
|
|
2392
|
+
initToolExecutedWatermarkIfAbsent(fakeFlushCheckpointStore),
|
|
2393
|
+
).not.toThrow();
|
|
2394
|
+
});
|
|
2395
|
+
|
|
2417
2396
|
test("checkpoint store failure during construction is non-fatal — degraded-DB daemons still start", () => {
|
|
2418
2397
|
// initializeDb() failures are tolerated at daemon startup (degraded
|
|
2419
2398
|
// mode), so the constructor's checkpoint init must never throw out of
|
|
2420
2399
|
// the constructor and abort the daemon.
|
|
2421
|
-
|
|
2400
|
+
mockGetFlushCheckpoint.mockImplementation(() => {
|
|
2422
2401
|
throw new Error("database disk image is malformed");
|
|
2423
2402
|
});
|
|
2424
|
-
expect(() =>
|
|
2403
|
+
expect(() => makeReporter()).not.toThrow();
|
|
2425
2404
|
|
|
2426
2405
|
// The write path failing is equally non-fatal.
|
|
2427
|
-
|
|
2428
|
-
|
|
2406
|
+
mockGetFlushCheckpoint.mockImplementation(() => null);
|
|
2407
|
+
mockSetFlushCheckpoint.mockImplementation(() => {
|
|
2429
2408
|
throw new Error("database disk image is malformed");
|
|
2430
2409
|
});
|
|
2431
|
-
expect(() =>
|
|
2410
|
+
expect(() => makeReporter()).not.toThrow();
|
|
2432
2411
|
});
|
|
2433
2412
|
|
|
2434
2413
|
// -------------------------------------------------------------------------
|
|
@@ -2452,7 +2431,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2452
2431
|
Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
|
|
2453
2432
|
);
|
|
2454
2433
|
|
|
2455
|
-
const reporter =
|
|
2434
|
+
const reporter = makeReporter();
|
|
2456
2435
|
await reporter.flush();
|
|
2457
2436
|
|
|
2458
2437
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
@@ -2491,59 +2470,637 @@ describe("UsageTelemetryReporter", () => {
|
|
|
2491
2470
|
});
|
|
2492
2471
|
});
|
|
2493
2472
|
|
|
2494
|
-
test("skill_loaded
|
|
2473
|
+
test("skill_loaded rows are deleted from the outbox after a 2xx, never via watermarks", async () => {
|
|
2495
2474
|
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
2496
2475
|
recordSkillLoadedEvent({ skillName: "web-research" });
|
|
2497
2476
|
recordSkillLoadedEvent({ skillName: "tasks" });
|
|
2477
|
+
expect(queryTelemetryOutboxBatch("skill_loaded", 10)).toHaveLength(2);
|
|
2498
2478
|
mockFetch.mockImplementation(() =>
|
|
2499
2479
|
Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
|
|
2500
2480
|
);
|
|
2501
2481
|
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2482
|
+
const reporter = makeReporter();
|
|
2483
|
+
await reporter.flush();
|
|
2484
|
+
|
|
2485
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
2486
|
+
expect(queryTelemetryOutboxBatch("skill_loaded", 10)).toHaveLength(0);
|
|
2487
|
+
// Ack-mode: the skill_loaded source never touches flush_checkpoints.
|
|
2488
|
+
expect(
|
|
2489
|
+
mockSetFlushCheckpoint.mock.calls.some((c) =>
|
|
2490
|
+
c[0].includes("skill_loaded"),
|
|
2491
|
+
),
|
|
2492
|
+
).toBe(false);
|
|
2493
|
+
});
|
|
2494
|
+
|
|
2495
|
+
test("a full skill_loaded batch drives recursion until the outbox drains", async () => {
|
|
2496
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
2497
|
+
// BATCH_SIZE (500) + 1 rows: the first flush ships and deletes a full
|
|
2498
|
+
// batch, and the fullBatch recursion ships the remaining row.
|
|
2499
|
+
for (let i = 0; i < 501; i++) {
|
|
2500
|
+
recordSkillLoadedEvent({ skillName: `skill-${i}` });
|
|
2501
|
+
}
|
|
2502
|
+
mockFetch.mockImplementation(() =>
|
|
2503
|
+
Promise.resolve(new Response('{"accepted":500}', { status: 200 })),
|
|
2504
|
+
);
|
|
2510
2505
|
|
|
2511
|
-
const reporter =
|
|
2506
|
+
const reporter = makeReporter();
|
|
2512
2507
|
await reporter.flush();
|
|
2513
2508
|
|
|
2514
|
-
|
|
2515
|
-
|
|
2509
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
2510
|
+
expect(queryTelemetryOutboxBatch("skill_loaded", 1000)).toHaveLength(0);
|
|
2511
|
+
});
|
|
2512
|
+
|
|
2513
|
+
// -------------------------------------------------------------------------
|
|
2514
|
+
// Config-setting events
|
|
2515
|
+
// -------------------------------------------------------------------------
|
|
2516
|
+
|
|
2517
|
+
test("config_setting events ship the key/value pair on the standard envelope", async () => {
|
|
2518
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
2519
|
+
recordConfigSettingEvent({
|
|
2520
|
+
configKey: "memory.enabled",
|
|
2521
|
+
configValue: "true",
|
|
2522
|
+
});
|
|
2523
|
+
recordConfigSettingEvent({
|
|
2524
|
+
configKey: "memory.v2.enabled",
|
|
2525
|
+
configValue: "false",
|
|
2526
|
+
});
|
|
2527
|
+
mockFetch.mockImplementation(() =>
|
|
2528
|
+
Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
|
|
2516
2529
|
);
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2530
|
+
|
|
2531
|
+
const reporter = makeReporter();
|
|
2532
|
+
await reporter.flush();
|
|
2533
|
+
|
|
2534
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
2535
|
+
const body = JSON.parse(
|
|
2536
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
2520
2537
|
);
|
|
2538
|
+
expect(body.events.length).toBe(2);
|
|
2539
|
+
|
|
2540
|
+
const byKey: Record<string, Record<string, unknown>> = {};
|
|
2541
|
+
for (const e of body.events as Array<{ config_key: string }>) {
|
|
2542
|
+
byKey[e.config_key] = e;
|
|
2543
|
+
}
|
|
2521
2544
|
|
|
2522
|
-
|
|
2523
|
-
|
|
2545
|
+
expect(byKey["memory.enabled"]).toMatchObject({
|
|
2546
|
+
type: "config_setting",
|
|
2547
|
+
config_key: "memory.enabled",
|
|
2548
|
+
config_value: "true",
|
|
2549
|
+
assistant_version: "1.2.3-test",
|
|
2550
|
+
});
|
|
2551
|
+
expect(typeof byKey["memory.enabled"].daemon_event_id).toBe("string");
|
|
2552
|
+
expect(typeof byKey["memory.enabled"].recorded_at).toBe("number");
|
|
2553
|
+
expect(byKey["memory.v2.enabled"]).toMatchObject({
|
|
2554
|
+
type: "config_setting",
|
|
2555
|
+
config_key: "memory.v2.enabled",
|
|
2556
|
+
config_value: "false",
|
|
2557
|
+
});
|
|
2558
|
+
});
|
|
2559
|
+
|
|
2560
|
+
test("config_setting rows are deleted on success and no watermark is written", async () => {
|
|
2561
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
2562
|
+
recordConfigSettingEvent({
|
|
2563
|
+
configKey: "memory.enabled",
|
|
2564
|
+
configValue: "true",
|
|
2565
|
+
});
|
|
2566
|
+
recordConfigSettingEvent({
|
|
2567
|
+
configKey: "memory.v2.enabled",
|
|
2568
|
+
configValue: "true",
|
|
2569
|
+
});
|
|
2570
|
+
expect(queryTelemetryOutboxBatch("config_setting", 10)).toHaveLength(2);
|
|
2571
|
+
mockFetch.mockImplementation(() =>
|
|
2572
|
+
Promise.resolve(new Response('{"accepted":2}', { status: 200 })),
|
|
2524
2573
|
);
|
|
2525
|
-
|
|
2526
|
-
|
|
2574
|
+
|
|
2575
|
+
const reporter = makeReporter();
|
|
2576
|
+
await reporter.flush();
|
|
2577
|
+
|
|
2578
|
+
// Ack-mode: acknowledged rows are deleted from the outbox instead of
|
|
2579
|
+
// advancing a `flush_checkpoints` watermark.
|
|
2580
|
+
expect(queryTelemetryOutboxBatch("config_setting", 10)).toHaveLength(0);
|
|
2581
|
+
const watermarkKeys = mockSetFlushCheckpoint.mock.calls
|
|
2582
|
+
.map((c) => c[0] as string)
|
|
2583
|
+
.filter((key) => key.startsWith("telemetry:config_setting:"));
|
|
2584
|
+
expect(watermarkKeys).toHaveLength(0);
|
|
2527
2585
|
});
|
|
2528
2586
|
|
|
2529
|
-
test("
|
|
2587
|
+
test("config_setting rows survive a failed POST for the next flush", async () => {
|
|
2588
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
2589
|
+
recordConfigSettingEvent({
|
|
2590
|
+
configKey: "memory.enabled",
|
|
2591
|
+
configValue: "true",
|
|
2592
|
+
});
|
|
2593
|
+
mockFetch.mockImplementation(() =>
|
|
2594
|
+
Promise.resolve(new Response("error", { status: 500 })),
|
|
2595
|
+
);
|
|
2596
|
+
|
|
2597
|
+
const reporter = makeReporter();
|
|
2598
|
+
await reporter.flush();
|
|
2599
|
+
|
|
2600
|
+
expect(queryTelemetryOutboxBatch("config_setting", 10)).toHaveLength(1);
|
|
2601
|
+
});
|
|
2602
|
+
|
|
2603
|
+
// -------------------------------------------------------------------------
|
|
2604
|
+
// Watchdog events
|
|
2605
|
+
// -------------------------------------------------------------------------
|
|
2606
|
+
|
|
2607
|
+
test("watchdog events ship the record-time payload with nested detail, then delete", async () => {
|
|
2530
2608
|
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2609
|
+
recordWatchdogEvent({
|
|
2610
|
+
checkName: "event_loop_blocked",
|
|
2611
|
+
value: 60000,
|
|
2612
|
+
detail: { reason: "no_bytes_60s", threshold_ms: 5000 },
|
|
2613
|
+
});
|
|
2614
|
+
mockFetch.mockImplementation(() =>
|
|
2615
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
2616
|
+
);
|
|
2617
|
+
|
|
2618
|
+
const reporter = makeReporter();
|
|
2619
|
+
await reporter.flush();
|
|
2620
|
+
|
|
2621
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
2622
|
+
const body = JSON.parse(
|
|
2623
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
2624
|
+
);
|
|
2625
|
+
expect(body.events).toHaveLength(1);
|
|
2626
|
+
expect(body.events[0]).toMatchObject({
|
|
2627
|
+
type: "watchdog",
|
|
2628
|
+
check_name: "event_loop_blocked",
|
|
2629
|
+
value: 60000,
|
|
2630
|
+
detail: { reason: "no_bytes_60s", threshold_ms: 5000 },
|
|
2631
|
+
assistant_version: "1.2.3-test",
|
|
2632
|
+
});
|
|
2633
|
+
expect(typeof body.events[0].daemon_event_id).toBe("string");
|
|
2634
|
+
expect(typeof body.events[0].recorded_at).toBe("number");
|
|
2635
|
+
expect(queryTelemetryOutboxBatch("watchdog", 10)).toHaveLength(0);
|
|
2636
|
+
});
|
|
2637
|
+
|
|
2638
|
+
// -------------------------------------------------------------------------
|
|
2639
|
+
// Ack-mode sources (grouped `ack` field: acknowledge + discardPending)
|
|
2640
|
+
// -------------------------------------------------------------------------
|
|
2641
|
+
|
|
2642
|
+
// Wire id deliberately differs from the row id: acknowledge must receive
|
|
2643
|
+
// ROW ids, never wire daemon_event_ids.
|
|
2644
|
+
function fakeWireEvent(rowId: string, createdAt: number): TelemetryEvent {
|
|
2645
|
+
return {
|
|
2646
|
+
type: "lifecycle",
|
|
2647
|
+
daemon_event_id: `wire-${rowId}`,
|
|
2648
|
+
event_name: "fake_event",
|
|
2649
|
+
recorded_at: createdAt,
|
|
2650
|
+
assistant_version: "1.2.3-test",
|
|
2651
|
+
};
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
/**
|
|
2655
|
+
* Ack-mode fake backed by a mutable in-memory row array: `collect` ignores
|
|
2656
|
+
* the cursor args, `acknowledge` deletes the acknowledged rows, and
|
|
2657
|
+
* `discardPending` drops everything — mirroring the outbox-store contract.
|
|
2658
|
+
*/
|
|
2659
|
+
function makeAckSource(id: string, rowIds: string[] = []) {
|
|
2660
|
+
let rows = rowIds.map((rowId, i) => ({
|
|
2661
|
+
rowId,
|
|
2535
2662
|
createdAt: 1700000000000 + i,
|
|
2536
|
-
skillName: "web-research",
|
|
2537
2663
|
}));
|
|
2538
|
-
|
|
2664
|
+
const acknowledge = mock((acked: string[]) => {
|
|
2665
|
+
rows = rows.filter((r) => !acked.includes(r.rowId));
|
|
2666
|
+
});
|
|
2667
|
+
const discardPending = mock(() => {
|
|
2668
|
+
rows = [];
|
|
2669
|
+
});
|
|
2670
|
+
const source: TelemetryEventSource = {
|
|
2671
|
+
id,
|
|
2672
|
+
collect(_afterCreatedAt, _afterId, limit) {
|
|
2673
|
+
const batch = rows.slice(0, limit);
|
|
2674
|
+
return {
|
|
2675
|
+
events: batch.map((r) => fakeWireEvent(r.rowId, r.createdAt)),
|
|
2676
|
+
rowIds: batch.map((r) => r.rowId),
|
|
2677
|
+
lastCursor: null,
|
|
2678
|
+
fullBatch: batch.length === limit,
|
|
2679
|
+
};
|
|
2680
|
+
},
|
|
2681
|
+
ack: { acknowledge, discardPending },
|
|
2682
|
+
};
|
|
2683
|
+
return { source, acknowledge, discardPending, pending: () => rows.length };
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
function makeWatermarkSource(
|
|
2687
|
+
id: string,
|
|
2688
|
+
rowIds: string[] = [],
|
|
2689
|
+
): TelemetryEventSource {
|
|
2690
|
+
const rows = rowIds.map((rowId, i) => ({
|
|
2691
|
+
rowId,
|
|
2692
|
+
createdAt: 1700000000000 + i,
|
|
2693
|
+
}));
|
|
2694
|
+
return {
|
|
2695
|
+
id,
|
|
2696
|
+
collect(_afterCreatedAt, _afterId, limit) {
|
|
2697
|
+
const batch = rows.slice(0, limit);
|
|
2698
|
+
const last = batch.length > 0 ? batch[batch.length - 1] : null;
|
|
2699
|
+
return {
|
|
2700
|
+
events: batch.map((r) => fakeWireEvent(r.rowId, r.createdAt)),
|
|
2701
|
+
lastCursor: last
|
|
2702
|
+
? { createdAt: last.createdAt, id: last.rowId }
|
|
2703
|
+
: null,
|
|
2704
|
+
fullBatch: batch.length === limit,
|
|
2705
|
+
};
|
|
2706
|
+
},
|
|
2707
|
+
};
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
test("ack-mode source: acknowledge receives ROW ids after a 2xx and no watermark keys are written", async () => {
|
|
2711
|
+
const { source, acknowledge } = makeAckSource("fake_ack", [
|
|
2712
|
+
"row-1",
|
|
2713
|
+
"row-2",
|
|
2714
|
+
]);
|
|
2715
|
+
const reporter = new UsageTelemetryReporter(
|
|
2716
|
+
[source],
|
|
2717
|
+
fakeFlushCheckpointStore,
|
|
2718
|
+
);
|
|
2719
|
+
await reporter.flush();
|
|
2720
|
+
|
|
2721
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
2722
|
+
const body = JSON.parse(
|
|
2723
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
2724
|
+
);
|
|
2725
|
+
expect(
|
|
2726
|
+
body.events.map((e: { daemon_event_id: string }) => e.daemon_event_id),
|
|
2727
|
+
).toEqual(["wire-row-1", "wire-row-2"]);
|
|
2728
|
+
expect(acknowledge).toHaveBeenCalledTimes(1);
|
|
2729
|
+
expect(acknowledge.mock.calls[0][0]).toEqual(["row-1", "row-2"]);
|
|
2730
|
+
// Ack-mode sources never touch flush_checkpoints.
|
|
2731
|
+
expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
|
|
2732
|
+
});
|
|
2733
|
+
|
|
2734
|
+
test("acknowledge is NOT called when the POST fails", async () => {
|
|
2539
2735
|
mockFetch.mockImplementation(() =>
|
|
2540
|
-
Promise.resolve(new Response(
|
|
2736
|
+
Promise.resolve(new Response("error", { status: 500 })),
|
|
2737
|
+
);
|
|
2738
|
+
const { source, acknowledge, pending } = makeAckSource("fake_ack", [
|
|
2739
|
+
"row-1",
|
|
2740
|
+
]);
|
|
2741
|
+
const reporter = new UsageTelemetryReporter(
|
|
2742
|
+
[source],
|
|
2743
|
+
fakeFlushCheckpointStore,
|
|
2541
2744
|
);
|
|
2745
|
+
await reporter.flush();
|
|
2746
|
+
|
|
2747
|
+
expect(acknowledge).not.toHaveBeenCalled();
|
|
2748
|
+
expect(pending()).toBe(1);
|
|
2749
|
+
expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
|
|
2750
|
+
});
|
|
2542
2751
|
|
|
2543
|
-
|
|
2752
|
+
test("acknowledge is NOT called when platform credentials are missing", async () => {
|
|
2753
|
+
mockPlatformClient = null;
|
|
2754
|
+
const { source, acknowledge, pending } = makeAckSource("fake_ack", [
|
|
2755
|
+
"row-1",
|
|
2756
|
+
]);
|
|
2757
|
+
const reporter = new UsageTelemetryReporter(
|
|
2758
|
+
[source],
|
|
2759
|
+
fakeFlushCheckpointStore,
|
|
2760
|
+
);
|
|
2544
2761
|
await reporter.flush();
|
|
2545
2762
|
|
|
2546
|
-
|
|
2547
|
-
expect(
|
|
2763
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
2764
|
+
expect(acknowledge).not.toHaveBeenCalled();
|
|
2765
|
+
expect(pending()).toBe(1);
|
|
2766
|
+
});
|
|
2767
|
+
|
|
2768
|
+
test("acknowledge is skipped for an empty ack batch shipped alongside a watermark source", async () => {
|
|
2769
|
+
const { source: ackSource, acknowledge } = makeAckSource("fake_ack");
|
|
2770
|
+
const wmSource = makeWatermarkSource("fake_wm", ["wm-1"]);
|
|
2771
|
+
const reporter = new UsageTelemetryReporter(
|
|
2772
|
+
[wmSource, ackSource],
|
|
2773
|
+
fakeFlushCheckpointStore,
|
|
2774
|
+
);
|
|
2775
|
+
await reporter.flush();
|
|
2776
|
+
|
|
2777
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
2778
|
+
expect(acknowledge).not.toHaveBeenCalled();
|
|
2779
|
+
});
|
|
2780
|
+
|
|
2781
|
+
test("opt-out: discardPending drops ack-mode rows while watermark sources get the sentinel pin", async () => {
|
|
2782
|
+
mockGetCachedShareAnalytics.mockReturnValue(false);
|
|
2783
|
+
const {
|
|
2784
|
+
source: ackSource,
|
|
2785
|
+
discardPending,
|
|
2786
|
+
pending,
|
|
2787
|
+
} = makeAckSource("fake_ack", ["row-1"]);
|
|
2788
|
+
const wmSource = makeWatermarkSource("fake_wm", ["wm-1"]);
|
|
2789
|
+
const reporter = new UsageTelemetryReporter(
|
|
2790
|
+
[wmSource, ackSource],
|
|
2791
|
+
fakeFlushCheckpointStore,
|
|
2792
|
+
);
|
|
2793
|
+
await reporter.flush();
|
|
2794
|
+
|
|
2795
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
2796
|
+
expect(discardPending).toHaveBeenCalledTimes(1);
|
|
2797
|
+
expect(pending()).toBe(0);
|
|
2798
|
+
|
|
2799
|
+
const keys = mockSetFlushCheckpoint.mock.calls.map((c) => c[0]);
|
|
2800
|
+
expect(keys).toContain("telemetry:fake_wm:last_reported_at");
|
|
2801
|
+
const wmIdCall = mockSetFlushCheckpoint.mock.calls.find(
|
|
2802
|
+
(c) => c[0] === "telemetry:fake_wm:last_reported_id",
|
|
2803
|
+
);
|
|
2804
|
+
expect(wmIdCall?.[1]).toBe("ffffffff-ffff-ffff-ffff-ffffffffffff");
|
|
2805
|
+
expect(keys.some((k) => k.includes("fake_ack"))).toBe(false);
|
|
2806
|
+
});
|
|
2807
|
+
|
|
2808
|
+
test("mixed watermark+ack sources flush in construction order, each acknowledged in its own mode", async () => {
|
|
2809
|
+
const wmSource = makeWatermarkSource("fake_wm", ["wm-1", "wm-2"]);
|
|
2810
|
+
const { source: ackSource, acknowledge } = makeAckSource("fake_ack", [
|
|
2811
|
+
"row-1",
|
|
2812
|
+
]);
|
|
2813
|
+
const reporter = new UsageTelemetryReporter(
|
|
2814
|
+
[wmSource, ackSource],
|
|
2815
|
+
fakeFlushCheckpointStore,
|
|
2816
|
+
);
|
|
2817
|
+
await reporter.flush();
|
|
2818
|
+
|
|
2819
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
2820
|
+
const body = JSON.parse(
|
|
2821
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
2822
|
+
);
|
|
2823
|
+
expect(
|
|
2824
|
+
body.events.map((e: { daemon_event_id: string }) => e.daemon_event_id),
|
|
2825
|
+
).toEqual(["wire-wm-1", "wire-wm-2", "wire-row-1"]);
|
|
2826
|
+
|
|
2827
|
+
expect(acknowledge).toHaveBeenCalledTimes(1);
|
|
2828
|
+
expect(acknowledge.mock.calls[0][0]).toEqual(["row-1"]);
|
|
2829
|
+
|
|
2830
|
+
const wmAtCalls = mockSetFlushCheckpoint.mock.calls.filter(
|
|
2831
|
+
(c) => c[0] === "telemetry:fake_wm:last_reported_at",
|
|
2832
|
+
);
|
|
2833
|
+
expect(wmAtCalls[wmAtCalls.length - 1][1]).toBe(String(1700000000001));
|
|
2834
|
+
const wmIdCalls = mockSetFlushCheckpoint.mock.calls.filter(
|
|
2835
|
+
(c) => c[0] === "telemetry:fake_wm:last_reported_id",
|
|
2836
|
+
);
|
|
2837
|
+
expect(wmIdCalls[wmIdCalls.length - 1][1]).toBe("wm-2");
|
|
2838
|
+
expect(
|
|
2839
|
+
mockSetFlushCheckpoint.mock.calls.some((c) => c[0].includes("fake_ack")),
|
|
2840
|
+
).toBe(false);
|
|
2841
|
+
});
|
|
2842
|
+
|
|
2843
|
+
test("an ack-mode full batch drives recursion until the backlog drains", async () => {
|
|
2844
|
+
// 501 rows: the first collect fills BATCH_SIZE (500), acknowledge drains
|
|
2845
|
+
// them, and the fullBatch recursion ships the remaining row.
|
|
2846
|
+
const rowIds = Array.from({ length: 501 }, (_, i) => `row-${i}`);
|
|
2847
|
+
const { source, acknowledge, pending } = makeAckSource("fake_ack", rowIds);
|
|
2848
|
+
const reporter = new UsageTelemetryReporter(
|
|
2849
|
+
[source],
|
|
2850
|
+
fakeFlushCheckpointStore,
|
|
2851
|
+
);
|
|
2852
|
+
await reporter.flush();
|
|
2853
|
+
|
|
2854
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
2855
|
+
expect(acknowledge).toHaveBeenCalledTimes(2);
|
|
2856
|
+
expect(acknowledge.mock.calls[0][0]).toHaveLength(500);
|
|
2857
|
+
expect(acknowledge.mock.calls[1][0]).toEqual(["row-500"]);
|
|
2858
|
+
expect(pending()).toBe(0);
|
|
2859
|
+
});
|
|
2860
|
+
|
|
2861
|
+
/** Ack source whose collect returns events with broken rowIds. */
|
|
2862
|
+
function makeBrokenAckSource(id: string, rowIds: string[] | undefined) {
|
|
2863
|
+
const acknowledge = mock((_acked: string[]) => {});
|
|
2864
|
+
const discardPending = mock(() => {});
|
|
2865
|
+
const source: TelemetryEventSource = {
|
|
2866
|
+
id,
|
|
2867
|
+
collect() {
|
|
2868
|
+
return {
|
|
2869
|
+
events: [
|
|
2870
|
+
fakeWireEvent("row-1", 1700000000000),
|
|
2871
|
+
fakeWireEvent("row-2", 1700000000001),
|
|
2872
|
+
],
|
|
2873
|
+
...(rowIds ? { rowIds } : {}),
|
|
2874
|
+
lastCursor: null,
|
|
2875
|
+
fullBatch: false,
|
|
2876
|
+
};
|
|
2877
|
+
},
|
|
2878
|
+
ack: { acknowledge, discardPending },
|
|
2879
|
+
};
|
|
2880
|
+
return { source, acknowledge };
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
test("an ack batch omitting rowIds ships nothing while other sources still ship", async () => {
|
|
2884
|
+
const { source: brokenSource, acknowledge } = makeBrokenAckSource(
|
|
2885
|
+
"fake_broken_ack",
|
|
2886
|
+
undefined,
|
|
2887
|
+
);
|
|
2888
|
+
const wmSource = makeWatermarkSource("fake_wm", ["wm-1"]);
|
|
2889
|
+
const reporter = new UsageTelemetryReporter(
|
|
2890
|
+
[brokenSource, wmSource],
|
|
2891
|
+
fakeFlushCheckpointStore,
|
|
2892
|
+
);
|
|
2893
|
+
await reporter.flush();
|
|
2894
|
+
|
|
2895
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
2896
|
+
const body = JSON.parse(
|
|
2897
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
2898
|
+
);
|
|
2899
|
+
expect(
|
|
2900
|
+
body.events.map((e: { daemon_event_id: string }) => e.daemon_event_id),
|
|
2901
|
+
).toEqual(["wire-wm-1"]);
|
|
2902
|
+
expect(acknowledge).not.toHaveBeenCalled();
|
|
2903
|
+
expect(
|
|
2904
|
+
mockSetFlushCheckpoint.mock.calls.some((c) =>
|
|
2905
|
+
c[0].includes("fake_broken_ack"),
|
|
2906
|
+
),
|
|
2907
|
+
).toBe(false);
|
|
2908
|
+
});
|
|
2909
|
+
|
|
2910
|
+
test("an ack batch with mismatched rowIds ships nothing while other sources still ship", async () => {
|
|
2911
|
+
const { source: brokenSource, acknowledge } = makeBrokenAckSource(
|
|
2912
|
+
"fake_broken_ack",
|
|
2913
|
+
["row-1"],
|
|
2914
|
+
);
|
|
2915
|
+
const wmSource = makeWatermarkSource("fake_wm", ["wm-1"]);
|
|
2916
|
+
const reporter = new UsageTelemetryReporter(
|
|
2917
|
+
[brokenSource, wmSource],
|
|
2918
|
+
fakeFlushCheckpointStore,
|
|
2919
|
+
);
|
|
2920
|
+
await reporter.flush();
|
|
2921
|
+
|
|
2922
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
2923
|
+
const body = JSON.parse(
|
|
2924
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
2925
|
+
);
|
|
2926
|
+
expect(
|
|
2927
|
+
body.events.map((e: { daemon_event_id: string }) => e.daemon_event_id),
|
|
2928
|
+
).toEqual(["wire-wm-1"]);
|
|
2929
|
+
expect(acknowledge).not.toHaveBeenCalled();
|
|
2930
|
+
});
|
|
2931
|
+
|
|
2932
|
+
// -------------------------------------------------------------------------
|
|
2933
|
+
// outboxSource (real telemetry_events outbox behind an ack-mode source)
|
|
2934
|
+
// -------------------------------------------------------------------------
|
|
2935
|
+
|
|
2936
|
+
// Synthetic name: these tests exercise the generic outbox machinery, not a
|
|
2937
|
+
// shipped event type.
|
|
2938
|
+
const OUTBOX_NAME = "test_outbox" as OutboxTelemetryEventName;
|
|
2939
|
+
|
|
2940
|
+
function seedOutboxRow(id: string, createdAt: number): void {
|
|
2941
|
+
insertTelemetryOutboxEvent({
|
|
2942
|
+
id,
|
|
2943
|
+
name: OUTBOX_NAME,
|
|
2944
|
+
createdAt,
|
|
2945
|
+
event: fakeWireEvent(id, createdAt),
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
function pendingOutboxIds(): string[] {
|
|
2950
|
+
return queryTelemetryOutboxBatch(OUTBOX_NAME, 1000).map((r) => r.id);
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
test("outboxSource ships pending rows and deletes them after a 2xx", async () => {
|
|
2954
|
+
seedOutboxRow("ob-1", 1700000001000);
|
|
2955
|
+
seedOutboxRow("ob-2", 1700000002000);
|
|
2956
|
+
const reporter = new UsageTelemetryReporter(
|
|
2957
|
+
[outboxSource(OUTBOX_NAME)],
|
|
2958
|
+
fakeFlushCheckpointStore,
|
|
2959
|
+
);
|
|
2960
|
+
await reporter.flush();
|
|
2961
|
+
|
|
2962
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
2963
|
+
const body = JSON.parse(
|
|
2964
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
2965
|
+
);
|
|
2966
|
+
expect(
|
|
2967
|
+
body.events.map((e: { daemon_event_id: string }) => e.daemon_event_id),
|
|
2968
|
+
).toEqual(["wire-ob-1", "wire-ob-2"]);
|
|
2969
|
+
expect(pendingOutboxIds()).toEqual([]);
|
|
2970
|
+
// Ack-mode: flush_checkpoints untouched.
|
|
2971
|
+
expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
|
|
2972
|
+
});
|
|
2973
|
+
|
|
2974
|
+
test("outboxSource leaves rows intact when the POST fails", async () => {
|
|
2975
|
+
mockFetch.mockImplementation(() =>
|
|
2976
|
+
Promise.resolve(new Response("error", { status: 500 })),
|
|
2977
|
+
);
|
|
2978
|
+
seedOutboxRow("ob-1", 1700000001000);
|
|
2979
|
+
const reporter = new UsageTelemetryReporter(
|
|
2980
|
+
[outboxSource(OUTBOX_NAME)],
|
|
2981
|
+
fakeFlushCheckpointStore,
|
|
2982
|
+
);
|
|
2983
|
+
await reporter.flush();
|
|
2984
|
+
|
|
2985
|
+
expect(pendingOutboxIds()).toEqual(["ob-1"]);
|
|
2986
|
+
expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
|
|
2987
|
+
});
|
|
2988
|
+
|
|
2989
|
+
test("outboxSource opt-out discards all pending rows for the name", async () => {
|
|
2990
|
+
mockGetCachedShareAnalytics.mockReturnValue(false);
|
|
2991
|
+
seedOutboxRow("ob-1", 1700000001000);
|
|
2992
|
+
seedOutboxRow("ob-2", 1700000002000);
|
|
2993
|
+
const reporter = new UsageTelemetryReporter(
|
|
2994
|
+
[outboxSource(OUTBOX_NAME)],
|
|
2995
|
+
fakeFlushCheckpointStore,
|
|
2996
|
+
);
|
|
2997
|
+
await reporter.flush();
|
|
2998
|
+
|
|
2999
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
3000
|
+
expect(pendingOutboxIds()).toEqual([]);
|
|
3001
|
+
expect(mockSetFlushCheckpoint).not.toHaveBeenCalled();
|
|
3002
|
+
});
|
|
3003
|
+
|
|
3004
|
+
test("outboxSource purges corrupt-payload rows at collect while valid siblings ship", async () => {
|
|
3005
|
+
// Raw inserts: the store API only writes JSON.stringify-ed events, so
|
|
3006
|
+
// corrupt payloads (invalid JSON, non-object JSON) go in directly.
|
|
3007
|
+
getTelemetryDb()!
|
|
3008
|
+
.insert(telemetryEvents)
|
|
3009
|
+
.values([
|
|
3010
|
+
{
|
|
3011
|
+
id: "ob-corrupt-json",
|
|
3012
|
+
name: OUTBOX_NAME,
|
|
3013
|
+
createdAt: 1700000001000,
|
|
3014
|
+
conversationId: null,
|
|
3015
|
+
payload: "{not json",
|
|
3016
|
+
},
|
|
3017
|
+
{
|
|
3018
|
+
id: "ob-non-object",
|
|
3019
|
+
name: OUTBOX_NAME,
|
|
3020
|
+
createdAt: 1700000002000,
|
|
3021
|
+
conversationId: null,
|
|
3022
|
+
payload: "42",
|
|
3023
|
+
},
|
|
3024
|
+
])
|
|
3025
|
+
.run();
|
|
3026
|
+
seedOutboxRow("ob-valid", 1700000003000);
|
|
3027
|
+
const reporter = new UsageTelemetryReporter(
|
|
3028
|
+
[outboxSource(OUTBOX_NAME)],
|
|
3029
|
+
fakeFlushCheckpointStore,
|
|
3030
|
+
);
|
|
3031
|
+
await reporter.flush();
|
|
3032
|
+
|
|
3033
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
3034
|
+
const body = JSON.parse(
|
|
3035
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
3036
|
+
);
|
|
3037
|
+
expect(
|
|
3038
|
+
body.events.map((e: { daemon_event_id: string }) => e.daemon_event_id),
|
|
3039
|
+
).toEqual(["wire-ob-valid"]);
|
|
3040
|
+
// Corrupt rows are gone (purged at collect), the valid row via ack.
|
|
3041
|
+
expect(pendingOutboxIds()).toEqual([]);
|
|
3042
|
+
});
|
|
3043
|
+
|
|
3044
|
+
test("a full outbox batch drives recursion until the backlog drains", async () => {
|
|
3045
|
+
// 501 rows: the first collect fills BATCH_SIZE (500) and the fullBatch
|
|
3046
|
+
// recursion ships the remaining row.
|
|
3047
|
+
for (let i = 0; i < 501; i++) {
|
|
3048
|
+
seedOutboxRow(`ob-${String(i).padStart(3, "0")}`, 1700000000000 + i);
|
|
3049
|
+
}
|
|
3050
|
+
const reporter = new UsageTelemetryReporter(
|
|
3051
|
+
[outboxSource(OUTBOX_NAME)],
|
|
3052
|
+
fakeFlushCheckpointStore,
|
|
3053
|
+
);
|
|
3054
|
+
await reporter.flush();
|
|
3055
|
+
|
|
3056
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
3057
|
+
const secondBody = JSON.parse(
|
|
3058
|
+
(mockFetch.mock.calls[1] as [string, RequestInit])[1].body as string,
|
|
3059
|
+
);
|
|
3060
|
+
expect(secondBody.events).toHaveLength(1);
|
|
3061
|
+
expect(pendingOutboxIds()).toEqual([]);
|
|
3062
|
+
});
|
|
3063
|
+
|
|
3064
|
+
// -------------------------------------------------------------------------
|
|
3065
|
+
// Lifecycle events (outbox-backed)
|
|
3066
|
+
// -------------------------------------------------------------------------
|
|
3067
|
+
|
|
3068
|
+
test("a recorded lifecycle event flushes once and its outbox row is deleted", async () => {
|
|
3069
|
+
mockQueryUnreportedUsageEvents.mockReturnValue([]);
|
|
3070
|
+
const recorded = recordLifecycleEvent("app_open");
|
|
3071
|
+
expect(recorded).not.toBeNull();
|
|
3072
|
+
mockFetch.mockImplementation(() =>
|
|
3073
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
3074
|
+
);
|
|
3075
|
+
|
|
3076
|
+
const reporter = makeReporter();
|
|
3077
|
+
await reporter.flush();
|
|
3078
|
+
|
|
3079
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
3080
|
+
const body = JSON.parse(
|
|
3081
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
3082
|
+
);
|
|
3083
|
+
expect(body.events).toHaveLength(1);
|
|
3084
|
+
// The stored payload IS the wire event — record-time version included.
|
|
3085
|
+
expect(body.events[0]).toEqual({
|
|
3086
|
+
type: "lifecycle",
|
|
3087
|
+
daemon_event_id: recorded!.id,
|
|
3088
|
+
event_name: "app_open",
|
|
3089
|
+
recorded_at: recorded!.createdAt,
|
|
3090
|
+
assistant_version: "1.2.3-test",
|
|
3091
|
+
});
|
|
3092
|
+
// Delete-on-flush: the outbox row is gone, and lifecycle (ack-mode)
|
|
3093
|
+
// never touches flush_checkpoints.
|
|
3094
|
+
expect(queryTelemetryOutboxBatch("lifecycle", 10)).toEqual([]);
|
|
3095
|
+
expect(
|
|
3096
|
+
mockSetFlushCheckpoint.mock.calls.some((c) =>
|
|
3097
|
+
c[0].includes(":lifecycle:"),
|
|
3098
|
+
),
|
|
3099
|
+
).toBe(false);
|
|
3100
|
+
|
|
3101
|
+
// A second flush finds nothing pending — the event never re-ships.
|
|
3102
|
+
mockFetch.mockClear();
|
|
3103
|
+
await reporter.flush();
|
|
3104
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
2548
3105
|
});
|
|
2549
3106
|
});
|