@vellumai/assistant 0.10.9 → 0.10.10-dev.202607162206.d08e98e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +1 -1
- package/Dockerfile +8 -0
- package/docs/activation-funnel-telemetry.md +13 -7
- 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/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 +530 -107
- package/package.json +1 -1
- package/scripts/generate-openapi.ts +8 -0
- package/src/__tests__/activation-early-marking.test.ts +6 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +22 -25
- package/src/__tests__/agent-wake-override-profile.test.ts +21 -48
- package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
- package/src/__tests__/app-bundler.test.ts +4 -12
- package/src/__tests__/app-executors.test.ts +2 -49
- package/src/__tests__/app-routes-csp.test.ts +114 -146
- package/src/__tests__/auth-fallback-events-store.test.ts +8 -1
- package/src/__tests__/build-persisted-content.test.ts +96 -0
- package/src/__tests__/bundle-scanner.test.ts +27 -1
- package/src/__tests__/call-controller.test.ts +291 -0
- package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +165 -0
- package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
- package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/compactor-image-manifest-trust.test.ts +50 -0
- package/src/__tests__/config-loader-backfill.test.ts +9 -4
- package/src/__tests__/config-schema-cmd.test.ts +10 -11
- package/src/__tests__/config-schema.test.ts +190 -257
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +208 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +5 -19
- package/src/__tests__/conversation-agent-loop.test.ts +22 -9
- package/src/__tests__/conversation-error.test.ts +31 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +110 -0
- package/src/__tests__/conversation-process-callsite.test.ts +12 -19
- package/src/__tests__/conversation-routes-slash-commands.test.ts +7 -21
- package/src/__tests__/conversation-summarize-route.test.ts +36 -44
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +7 -7
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -1
- package/src/__tests__/conversation-usage.test.ts +4 -12
- package/src/__tests__/credential-routes.test.ts +250 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/db-migration-rollback.test.ts +22 -0
- package/src/__tests__/empty-response-hook.test.ts +186 -1
- package/src/__tests__/external-plugin-loader.test.ts +11 -5
- package/src/__tests__/heartbeat-service.test.ts +0 -28
- package/src/__tests__/host-shell-tool.test.ts +2 -0
- package/src/__tests__/inactive-tool-error-messages.test.ts +2 -2
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
- package/src/__tests__/internal-telemetry-routes.test.ts +23 -4
- package/src/__tests__/invite-routes-http.test.ts +12 -16
- package/src/__tests__/list-all-apps.test.ts +0 -4
- package/src/__tests__/llm-context-resolution.test.ts +32 -56
- package/src/__tests__/llm-request-log-turn-query.test.ts +109 -0
- 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__/max-tokens-continue-hook.test.ts +19 -0
- package/src/__tests__/media-stream-output.test.ts +259 -3
- package/src/__tests__/media-stream-server-integration.test.ts +22 -1
- package/src/__tests__/media-stream-stt-session.test.ts +47 -0
- package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -0
- package/src/__tests__/memory-recall-log-store.test.ts +47 -13
- package/src/__tests__/mock-gateway-ipc.ts +46 -1
- package/src/__tests__/mtime-cache.test.ts +61 -0
- package/src/__tests__/navigate-settings-tab.test.ts +2 -0
- package/src/__tests__/normalize-onboarding.test.ts +33 -0
- package/src/__tests__/onboarding-persona-write.test.ts +26 -0
- package/src/__tests__/plugin-api-resolve-credential.test.ts +140 -0
- package/src/__tests__/plugin-app-serve-routes.test.ts +166 -14
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +38 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +27 -20
- package/src/__tests__/provider-connections-backfill.test.ts +138 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +10 -31
- package/src/__tests__/provider-registry-ollama.test.ts +8 -18
- package/src/__tests__/provider-send-message-override-profile.test.ts +23 -23
- package/src/__tests__/provider-usage-tracking.test.ts +9 -19
- package/src/__tests__/prune-old-conversations-job.test.ts +12 -0
- package/src/__tests__/published-app-updater.test.ts +22 -16
- package/src/__tests__/registry.test.ts +5 -16
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +22 -23
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +43 -48
- package/src/__tests__/retry-thinking-tool-choice.test.ts +57 -66
- package/src/__tests__/retry-verbosity-normalization.test.ts +24 -23
- package/src/__tests__/reveal-success-registry.test.ts +123 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +130 -0
- package/src/__tests__/secret-fixtures.ts +9 -0
- package/src/__tests__/server-history-render.test.ts +28 -0
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/slack-share-routes.test.ts +0 -1
- package/src/__tests__/stt-stream-session.test.ts +6 -5
- package/src/__tests__/subagent-call-site-routing.test.ts +69 -95
- package/src/__tests__/subagent-disposal.test.ts +2 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +2 -0
- package/src/__tests__/subagent-manager-notify.test.ts +2 -0
- package/src/__tests__/subagent-role-registry.test.ts +37 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
- package/src/__tests__/subagent-terminal-message.test.ts +50 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -5
- package/src/__tests__/surface-completion-nudge-hook.test.ts +19 -0
- package/src/__tests__/telemetry-routes.test.ts +99 -19
- package/src/__tests__/tool-audit.test.ts +34 -4
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-profiler.test.ts +72 -1
- package/src/__tests__/tool-result-spool.test.ts +49 -4
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
- package/src/__tests__/ui-channel-variants.test.ts +108 -0
- package/src/__tests__/ui-shape-teaching.test.ts +255 -0
- package/src/__tests__/usage-attribution.test.ts +18 -41
- package/src/__tests__/user-plugin-loader.test.ts +4 -4
- package/src/__tests__/voice-config-update.test.ts +46 -0
- package/src/__tests__/voice-session-bridge.test.ts +216 -84
- package/src/__tests__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
- package/src/agent/loop.ts +4 -3
- 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/api/responses/conversation-message.ts +5 -0
- package/src/apps/app-store.ts +25 -35
- 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__/voice-session-bridge.test.ts +47 -0
- package/src/calls/call-constants.ts +5 -0
- package/src/calls/call-controller.ts +100 -32
- package/src/calls/call-transport.ts +9 -0
- package/src/calls/media-stream-output.ts +107 -4
- package/src/calls/media-stream-server.ts +29 -9
- package/src/calls/media-stream-stt-session.ts +7 -1
- package/src/calls/media-turn-detector.ts +11 -1
- package/src/calls/voice-session-bridge.ts +84 -63
- package/src/cli/commands/__tests__/inference-providers.test.ts +270 -33
- package/src/cli/commands/__tests__/notifications.test.ts +24 -3
- package/src/cli/commands/config.help.ts +6 -6
- package/src/cli/commands/credentials.help.ts +13 -0
- package/src/cli/commands/credentials.ts +6 -1
- package/src/cli/commands/email.help.ts +7 -0
- package/src/cli/commands/email.ts +35 -1
- package/src/cli/commands/inference-providers.ts +168 -107
- package/src/cli/commands/inference.help.ts +118 -46
- package/src/cli/commands/memory/index.help.ts +23 -0
- package/src/cli/commands/memory/nodes.ts +146 -0
- package/src/cli/commands/notifications.help.ts +10 -10
- package/src/cli/commands/oauth/connect-surface-guidance.test.ts +40 -0
- package/src/cli/commands/oauth/connect-surface-guidance.ts +54 -0
- package/src/cli/commands/oauth/connect.test.ts +126 -0
- package/src/cli/commands/oauth/connect.ts +29 -6
- package/src/cli/commands/oauth/index.help.ts +7 -1
- package/src/cli/commands/oauth/status.test.ts +69 -3
- package/src/cli/commands/oauth/status.ts +50 -12
- package/src/cli/commands/plugins.help.ts +13 -2
- package/src/cli/commands/plugins.ts +61 -7
- package/src/cli/commands/telemetry.help.ts +13 -0
- package/src/cli/commands/telemetry.ts +45 -5
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +8 -2
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +213 -9
- package/src/cli/lib/bundled-marketplace.json +93 -36
- package/src/cli/lib/inspect-plugin.ts +5 -1
- package/src/cli/lib/install-from-github.ts +68 -4
- package/src/cli/lib/plugin-catalog-local.ts +6 -2
- package/src/cli/lib/plugin-fingerprint.ts +3 -3
- package/src/cli/lib/upgrade-plugin.ts +236 -35
- package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
- package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +137 -0
- package/src/config/__tests__/profile-materialization.test.ts +1 -88
- package/src/config/bundled-skills/AGENTS.md +3 -30
- package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
- package/src/config/bundled-skills/app-builder/TOOLS.json +23 -0
- package/src/config/bundled-skills/app-builder/tools/app-open.ts +32 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +3 -1
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +2 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +18 -10
- package/src/config/bundled-skills/subagent/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
- package/src/config/call-site-defaults.ts +3 -3
- package/src/config/feature-flag-registry.json +24 -39
- package/src/config/llm-resolver.ts +88 -475
- package/src/config/profile-materialization.ts +11 -11
- package/src/config/schema.ts +93 -127
- package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
- package/src/config/schemas/__tests__/stt.test.ts +31 -3
- package/src/config/schemas/live-voice.ts +10 -4
- package/src/config/schemas/llm.ts +52 -15
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +8 -0
- package/src/config/schemas/memory-v2.ts +2 -2
- package/src/config/schemas/memory-v3.ts +1 -1
- package/src/config/schemas/services.ts +6 -3
- package/src/config/schemas/stt.ts +38 -21
- package/src/config/schemas/tts.ts +13 -18
- package/src/config/skills.ts +31 -21
- package/src/context/compactor.ts +44 -7
- package/src/context/post-turn-tool-result-truncation.ts +4 -2
- package/src/context/tool-result-spool.ts +26 -5
- package/src/conversations/__tests__/message-consolidation.test.ts +48 -0
- package/src/conversations/message-consolidation.ts +22 -2
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +5 -12
- 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 +627 -34
- package/src/daemon/conversation-agent-loop.ts +71 -31
- package/src/daemon/conversation-error.ts +36 -14
- package/src/daemon/conversation-process.ts +22 -0
- package/src/daemon/conversation-store.ts +35 -0
- package/src/daemon/conversation-surfaces.ts +25 -25
- package/src/daemon/conversation-tool-setup.ts +33 -7
- package/src/daemon/conversation.ts +55 -5
- package/src/daemon/handlers/shared.ts +33 -2
- package/src/daemon/lifecycle.ts +4 -4
- package/src/daemon/message-types/conversations.ts +6 -17
- package/src/daemon/providers-setup.ts +8 -0
- package/src/daemon/tool-setup-types.ts +7 -1
- package/src/daemon/wake-conversation-ops.ts +10 -1
- package/src/hooks/types.ts +9 -0
- package/src/ipc/__tests__/email-ipc.test.ts +90 -0
- package/src/ipc/gateway-client.test.ts +59 -0
- package/src/ipc/gateway-client.ts +70 -27
- package/src/live-voice/__tests__/live-voice-events.test.ts +14 -2
- package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
- package/src/live-voice/__tests__/live-voice-vad.test.ts +804 -13
- package/src/live-voice/__tests__/protocol.test.ts +122 -0
- package/src/live-voice/live-voice-session.ts +443 -40
- package/src/live-voice/protocol.ts +143 -1
- package/src/monitoring/__tests__/plugin-source-watch.test.ts +3 -1
- package/src/monitoring/plugin-source-watch.ts +3 -62
- package/src/notifications/README.md +1 -1
- package/src/permissions/checker.ts +8 -4
- package/src/persistence/__tests__/db-init-migrations-ok.test.ts +26 -0
- package/src/persistence/conversation-crud.ts +104 -2
- package/src/persistence/db-init.ts +14 -3
- package/src/persistence/job-handlers/cleanup.ts +15 -7
- package/src/persistence/llm-request-log-store.ts +88 -52
- package/src/persistence/migrations/298-move-memory-jobs-to-memory-db.ts +7 -31
- package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
- package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +8 -34
- package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +90 -0
- package/src/persistence/migrations/337-move-memory-recall-logs-to-memory-db.ts +114 -0
- package/src/persistence/migrations/338-move-memory-v3-selections-to-memory-db.ts +84 -0
- package/src/persistence/migrations/339-move-activation-sessions-to-memory-db.ts +52 -0
- package/src/persistence/migrations/__tests__/run-migrations.test.ts +155 -0
- package/src/persistence/migrations/helpers/relocation.ts +44 -1
- package/src/persistence/migrations/run-migrations.ts +25 -1
- package/src/persistence/schema/infrastructure.ts +9 -0
- package/src/persistence/schema/memory-core.ts +3 -0
- package/src/persistence/schema/memory-injection.ts +2 -0
- package/src/persistence/steps.ts +36 -0
- package/src/platform/client.test.ts +1 -44
- package/src/platform/client.ts +10 -20
- package/src/platform/consent-cache.test.ts +89 -23
- package/src/platform/consent-cache.ts +71 -33
- package/src/plugin-api/constants.ts +12 -0
- package/src/plugin-api/conversation-turn.ts +37 -14
- package/src/plugin-api/index.ts +11 -1
- package/src/plugin-api/resolve-credential.ts +75 -0
- package/src/plugin-api/vision-support.test.ts +8 -19
- package/src/plugin-api/vision-support.ts +26 -17
- package/src/plugins/collect-source-versions.ts +77 -0
- package/src/plugins/defaults/compaction/compact.ts +6 -0
- package/src/plugins/defaults/compaction/window-manager.ts +9 -0
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +18 -24
- package/src/plugins/defaults/empty-response/hooks/user-prompt-submit.ts +38 -0
- package/src/plugins/defaults/empty-response/refusal-quarantine.ts +99 -0
- package/src/plugins/defaults/image-fallback/__tests__/caption-cache-persistence.test.ts +7 -3
- package/src/plugins/defaults/index.ts +8 -1
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +48 -6
- package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +28 -22
- package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +148 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +80 -8
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +202 -0
- package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +64 -25
- package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +24 -11
- package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +70 -6
- package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +278 -0
- package/src/plugins/defaults/memory/activation-session-store.ts +27 -20
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -9
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -8
- package/src/plugins/defaults/memory/graph/retriever.test.ts +6 -6
- package/src/plugins/defaults/memory/graph/store.ts +114 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +36 -1
- package/src/plugins/defaults/memory/graph/tools.ts +47 -13
- package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +16 -35
- package/src/plugins/defaults/memory/jobs-worker.ts +10 -1
- package/src/plugins/defaults/memory/memory-db.ts +3 -2
- package/src/plugins/defaults/memory/memory-recall-log-store.ts +129 -66
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +8 -0
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +11 -122
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
- package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +84 -46
- package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +42 -32
- package/src/plugins/defaults/memory/path-containment.ts +21 -0
- package/src/plugins/defaults/memory/prompt-override.ts +51 -13
- package/src/plugins/defaults/memory/tools.test.ts +34 -0
- package/src/plugins/defaults/memory/tools.ts +12 -2
- package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +19 -15
- package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +24 -19
- package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +19 -15
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +22 -2
- package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +5 -3
- package/src/plugins/defaults/memory/v2/harness/oracle.ts +59 -41
- package/src/plugins/defaults/memory/v2/harness/replay-input.ts +29 -25
- package/src/plugins/defaults/memory/v2/migration.ts +46 -16
- package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +17 -15
- package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +24 -22
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +9 -5
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +277 -10
- package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +57 -5
- package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +9 -7
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +38 -7
- package/src/plugins/defaults/memory/v3/hot-set.test.ts +36 -18
- package/src/plugins/defaults/memory/v3/hot-set.ts +12 -14
- package/src/plugins/defaults/memory/v3/learned-edges.test.ts +47 -27
- package/src/plugins/defaults/memory/v3/learned-edges.ts +12 -14
- package/src/plugins/defaults/memory/v3/orchestrate.ts +139 -22
- package/src/plugins/defaults/memory/v3/prune.test.ts +10 -3
- package/src/plugins/defaults/memory/v3/prune.ts +17 -11
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +23 -11
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +70 -53
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +9 -6
- package/src/plugins/external-plugin-loader.ts +15 -15
- package/src/plugins/mtime-cache.ts +47 -0
- package/src/plugins/pipeline.ts +9 -1
- package/src/plugins/plugin-execution-context.ts +44 -0
- package/src/plugins/plugin-tree-walk.ts +31 -24
- package/src/plugins/source-fingerprint.ts +3 -4
- package/src/prompts/normalize-onboarding.ts +12 -0
- package/src/prompts/persona-resolver.ts +8 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +1 -1
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +6 -9
- package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
- package/src/providers/__tests__/retry-callsite.test.ts +235 -215
- package/src/providers/__tests__/satellite-connection-routing.test.ts +8 -16
- package/src/providers/atlascloud/client.ts +10 -49
- package/src/providers/baseten/client.ts +43 -0
- package/src/providers/call-site-routing.ts +26 -13
- package/src/providers/connection-resolution.ts +9 -11
- package/src/providers/fetch-provider-catalog.ts +4 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +29 -4
- package/src/providers/inference/__tests__/connection-availability-keyless.test.ts +78 -0
- package/src/providers/inference/adapter-factory.ts +27 -6
- package/src/providers/inference/auth.ts +29 -1
- package/src/providers/inference/backfill.ts +61 -10
- package/src/providers/inference/connection-availability.ts +3 -2
- package/src/providers/inference/resolve-auth.ts +9 -1
- package/src/providers/model-catalog.ts +37 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
- package/src/providers/openai/api-key-validation.ts +70 -0
- package/src/providers/retry.ts +2 -0
- package/src/providers/types.ts +11 -4
- package/src/providers/vellum-model-routing.test.ts +26 -0
- package/src/providers/vellum-model-routing.ts +30 -0
- package/src/providers/voice-error-copy.ts +47 -0
- package/src/runtime/agent-wake.ts +40 -24
- package/src/runtime/for-chat-mint-registry.ts +118 -0
- package/src/runtime/reveal-nonce.ts +49 -0
- package/src/runtime/reveal-success-registry.ts +306 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +88 -3
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -30
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +7 -0
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -0
- package/src/runtime/routes/app-management-routes.ts +152 -45
- package/src/runtime/routes/app-routes.ts +16 -77
- package/src/runtime/routes/canned-message-complete.ts +16 -16
- package/src/runtime/routes/conversation-management-routes.ts +17 -14
- package/src/runtime/routes/conversation-query-routes.ts +54 -6
- package/src/runtime/routes/conversation-routes.ts +48 -2
- package/src/runtime/routes/credential-routes.ts +116 -3
- package/src/runtime/routes/email-routes.ts +17 -1
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +33 -5
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +6 -5
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -28
- package/src/runtime/routes/inference-send-routes.ts +1 -1
- package/src/runtime/routes/internal-telemetry-routes.ts +7 -6
- package/src/runtime/routes/plugins-routes.ts +37 -6
- package/src/runtime/routes/publish-routes.ts +15 -18
- package/src/runtime/routes/schedule-worker-routes.ts +9 -0
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/telemetry-routes.ts +149 -45
- package/src/schedule/__tests__/schedule-timezone.test.ts +101 -0
- package/src/schedule/__tests__/worker-watchdog.test.ts +209 -0
- package/src/schedule/schedule-store.ts +12 -1
- package/src/schedule/schedule-timezone.ts +63 -0
- package/src/schedule/scheduler.ts +100 -6
- package/src/schedule/worker-control.ts +19 -0
- package/src/security/auth-fallback-events-store.ts +7 -6
- package/src/security/secret-scanner.ts +26 -1
- package/src/services/published-app-updater.ts +6 -11
- package/src/stt/stt-stream-session.ts +36 -16
- package/src/subagent/manager.ts +34 -8
- package/src/telemetry/AGENTS.md +30 -1
- package/src/telemetry/__tests__/config-setting-snapshot.test.ts +18 -0
- package/src/telemetry/__tests__/outbox-test-harness.ts +5 -3
- package/src/telemetry/config-setting-snapshot.ts +44 -10
- package/src/telemetry/telemetry-event-sources.test.ts +124 -30
- package/src/telemetry/telemetry-event-sources.ts +137 -83
- package/src/telemetry/telemetry-events-outbox.test.ts +46 -1
- package/src/telemetry/telemetry-events-outbox.ts +60 -9
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire-validation.ts +39 -2
- package/src/telemetry/telemetry-wire.generated.ts +8 -0
- package/src/telemetry/tool-audit.ts +15 -9
- package/src/telemetry/tool-executed-events-store.test.ts +1 -1
- package/src/telemetry/turn-events-store.ts +20 -0
- package/src/telemetry/types.ts +45 -12
- package/src/telemetry/usage-telemetry-reporter.test.ts +294 -25
- package/src/telemetry/usage-telemetry-reporter.ts +117 -18
- package/src/telemetry/watchdog-direct-emit.test.ts +11 -3
- package/src/telemetry/watchdog-direct-emit.ts +11 -6
- package/src/tools/apps/executors.ts +11 -36
- package/src/tools/executor.ts +14 -1
- package/src/tools/host-terminal/host-shell.ts +6 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +1 -1
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +25 -0
- package/src/tools/network/web-fetch.ts +6 -2
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/subagent/spawn.ts +7 -11
- package/src/tools/terminal/shell.ts +5 -2
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/tool-profiler.ts +37 -6
- package/src/tools/ui-surface/channel-variants.ts +101 -0
- package/src/tools/ui-surface/definitions.ts +23 -67
- package/src/tools/ui-surface/surface-shape-docs.ts +229 -0
- package/src/tts/__tests__/provider-adapters.test.ts +18 -0
- package/src/tts/provider-catalog.ts +2 -4
- package/src/tts/providers/deepgram-provider.ts +10 -1
- package/src/types/onboarding-context.ts +8 -0
- package/src/usage/attribution.ts +18 -112
- package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
- package/src/util/provider-error-patterns.ts +7 -1
- package/src/util/worker-process.ts +105 -0
- package/src/watcher/__tests__/telemetry.test.ts +17 -4
- package/src/watcher/telemetry.ts +5 -5
- package/src/workspace/migrations/131-drop-web-fetch-mode.ts +61 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/workspace/provider-commit-message-generator.ts +6 -4
- package/src/__tests__/app-open-proxy.test.ts +0 -67
- package/src/onboarding/onboarding-research-events-store.test.ts +0 -230
- package/src/onboarding/onboarding-research-events-store.ts +0 -104
- package/src/runtime/routes/assets/vellum-design-system.css +0 -2236
- package/src/tools/apps/definitions.ts +0 -73
- package/src/tools/apps/open-proxy.ts +0 -43
|
@@ -60,6 +60,24 @@ describe("config-setting-snapshot", () => {
|
|
|
60
60
|
expect(recordedPairs()).toEqual([["memory.v2.enabled", "false"]]);
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
test("unknown consent skips entirely — no rows, no memo", () => {
|
|
64
|
+
setShareAnalytics("unknown");
|
|
65
|
+
recordConfigSettingSnapshot(makeConfig(true, false));
|
|
66
|
+
|
|
67
|
+
// A snapshot is re-derivable state (unlike outbox events), so nothing is
|
|
68
|
+
// recorded while consent is unresolved...
|
|
69
|
+
expect(recordedPairs()).toHaveLength(0);
|
|
70
|
+
|
|
71
|
+
// ...and nothing was memoized: once consent resolves, the next snapshot
|
|
72
|
+
// records the complete current set.
|
|
73
|
+
setShareAnalytics(true);
|
|
74
|
+
recordConfigSettingSnapshot(makeConfig(true, false));
|
|
75
|
+
expect(recordedPairs().sort()).toEqual([
|
|
76
|
+
["memory.enabled", "true"],
|
|
77
|
+
["memory.v2.enabled", "false"],
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
|
|
63
81
|
test("consent off drops the snapshot without poisoning the memo", () => {
|
|
64
82
|
setShareAnalytics(false);
|
|
65
83
|
recordConfigSettingSnapshot(makeConfig(true, true));
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { mock, spyOn } from "bun:test";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import type { ConsentState } from "../../platform/consent-cache.js";
|
|
4
|
+
|
|
5
|
+
let shareAnalytics: ConsentState = true;
|
|
4
6
|
// Owner's `share_diagnostics` consent + accepted version — a second,
|
|
5
7
|
// independent gate some outbox event types layer on top of
|
|
6
8
|
// `share_analytics` (their payload carries content richer than metadata).
|
|
@@ -17,7 +19,7 @@ let shareDiagnosticsVersion = "2999-01-01";
|
|
|
17
19
|
// imports are as safe as in the test files themselves (AGENTS.md "Test
|
|
18
20
|
// machinery isolation" scopes its no-src/ rule to preload-time machinery).
|
|
19
21
|
mock.module("../../platform/consent-cache.js", () => ({
|
|
20
|
-
|
|
22
|
+
getRawShareAnalytics: () => shareAnalytics,
|
|
21
23
|
getCachedShareDiagnostics: () => shareDiagnostics,
|
|
22
24
|
getCachedShareDiagnosticsVersion: () => shareDiagnosticsVersion,
|
|
23
25
|
}));
|
|
@@ -31,7 +33,7 @@ import { queryTelemetryOutboxBatch } from "../telemetry-events-outbox.js";
|
|
|
31
33
|
await initializeDb();
|
|
32
34
|
|
|
33
35
|
/** Flip the mocked share_analytics consent; call with true in beforeEach. */
|
|
34
|
-
export function setShareAnalytics(value:
|
|
36
|
+
export function setShareAnalytics(value: ConsentState): void {
|
|
35
37
|
shareAnalytics = value;
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getConfigReadOnly } from "../config/loader.js";
|
|
2
2
|
import type { AssistantConfig } from "../config/schema.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getRawShareAnalytics } from "../platform/consent-cache.js";
|
|
4
4
|
import { getLogger } from "../util/logger.js";
|
|
5
5
|
import { recordConfigSettingEvent } from "./config-setting-events-store.js";
|
|
6
6
|
|
|
@@ -51,13 +51,18 @@ const lastRecorded = new Map<string, string>();
|
|
|
51
51
|
* Record a `config_setting` event for every tracked setting whose effective
|
|
52
52
|
* value differs from what this process last recorded.
|
|
53
53
|
*
|
|
54
|
-
* Consent-gated with a memo reset on opt-out. While `share_analytics` is
|
|
55
|
-
* nothing is recorded and the memo is cleared: the
|
|
56
|
-
* discards any config_setting rows still pending
|
|
57
|
-
* before the opt-out no longer corresponds to a
|
|
58
|
-
* means a later re-opt-in (in the same process,
|
|
59
|
-
* the full current snapshot rather than skipping
|
|
60
|
-
* that expect a complete snapshot after re-opt-in
|
|
54
|
+
* Consent-gated with a memo reset on opt-out. While `share_analytics` is a
|
|
55
|
+
* confirmed opt-out, nothing is recorded and the memo is cleared: the
|
|
56
|
+
* reporter's opt-out flush discards any config_setting rows still pending
|
|
57
|
+
* upload, so a memo entry from before the opt-out no longer corresponds to a
|
|
58
|
+
* delivered row. Clearing it means a later re-opt-in (in the same process,
|
|
59
|
+
* unchanged config) re-records the full current snapshot rather than skipping
|
|
60
|
+
* memoized keys — consumers that expect a complete snapshot after re-opt-in
|
|
61
|
+
* stay correct. An UNKNOWN consent state skips entirely (no record, no memo,
|
|
62
|
+
* no clear): unlike outbox events, a snapshot is re-derivable state, so
|
|
63
|
+
* deferring it loses nothing — while recording it would either memoize a row
|
|
64
|
+
* a later opt-out purge invalidates, or (without the memo) double-record on
|
|
65
|
+
* every boot.
|
|
61
66
|
*
|
|
62
67
|
* Retry-friendly when opted in: `recordConfigSettingEvent` returns false when
|
|
63
68
|
* the telemetry DB is momentarily unavailable, and the memo is only advanced
|
|
@@ -67,7 +72,11 @@ const lastRecorded = new Map<string, string>();
|
|
|
67
72
|
* retries on the next invocation.
|
|
68
73
|
*/
|
|
69
74
|
export function recordConfigSettingSnapshot(config: AssistantConfig): void {
|
|
70
|
-
|
|
75
|
+
const consent = getRawShareAnalytics();
|
|
76
|
+
if (consent === "unknown") {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (consent === false) {
|
|
71
80
|
lastRecorded.clear();
|
|
72
81
|
return;
|
|
73
82
|
}
|
|
@@ -89,6 +98,14 @@ export function recordConfigSettingSnapshot(config: AssistantConfig): void {
|
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
let snapshotTimer: ReturnType<typeof setInterval> | null = null;
|
|
101
|
+
let bootRetryTimer: ReturnType<typeof setTimeout> | null = null;
|
|
102
|
+
|
|
103
|
+
// The boot-time snapshot races the first consent refresh (fire-and-forget in
|
|
104
|
+
// the monitor's startup), so consent is usually still "unknown" on the first
|
|
105
|
+
// call and the snapshot is skipped. Retry on a short timer until consent
|
|
106
|
+
// resolves rather than waiting a full hourly interval for the per-boot
|
|
107
|
+
// assertion.
|
|
108
|
+
const BOOT_RETRY_INTERVAL_MS = 60_000;
|
|
92
109
|
|
|
93
110
|
function recordSnapshotFromConfig(): void {
|
|
94
111
|
try {
|
|
@@ -100,6 +117,19 @@ function recordSnapshotFromConfig(): void {
|
|
|
100
117
|
}
|
|
101
118
|
}
|
|
102
119
|
|
|
120
|
+
function recordBootSnapshotOnceConsentKnown(): void {
|
|
121
|
+
if (getRawShareAnalytics() === "unknown") {
|
|
122
|
+
bootRetryTimer = setTimeout(
|
|
123
|
+
recordBootSnapshotOnceConsentKnown,
|
|
124
|
+
BOOT_RETRY_INTERVAL_MS,
|
|
125
|
+
);
|
|
126
|
+
bootRetryTimer.unref?.();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
bootRetryTimer = null;
|
|
130
|
+
recordSnapshotFromConfig();
|
|
131
|
+
}
|
|
132
|
+
|
|
103
133
|
/**
|
|
104
134
|
* Start the config-setting snapshot loop in the resource monitor process:
|
|
105
135
|
* record the tracked settings once at boot, then hourly. No-op in dev mode
|
|
@@ -114,7 +144,7 @@ export function startConfigSnapshotReporter(): void {
|
|
|
114
144
|
if (snapshotTimer) {
|
|
115
145
|
return;
|
|
116
146
|
}
|
|
117
|
-
|
|
147
|
+
recordBootSnapshotOnceConsentKnown();
|
|
118
148
|
snapshotTimer = setInterval(
|
|
119
149
|
recordSnapshotFromConfig,
|
|
120
150
|
CONFIG_SNAPSHOT_INTERVAL_MS,
|
|
@@ -127,6 +157,10 @@ export function stopConfigSnapshotReporter(): void {
|
|
|
127
157
|
clearInterval(snapshotTimer);
|
|
128
158
|
snapshotTimer = null;
|
|
129
159
|
}
|
|
160
|
+
if (bootRetryTimer) {
|
|
161
|
+
clearTimeout(bootRetryTimer);
|
|
162
|
+
bootRetryTimer = null;
|
|
163
|
+
}
|
|
130
164
|
}
|
|
131
165
|
|
|
132
166
|
/** Test-only: clear the per-process last-recorded memo. */
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { beforeEach, describe, expect, test } from "bun:test";
|
|
8
8
|
|
|
9
|
-
import { recordOnboardingResearchEvent } from "../onboarding/onboarding-research-events-store.js";
|
|
10
9
|
import {
|
|
10
|
+
pendingOutboxRows,
|
|
11
11
|
resetOutboxTable,
|
|
12
12
|
setShareAnalytics,
|
|
13
13
|
setShareDiagnostics,
|
|
@@ -16,25 +16,72 @@ import {
|
|
|
16
16
|
ALL_TELEMETRY_EVENT_SOURCES,
|
|
17
17
|
DAEMON_TELEMETRY_EVENT_SOURCES,
|
|
18
18
|
MONITOR_TELEMETRY_EVENT_SOURCES,
|
|
19
|
+
ORPHAN_OUTBOX_DRAIN_SOURCE_ID,
|
|
20
|
+
orphanOutboxDrainSource,
|
|
19
21
|
} from "./telemetry-event-sources.js";
|
|
20
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
insertTelemetryOutboxEvents,
|
|
24
|
+
recordTelemetryEvent,
|
|
25
|
+
} from "./telemetry-events-outbox.js";
|
|
26
|
+
import { telemetryEventSchema } from "./telemetry-wire.generated.js";
|
|
27
|
+
import type { TelemetryEvent } from "./types.js";
|
|
28
|
+
import {
|
|
29
|
+
OUTBOX_TELEMETRY_EVENT_NAMES,
|
|
30
|
+
WATERMARK_TELEMETRY_EVENT_NAMES,
|
|
31
|
+
} from "./types.js";
|
|
21
32
|
|
|
22
33
|
describe("telemetry event source partition", () => {
|
|
23
34
|
test("the full source list carries every event type in payload order", () => {
|
|
35
|
+
// Watermark sources first (their ids are the table names, not the wire
|
|
36
|
+
// discriminants), then outbox events in wire-contract order, then the
|
|
37
|
+
// synthetic orphan-drain source last.
|
|
24
38
|
expect(ALL_TELEMETRY_EVENT_SOURCES.map((s) => s.id)).toEqual([
|
|
25
39
|
"usage",
|
|
26
40
|
"turns",
|
|
41
|
+
"tool_executed",
|
|
27
42
|
"lifecycle",
|
|
28
43
|
"onboarding",
|
|
29
44
|
"auth_fallback",
|
|
30
|
-
"tool_executed",
|
|
31
45
|
"skill_loaded",
|
|
32
46
|
"watchdog",
|
|
33
47
|
"config_setting",
|
|
34
48
|
"onboarding_research",
|
|
49
|
+
ORPHAN_OUTBOX_DRAIN_SOURCE_ID,
|
|
35
50
|
]);
|
|
36
51
|
});
|
|
37
52
|
|
|
53
|
+
test("every wire event type is partitioned to exactly one flush lane", () => {
|
|
54
|
+
// The derivation's core guarantee: a new wire event type can't silently go
|
|
55
|
+
// unflushed. Every discriminant in the generated contract is either
|
|
56
|
+
// watermark-flushed or (by default) outbox-backed, never both, never
|
|
57
|
+
// neither — and, ignoring the synthetic orphan-drain source, the counts
|
|
58
|
+
// line up with one source per type.
|
|
59
|
+
const wireTypes = new Set(
|
|
60
|
+
telemetryEventSchema.options.map((o) => o.shape.type.value),
|
|
61
|
+
);
|
|
62
|
+
const partitioned = [
|
|
63
|
+
...WATERMARK_TELEMETRY_EVENT_NAMES,
|
|
64
|
+
...OUTBOX_TELEMETRY_EVENT_NAMES,
|
|
65
|
+
];
|
|
66
|
+
expect(new Set(partitioned)).toEqual(wireTypes);
|
|
67
|
+
expect(partitioned.length).toBe(wireTypes.size); // disjoint: no double-count
|
|
68
|
+
const perTypeSources = ALL_TELEMETRY_EVENT_SOURCES.filter(
|
|
69
|
+
(s) => s.id !== ORPHAN_OUTBOX_DRAIN_SOURCE_ID,
|
|
70
|
+
);
|
|
71
|
+
expect(perTypeSources.length).toBe(wireTypes.size);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("every watermark name is a real wire discriminant", () => {
|
|
75
|
+
// Guards a stale/typo watermark name that would wrongly exclude a live type
|
|
76
|
+
// from the outbox (and thus from any flush source).
|
|
77
|
+
const wireTypes = new Set(
|
|
78
|
+
telemetryEventSchema.options.map((o) => o.shape.type.value),
|
|
79
|
+
);
|
|
80
|
+
for (const name of WATERMARK_TELEMETRY_EVENT_NAMES) {
|
|
81
|
+
expect(wireTypes.has(name)).toBe(true);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
38
85
|
test("the daemon flushes turns only", () => {
|
|
39
86
|
expect(DAEMON_TELEMETRY_EVENT_SOURCES.map((s) => s.id)).toEqual(["turns"]);
|
|
40
87
|
});
|
|
@@ -68,7 +115,7 @@ describe("telemetry event source partition", () => {
|
|
|
68
115
|
});
|
|
69
116
|
});
|
|
70
117
|
|
|
71
|
-
describe("onboarding_research source:
|
|
118
|
+
describe("onboarding_research source: flushes via the default outbox source", () => {
|
|
72
119
|
beforeEach(() => {
|
|
73
120
|
setShareAnalytics(true);
|
|
74
121
|
setShareDiagnostics(true);
|
|
@@ -86,43 +133,90 @@ describe("onboarding_research source: flush-time diagnostics gate", () => {
|
|
|
86
133
|
}
|
|
87
134
|
|
|
88
135
|
function recordSample(): void {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
136
|
+
recordTelemetryEvent(
|
|
137
|
+
"onboarding_research",
|
|
138
|
+
{
|
|
139
|
+
conversation_id: "conv-x",
|
|
140
|
+
status: "done",
|
|
141
|
+
claims: [],
|
|
142
|
+
claim_count: 0,
|
|
143
|
+
claims_confident: 0,
|
|
144
|
+
claims_maybe: 0,
|
|
145
|
+
claims_guessing: 0,
|
|
146
|
+
suggestions: [],
|
|
147
|
+
suggestion_count: 0,
|
|
148
|
+
plugins: [],
|
|
149
|
+
installed_plugins: [],
|
|
150
|
+
},
|
|
151
|
+
{ conversationId: "conv-x" },
|
|
152
|
+
);
|
|
97
153
|
}
|
|
98
154
|
|
|
99
|
-
test("ships
|
|
155
|
+
test("ships pending rows regardless of diagnostics consent, like every other outbox event", () => {
|
|
156
|
+
// No flush-time diagnostics gate: the daemon rides `share_analytics`
|
|
157
|
+
// only and leaves the diagnostics decision to the platform's
|
|
158
|
+
// authoritative server-side ingest gate. Diagnostics off (and even a
|
|
159
|
+
// stale accepted version) must not hold the row back.
|
|
160
|
+
setShareDiagnostics(false, "2000-01-01");
|
|
100
161
|
recordSample();
|
|
162
|
+
|
|
101
163
|
const batch = onboardingResearchSource().collect(0, undefined, 100);
|
|
102
164
|
expect(batch.events).toHaveLength(1);
|
|
103
165
|
});
|
|
166
|
+
});
|
|
104
167
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
168
|
+
describe("orphan-drain source", () => {
|
|
169
|
+
beforeEach(() => {
|
|
170
|
+
resetOutboxTable();
|
|
171
|
+
setShareAnalytics(true);
|
|
172
|
+
});
|
|
110
173
|
|
|
111
|
-
|
|
112
|
-
|
|
174
|
+
function insertOutboxRow(name: string, id: string): void {
|
|
175
|
+
insertTelemetryOutboxEvents([
|
|
176
|
+
{
|
|
177
|
+
id,
|
|
178
|
+
name,
|
|
179
|
+
createdAt: 1,
|
|
180
|
+
event: {
|
|
181
|
+
type: name,
|
|
182
|
+
daemon_event_id: id,
|
|
183
|
+
recorded_at: 1,
|
|
184
|
+
assistant_version: "1.2.3",
|
|
185
|
+
} as unknown as TelemetryEvent,
|
|
186
|
+
},
|
|
187
|
+
]);
|
|
188
|
+
}
|
|
113
189
|
|
|
114
|
-
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
});
|
|
190
|
+
test("drains rows for a type no longer in the wire contract, then self-heals on ack", () => {
|
|
191
|
+
// A row whose name isn't a current outbox event type — e.g. recorded before
|
|
192
|
+
// the platform removed the type. No per-type source queries it.
|
|
193
|
+
const orphanName = "removed_legacy_event";
|
|
194
|
+
insertOutboxRow(orphanName, "orphan-row-1");
|
|
120
195
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
196
|
+
const source = orphanOutboxDrainSource();
|
|
197
|
+
const batch = source.collect(0, undefined, 500);
|
|
198
|
+
expect(batch.events).toHaveLength(1);
|
|
199
|
+
expect((batch.events[0] as { type: string }).type).toBe(orphanName);
|
|
200
|
+
expect(batch.rowIds).toEqual(["orphan-row-1"]);
|
|
124
201
|
|
|
125
|
-
|
|
202
|
+
// Send-then-ack: the reporter deletes the row after a 2xx, so it self-heals.
|
|
203
|
+
source.ack?.acknowledge(batch.rowIds ?? []);
|
|
204
|
+
expect(pendingOutboxRows(orphanName)).toHaveLength(0);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("ignores rows for a current outbox event type", () => {
|
|
208
|
+
// Known types drain through their own per-type source, not this one.
|
|
209
|
+
const knownName = OUTBOX_TELEMETRY_EVENT_NAMES[0];
|
|
210
|
+
insertOutboxRow(knownName, "known-row-1");
|
|
211
|
+
const batch = orphanOutboxDrainSource().collect(0, undefined, 500);
|
|
126
212
|
expect(batch.events).toHaveLength(0);
|
|
127
213
|
});
|
|
214
|
+
|
|
215
|
+
test("is registered in the monitor partition", () => {
|
|
216
|
+
expect(
|
|
217
|
+
MONITOR_TELEMETRY_EVENT_SOURCES.some(
|
|
218
|
+
(s) => s.id === ORPHAN_OUTBOX_DRAIN_SOURCE_ID,
|
|
219
|
+
),
|
|
220
|
+
).toBe(true);
|
|
221
|
+
});
|
|
128
222
|
});
|
|
@@ -20,6 +20,7 @@ import { APP_VERSION } from "../version.js";
|
|
|
20
20
|
import {
|
|
21
21
|
deleteTelemetryOutboxEvents,
|
|
22
22
|
discardPendingTelemetryOutboxEvents,
|
|
23
|
+
queryDistinctOutboxEventNames,
|
|
23
24
|
queryTelemetryOutboxBatch,
|
|
24
25
|
} from "./telemetry-events-outbox.js";
|
|
25
26
|
import { queryUnreportedToolExecutedEvents } from "./tool-executed-events-store.js";
|
|
@@ -31,6 +32,7 @@ import type {
|
|
|
31
32
|
TelemetryEvent,
|
|
32
33
|
TurnTelemetryClientInfo,
|
|
33
34
|
} from "./types.js";
|
|
35
|
+
import { OUTBOX_TELEMETRY_EVENT_NAMES } from "./types.js";
|
|
34
36
|
|
|
35
37
|
const log = getLogger("usage-telemetry");
|
|
36
38
|
|
|
@@ -130,13 +132,48 @@ function simpleSource<Row extends { id: string; createdAt: number }>(
|
|
|
130
132
|
};
|
|
131
133
|
}
|
|
132
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Query one outbox event name and parse each stored payload into its wire
|
|
137
|
+
* event, accumulating into `events`/`rowIds`. A row whose payload does not
|
|
138
|
+
* parse to an object is collected into `corruptIds` for immediate purge: an
|
|
139
|
+
* early empty-batch return in the reporter would otherwise strand it at the
|
|
140
|
+
* head of the queue forever. Shared by {@link outboxSource} and the
|
|
141
|
+
* orphan-drain source.
|
|
142
|
+
*/
|
|
143
|
+
function collectOutboxName(
|
|
144
|
+
name: string,
|
|
145
|
+
limit: number,
|
|
146
|
+
into: { events: TelemetryEvent[]; rowIds: string[]; corruptIds: string[] },
|
|
147
|
+
): { queried: number } {
|
|
148
|
+
const rows = queryTelemetryOutboxBatch(name, limit);
|
|
149
|
+
for (const row of rows) {
|
|
150
|
+
let event: TelemetryEvent | null = null;
|
|
151
|
+
try {
|
|
152
|
+
const parsed = JSON.parse(row.payload) as unknown;
|
|
153
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
154
|
+
event = parsed as TelemetryEvent;
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
// Fall through to the purge below.
|
|
158
|
+
}
|
|
159
|
+
if (event) {
|
|
160
|
+
into.events.push(event);
|
|
161
|
+
into.rowIds.push(row.id);
|
|
162
|
+
} else {
|
|
163
|
+
into.corruptIds.push(row.id);
|
|
164
|
+
log.warn(
|
|
165
|
+
{ name, rowId: row.id, payloadLength: row.payload.length },
|
|
166
|
+
"Telemetry outbox: unparseable payload — purging row",
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { queried: rows.length };
|
|
171
|
+
}
|
|
172
|
+
|
|
133
173
|
/**
|
|
134
174
|
* Build an ack-mode source over the generic `telemetry_events` outbox for one
|
|
135
175
|
* event name. `collect` ignores the cursor args — acknowledged rows are
|
|
136
|
-
* deleted, so the head of the queue is always the next batch
|
|
137
|
-
* each stored payload into its wire event. A row whose payload does not parse
|
|
138
|
-
* to an object is purged immediately with a warn: an early empty-batch return
|
|
139
|
-
* in the reporter would otherwise strand it at the head of the queue forever.
|
|
176
|
+
* deleted, so the head of the queue is always the next batch.
|
|
140
177
|
*/
|
|
141
178
|
export function outboxSource(
|
|
142
179
|
name: OutboxTelemetryEventName,
|
|
@@ -144,39 +181,20 @@ export function outboxSource(
|
|
|
144
181
|
return {
|
|
145
182
|
id: name,
|
|
146
183
|
collect(_afterCreatedAt, _afterId, limit) {
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
156
|
-
event = parsed as TelemetryEvent;
|
|
157
|
-
}
|
|
158
|
-
} catch {
|
|
159
|
-
// Fall through to the purge below.
|
|
160
|
-
}
|
|
161
|
-
if (event) {
|
|
162
|
-
events.push(event);
|
|
163
|
-
rowIds.push(row.id);
|
|
164
|
-
} else {
|
|
165
|
-
corruptIds.push(row.id);
|
|
166
|
-
log.warn(
|
|
167
|
-
{ name, rowId: row.id, payloadLength: row.payload.length },
|
|
168
|
-
"Telemetry outbox: unparseable payload — purging row",
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
if (corruptIds.length > 0) {
|
|
173
|
-
deleteTelemetryOutboxEvents(corruptIds);
|
|
184
|
+
const acc = {
|
|
185
|
+
events: [] as TelemetryEvent[],
|
|
186
|
+
rowIds: [] as string[],
|
|
187
|
+
corruptIds: [] as string[],
|
|
188
|
+
};
|
|
189
|
+
const { queried } = collectOutboxName(name, limit, acc);
|
|
190
|
+
if (acc.corruptIds.length > 0) {
|
|
191
|
+
deleteTelemetryOutboxEvents(acc.corruptIds);
|
|
174
192
|
}
|
|
175
193
|
return {
|
|
176
|
-
events,
|
|
177
|
-
rowIds,
|
|
194
|
+
events: acc.events,
|
|
195
|
+
rowIds: acc.rowIds,
|
|
178
196
|
lastCursor: null,
|
|
179
|
-
fullBatch:
|
|
197
|
+
fullBatch: queried === limit,
|
|
180
198
|
};
|
|
181
199
|
},
|
|
182
200
|
ack: {
|
|
@@ -186,44 +204,60 @@ export function outboxSource(
|
|
|
186
204
|
};
|
|
187
205
|
}
|
|
188
206
|
|
|
207
|
+
/** Source id of the synthetic orphan-drain source (not a wire event type). */
|
|
208
|
+
export const ORPHAN_OUTBOX_DRAIN_SOURCE_ID = "__orphan_outbox_drain";
|
|
209
|
+
|
|
189
210
|
/**
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
211
|
+
* Synthetic ack-mode source that drains outbox rows whose `name` is no longer a
|
|
212
|
+
* current outbox event type — e.g. rows recorded before the platform removed or
|
|
213
|
+
* renamed a type, still pending on a client that has since upgraded. The
|
|
214
|
+
* derived per-type sources ({@link OUTBOX_TELEMETRY_EVENT_NAMES}) never query
|
|
215
|
+
* those names, so without this the rows would sit in the outbox forever.
|
|
216
|
+
*
|
|
217
|
+
* Draining is send-then-ack: the platform skips an unknown event type but still
|
|
218
|
+
* returns 2xx, after which the row is deleted. This is safe against a stale
|
|
219
|
+
* local wire copy — a name merely missing from a lagging wire still ships and
|
|
220
|
+
* lands rather than being purged and lost — and rides `share_analytics` like
|
|
221
|
+
* every other outbox event (no outbox type is diagnostics-gated at flush).
|
|
201
222
|
*/
|
|
202
|
-
function
|
|
203
|
-
|
|
204
|
-
):
|
|
205
|
-
|
|
223
|
+
export function orphanOutboxDrainSource(): TelemetryEventSource {
|
|
224
|
+
const known = new Set<string>(OUTBOX_TELEMETRY_EVENT_NAMES);
|
|
225
|
+
const orphanNames = (): string[] =>
|
|
226
|
+
queryDistinctOutboxEventNames().filter((name) => !known.has(name));
|
|
206
227
|
return {
|
|
207
|
-
id:
|
|
208
|
-
collect(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
228
|
+
id: ORPHAN_OUTBOX_DRAIN_SOURCE_ID,
|
|
229
|
+
collect(_afterCreatedAt, _afterId, limit) {
|
|
230
|
+
const acc = {
|
|
231
|
+
events: [] as TelemetryEvent[],
|
|
232
|
+
rowIds: [] as string[],
|
|
233
|
+
corruptIds: [] as string[],
|
|
234
|
+
};
|
|
235
|
+
for (const name of orphanNames()) {
|
|
236
|
+
if (acc.events.length >= limit) {
|
|
237
|
+
break;
|
|
216
238
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
fullBatch: rows.length === limit,
|
|
222
|
-
};
|
|
239
|
+
collectOutboxName(name, limit - acc.events.length, acc);
|
|
240
|
+
}
|
|
241
|
+
if (acc.corruptIds.length > 0) {
|
|
242
|
+
deleteTelemetryOutboxEvents(acc.corruptIds);
|
|
223
243
|
}
|
|
224
|
-
return
|
|
244
|
+
return {
|
|
245
|
+
events: acc.events,
|
|
246
|
+
rowIds: acc.rowIds,
|
|
247
|
+
lastCursor: null,
|
|
248
|
+
// Conservative: never signals more-behind, so orphan draining spreads
|
|
249
|
+
// across flush cycles instead of monopolizing one. Rare by nature.
|
|
250
|
+
fullBatch: false,
|
|
251
|
+
};
|
|
252
|
+
},
|
|
253
|
+
ack: {
|
|
254
|
+
acknowledge: (rowIds) => deleteTelemetryOutboxEvents(rowIds),
|
|
255
|
+
discardPending: () => {
|
|
256
|
+
for (const name of orphanNames()) {
|
|
257
|
+
discardPendingTelemetryOutboxEvents(name);
|
|
258
|
+
}
|
|
259
|
+
},
|
|
225
260
|
},
|
|
226
|
-
ack: base.ack,
|
|
227
261
|
};
|
|
228
262
|
}
|
|
229
263
|
|
|
@@ -480,27 +514,47 @@ const toolExecutedSource = simpleSource(
|
|
|
480
514
|
export const TOOL_EXECUTED_SOURCE_ID = toolExecutedSource.id;
|
|
481
515
|
|
|
482
516
|
/**
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
* production reporters run the daemon/monitor partitions below.
|
|
517
|
+
* Watermark-flushed sources — the high-volume events on their own SQLite
|
|
518
|
+
* tables (`WATERMARK_TELEMETRY_EVENT_NAMES`), read forward by a `(createdAt,
|
|
519
|
+
* id)` cursor rather than the generic outbox.
|
|
487
520
|
*/
|
|
488
|
-
|
|
521
|
+
const WATERMARK_TELEMETRY_EVENT_SOURCES: readonly TelemetryEventSource[] = [
|
|
489
522
|
usageSource,
|
|
490
523
|
turnSource,
|
|
491
|
-
outboxSource("lifecycle"),
|
|
492
|
-
outboxSource("onboarding"),
|
|
493
|
-
outboxSource("auth_fallback"),
|
|
494
524
|
toolExecutedSource,
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
525
|
+
];
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Per-outbox-event flush-source override. The default for every outbox event is
|
|
529
|
+
* the plain {@link outboxSource}; list only the exceptions here. A new outbox
|
|
530
|
+
* event type therefore needs no edit — it flushes through `outboxSource`
|
|
531
|
+
* automatically. There are currently no exceptions: every outbox event,
|
|
532
|
+
* including `onboarding_research`, flushes through the default source and is
|
|
533
|
+
* gated only by the `share_analytics` consent enforced at record time.
|
|
534
|
+
*/
|
|
535
|
+
const OUTBOX_SOURCE_FACTORY: Partial<
|
|
536
|
+
Record<
|
|
537
|
+
OutboxTelemetryEventName,
|
|
538
|
+
(name: OutboxTelemetryEventName) => TelemetryEventSource
|
|
539
|
+
>
|
|
540
|
+
> = {};
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Every telemetry event source, in payload order (watermark sources first, then
|
|
544
|
+
* outbox events in wire-contract order). Derived from the wire contract via
|
|
545
|
+
* {@link OUTBOX_TELEMETRY_EVENT_NAMES}, so a new event type gets a flush source
|
|
546
|
+
* with no edit here. Intra-batch ordering is not load-bearing downstream
|
|
547
|
+
* (events are typed and deduped on `daemon_event_id`, not positional). This is
|
|
548
|
+
* the test/reference list; production reporters run the daemon/monitor
|
|
549
|
+
* partitions below.
|
|
550
|
+
*/
|
|
551
|
+
export const ALL_TELEMETRY_EVENT_SOURCES: readonly TelemetryEventSource[] = [
|
|
552
|
+
...WATERMARK_TELEMETRY_EVENT_SOURCES,
|
|
553
|
+
...OUTBOX_TELEMETRY_EVENT_NAMES.map((name) =>
|
|
554
|
+
(OUTBOX_SOURCE_FACTORY[name] ?? outboxSource)(name),
|
|
555
|
+
),
|
|
556
|
+
// Not a wire event type — drains rows for types the wire no longer declares.
|
|
557
|
+
orphanOutboxDrainSource(),
|
|
504
558
|
];
|
|
505
559
|
|
|
506
560
|
/**
|