@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
|
@@ -16,7 +16,6 @@ import { z } from "zod";
|
|
|
16
16
|
import {
|
|
17
17
|
getApp,
|
|
18
18
|
getAppDirPath,
|
|
19
|
-
isMultifileApp,
|
|
20
19
|
resolveEffectiveAppHtml,
|
|
21
20
|
} from "../../apps/app-store.js";
|
|
22
21
|
import {
|
|
@@ -45,23 +44,21 @@ async function handlePublish({ pathParams }: RouteHandlerArgs) {
|
|
|
45
44
|
throw new NotFoundError(`App not found: ${appId}`);
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
// Compile
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
};
|
|
64
|
-
}
|
|
47
|
+
// Compile if needed (same pattern as handleOpenApp)
|
|
48
|
+
const appDir = getAppDirPath(appId);
|
|
49
|
+
const distIndex = join(appDir, "dist", "index.html");
|
|
50
|
+
if (!existsSync(distIndex)) {
|
|
51
|
+
const result = await compileApp(appDir);
|
|
52
|
+
if (!result.ok) {
|
|
53
|
+
log.warn(
|
|
54
|
+
{ appId, errors: result.errors },
|
|
55
|
+
"Auto-compile failed before publish",
|
|
56
|
+
);
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
errorCode: "compile_failed",
|
|
60
|
+
error: `App failed to compile: ${result.errors?.join("; ") ?? "unknown error"}`,
|
|
61
|
+
};
|
|
65
62
|
}
|
|
66
63
|
}
|
|
67
64
|
|
|
@@ -14,6 +14,7 @@ import { z } from "zod";
|
|
|
14
14
|
import {
|
|
15
15
|
probeScheduleWorker,
|
|
16
16
|
ScheduleWorkerSpawnError,
|
|
17
|
+
setScheduleWorkerAdministrativelyStopped,
|
|
17
18
|
spawnScheduleWorkerProcess,
|
|
18
19
|
stopScheduleWorkerProcess,
|
|
19
20
|
} from "../../schedule/worker-control.js";
|
|
@@ -59,6 +60,10 @@ async function startScheduleWorker() {
|
|
|
59
60
|
throw new InternalError(message);
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
// A successful start clears any prior operator stop so the watchdog resumes
|
|
64
|
+
// supervising the worker.
|
|
65
|
+
setScheduleWorkerAdministrativelyStopped(false);
|
|
66
|
+
|
|
62
67
|
return {
|
|
63
68
|
pid: result.pid,
|
|
64
69
|
alreadyRunning: result.alreadyRunning,
|
|
@@ -80,6 +85,10 @@ function stopScheduleWorker() {
|
|
|
80
85
|
throw new InternalError(message);
|
|
81
86
|
}
|
|
82
87
|
|
|
88
|
+
// Mark as administratively stopped so the liveness watchdog does not respawn
|
|
89
|
+
// the worker the operator just told it to stop (cleared by start / restart).
|
|
90
|
+
setScheduleWorkerAdministrativelyStopped(true);
|
|
91
|
+
|
|
83
92
|
return {
|
|
84
93
|
workerWasRunning: before.status === "running",
|
|
85
94
|
...(before.pid != null ? { pid: before.pid } : {}),
|
|
@@ -26,6 +26,7 @@ import { clearEmbeddingBackendCache } from "../../persistence/embeddings/embeddi
|
|
|
26
26
|
import { maybeReseedCapabilitiesAfterManagedCredential } from "../../plugins/defaults/memory/v2/memory-v2-startup.js";
|
|
27
27
|
import { validateAnthropicApiKey } from "../../providers/anthropic/client.js";
|
|
28
28
|
import { validateAtlasCloudApiKey } from "../../providers/atlascloud/client.js";
|
|
29
|
+
import { validateBasetenApiKey } from "../../providers/baseten/client.js";
|
|
29
30
|
import { validateGeminiApiKey } from "../../providers/gemini/client.js";
|
|
30
31
|
import { validateMinimaxApiKey } from "../../providers/minimax/client.js";
|
|
31
32
|
import { validateOpenAIApiKey } from "../../providers/openai/client.js";
|
|
@@ -216,6 +217,15 @@ async function handleAddSecret({ body }: RouteHandlerArgs) {
|
|
|
216
217
|
);
|
|
217
218
|
return { success: false, error: validation.reason };
|
|
218
219
|
}
|
|
220
|
+
} else if (name === "baseten") {
|
|
221
|
+
const validation = await validateBasetenApiKey(value);
|
|
222
|
+
if (!validation.valid) {
|
|
223
|
+
log.warn(
|
|
224
|
+
{ provider: name, reason: validation.reason },
|
|
225
|
+
"API key validation failed",
|
|
226
|
+
);
|
|
227
|
+
return { success: false, error: validation.reason };
|
|
228
|
+
}
|
|
219
229
|
}
|
|
220
230
|
|
|
221
231
|
const stored = await setSecureKeyAsync(
|
|
@@ -2,16 +2,26 @@
|
|
|
2
2
|
* Route handlers for telemetry lifecycle events.
|
|
3
3
|
*
|
|
4
4
|
* POST /v1/telemetry/lifecycle — record a lifecycle event (app_open, hatch).
|
|
5
|
-
* POST /v1/telemetry/
|
|
6
|
-
*
|
|
5
|
+
* POST /v1/telemetry/ingest — record any outbox-backed telemetry event by its
|
|
6
|
+
* wire `type` + `fields`.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
|
|
11
|
-
import { recordOnboardingResearchEvent } from "../../onboarding/onboarding-research-events-store.js";
|
|
12
11
|
import { recordLifecycleEvent } from "../../persistence/lifecycle-events-store.js";
|
|
12
|
+
import { recordTelemetryOutboxEvent } from "../../telemetry/telemetry-events-outbox.js";
|
|
13
|
+
import {
|
|
14
|
+
getWireFieldsSchema,
|
|
15
|
+
getWireSchemaForType,
|
|
16
|
+
} from "../../telemetry/telemetry-wire-validation.js";
|
|
17
|
+
import type {
|
|
18
|
+
OutboxTelemetryEventName,
|
|
19
|
+
TelemetryEvent,
|
|
20
|
+
} from "../../telemetry/types.js";
|
|
21
|
+
import { OUTBOX_TELEMETRY_EVENT_NAMES } from "../../telemetry/types.js";
|
|
13
22
|
import { getUsageTelemetryReporter } from "../../telemetry/usage-telemetry-reporter.js";
|
|
14
23
|
import { getLogger } from "../../util/logger.js";
|
|
24
|
+
import { APP_VERSION } from "../../version.js";
|
|
15
25
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
16
26
|
import { BadRequestError } from "./errors.js";
|
|
17
27
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
@@ -20,25 +30,58 @@ const log = getLogger("telemetry-routes");
|
|
|
20
30
|
|
|
21
31
|
const VALID_EVENT_NAMES = new Set(["app_open", "hatch"]);
|
|
22
32
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
const ingestDaemonEventIdSchema = z
|
|
34
|
+
.string()
|
|
35
|
+
.min(1)
|
|
36
|
+
.max(128)
|
|
37
|
+
.optional()
|
|
38
|
+
.describe(
|
|
39
|
+
"Optional collapse key: rows sharing an id collapse downstream " +
|
|
40
|
+
"(e.g. a retried report). Defaults to a fresh per-row id.",
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* One request variant per outbox-backed type: `{ type, fields, daemon_event_id? }`
|
|
45
|
+
* where `fields` is the wire schema for that type minus the daemon-stamped base
|
|
46
|
+
* fields. Throws at module load if an outbox name has no wire schema (an
|
|
47
|
+
* impossible drift between the outbox name list and the wire contract).
|
|
48
|
+
*/
|
|
49
|
+
function buildIngestVariant(name: OutboxTelemetryEventName) {
|
|
50
|
+
const fields = getWireFieldsSchema(name);
|
|
51
|
+
if (!fields) {
|
|
52
|
+
throw new Error(`No wire schema for outbox telemetry type "${name}".`);
|
|
53
|
+
}
|
|
54
|
+
return z.object({
|
|
55
|
+
type: z.literal(name),
|
|
56
|
+
fields: fields.describe(
|
|
57
|
+
"Wire event fields, excluding the daemon-stamped base fields " +
|
|
58
|
+
"(type, daemon_event_id, recorded_at, assistant_version).",
|
|
59
|
+
),
|
|
60
|
+
daemon_event_id: ingestDaemonEventIdSchema,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Request shape for `POST /v1/telemetry/ingest`: a discriminated union over
|
|
66
|
+
* every outbox-backed event type, each carrying that type's wire `fields`.
|
|
67
|
+
* Derived from the wire contract, so the generated SDK body is strongly typed
|
|
68
|
+
* and stays in sync as the wire schemas evolve.
|
|
69
|
+
*/
|
|
70
|
+
const telemetryIngestRequestSchema = z.discriminatedUnion(
|
|
71
|
+
"type",
|
|
72
|
+
// `.map` widens to `ZodObject[]`, but `discriminatedUnion` wants a non-empty
|
|
73
|
+
// tuple. The outbox name list is non-empty, and the RUNTIME schema (not this
|
|
74
|
+
// static type) is what codegen introspects, so the cast doesn't affect the
|
|
75
|
+
// emitted OpenAPI/SDK types.
|
|
76
|
+
OUTBOX_TELEMETRY_EVENT_NAMES.map(buildIngestVariant) as [
|
|
77
|
+
ReturnType<typeof buildIngestVariant>,
|
|
78
|
+
...ReturnType<typeof buildIngestVariant>[],
|
|
79
|
+
],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
/** Placeholder id satisfying the wire schema's `daemon_event_id` bound during
|
|
83
|
+
* the up-front validation pass; the real id is stamped at record time. */
|
|
84
|
+
const VALIDATION_PROBE_DAEMON_EVENT_ID = "validation-probe";
|
|
42
85
|
|
|
43
86
|
function handleRecordLifecycleEvent({ body }: RouteHandlerArgs) {
|
|
44
87
|
const eventName = body?.event_name as string | undefined;
|
|
@@ -62,31 +105,85 @@ async function handleTelemetryFlush() {
|
|
|
62
105
|
if (!reporter) {
|
|
63
106
|
return { flushed: false, reason: "disabled" };
|
|
64
107
|
}
|
|
65
|
-
|
|
66
|
-
return { flushed: true };
|
|
108
|
+
return reporter.flush();
|
|
67
109
|
}
|
|
68
110
|
|
|
69
|
-
|
|
70
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Record any outbox-backed telemetry event reported by a client. This is the
|
|
113
|
+
* generic bridge that exposes the daemon's in-process `recordTelemetryEvent`
|
|
114
|
+
* recorder to clients over HTTP/IPC — for events the client observes and the
|
|
115
|
+
* daemon can't detect on its own (e.g. `onboarding_research`).
|
|
116
|
+
*
|
|
117
|
+
* Three guarantees before a row lands:
|
|
118
|
+
* 1. `type` must be an outbox-backed event and `fields` must match that
|
|
119
|
+
* type's wire shape — both enforced by the discriminated-union parse. The
|
|
120
|
+
* watermark types (`turn`, `llm_usage`, `tool_executed`) are excluded
|
|
121
|
+
* structurally: they aren't outbox-backed, so they have no variant.
|
|
122
|
+
* 2. The assembled event must additionally pass the full wire schema for the
|
|
123
|
+
* byte-bound superRefines the request `fields` schema drops (oversize
|
|
124
|
+
* claims/suggestions).
|
|
125
|
+
* 3. Consent is enforced by {@link recordTelemetryOutboxEvent} (the
|
|
126
|
+
* `share_analytics` gate), same as every daemon-internal emitter.
|
|
127
|
+
*
|
|
128
|
+
* An optional `daemon_event_id` lets the client supply a collapse key (e.g. a
|
|
129
|
+
* refresh-retried report reusing a conversation-scoped id); it defaults to the
|
|
130
|
+
* fresh row id. A `conversation_id` field on the event is threaded into the
|
|
131
|
+
* outbox row's dedicated column so pending rows redact on conversation deletion
|
|
132
|
+
* via the indexed delete.
|
|
133
|
+
*/
|
|
134
|
+
function handleTelemetryIngest({ body }: RouteHandlerArgs) {
|
|
135
|
+
const parsed = telemetryIngestRequestSchema.safeParse(body);
|
|
71
136
|
if (!parsed.success) {
|
|
72
137
|
throw new BadRequestError(parsed.error.message);
|
|
73
138
|
}
|
|
139
|
+
const { type, fields, daemon_event_id: daemonEventId } = parsed.data;
|
|
74
140
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
141
|
+
// `type` matched a union variant (an outbox-backed event), so a wire schema
|
|
142
|
+
// is guaranteed — its absence is an invariant breach, not a client error.
|
|
143
|
+
const wireSchema = getWireSchemaForType(type);
|
|
144
|
+
if (!wireSchema) {
|
|
145
|
+
throw new Error(`No wire schema registered for outbox type "${type}".`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Re-validate the assembled event against the full wire schema for the
|
|
149
|
+
// byte-bound superRefines the request `fields` schema drops. Up front (with
|
|
150
|
+
// placeholder base values) so an oversize payload 400s regardless of consent.
|
|
151
|
+
// The `.trim()`-transformed parse output is discarded: `fields` are recorded
|
|
152
|
+
// verbatim, matching pre-flush validation's non-mutating contract.
|
|
153
|
+
const validation = wireSchema.safeParse({
|
|
154
|
+
...fields,
|
|
155
|
+
type,
|
|
156
|
+
daemon_event_id: daemonEventId ?? VALIDATION_PROBE_DAEMON_EVENT_ID,
|
|
157
|
+
recorded_at: 0,
|
|
158
|
+
assistant_version: APP_VERSION,
|
|
82
159
|
});
|
|
160
|
+
if (!validation.success) {
|
|
161
|
+
throw new BadRequestError(validation.error.message);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const conversationId =
|
|
165
|
+
"conversation_id" in fields && typeof fields.conversation_id === "string"
|
|
166
|
+
? fields.conversation_id
|
|
167
|
+
: null;
|
|
168
|
+
|
|
169
|
+
const event = recordTelemetryOutboxEvent(
|
|
170
|
+
type,
|
|
171
|
+
(id, createdAt): TelemetryEvent =>
|
|
172
|
+
// Base fields stamped after the spread so a `fields` payload carrying a
|
|
173
|
+
// base key can never override the daemon's stamp.
|
|
174
|
+
({
|
|
175
|
+
...fields,
|
|
176
|
+
type,
|
|
177
|
+
daemon_event_id: daemonEventId ?? id,
|
|
178
|
+
recorded_at: createdAt,
|
|
179
|
+
assistant_version: APP_VERSION,
|
|
180
|
+
}) as TelemetryEvent,
|
|
181
|
+
{ conversationId },
|
|
182
|
+
);
|
|
83
183
|
if (!event) {
|
|
84
184
|
return { skipped: true };
|
|
85
185
|
}
|
|
86
|
-
log.info(
|
|
87
|
-
{ eventId: event.id, claimCount: parsed.data.claims.length },
|
|
88
|
-
"Recorded onboarding-research event",
|
|
89
|
-
);
|
|
186
|
+
log.info({ type, eventId: event.id }, "Recorded client telemetry event");
|
|
90
187
|
|
|
91
188
|
return { id: event.id };
|
|
92
189
|
}
|
|
@@ -134,7 +231,14 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
134
231
|
"Force-flush the telemetry events owned by the assistant process (turn events) to the platform. Other event types are flushed on their own cycle by the resource monitor process.",
|
|
135
232
|
tags: ["telemetry"],
|
|
136
233
|
responseBody: z.union([
|
|
137
|
-
z.object({
|
|
234
|
+
z.object({
|
|
235
|
+
flushed: z.literal(true),
|
|
236
|
+
sent: z.number().describe("Events POSTed to the platform"),
|
|
237
|
+
persisted: z.number().describe("Events the platform confirmed written"),
|
|
238
|
+
dropped: z
|
|
239
|
+
.number()
|
|
240
|
+
.describe("Events that did not land (sent - persisted)"),
|
|
241
|
+
}),
|
|
138
242
|
z.object({
|
|
139
243
|
flushed: z.literal(false),
|
|
140
244
|
reason: z.string(),
|
|
@@ -143,28 +247,28 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
143
247
|
handler: handleTelemetryFlush,
|
|
144
248
|
},
|
|
145
249
|
{
|
|
146
|
-
operationId: "
|
|
147
|
-
endpoint: "telemetry/
|
|
250
|
+
operationId: "telemetry_ingest",
|
|
251
|
+
endpoint: "telemetry/ingest",
|
|
148
252
|
method: "POST",
|
|
149
253
|
policy: {
|
|
150
254
|
requiredScopes: ["settings.write"],
|
|
151
255
|
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
152
256
|
},
|
|
153
|
-
summary: "Record
|
|
257
|
+
summary: "Record an outbox-backed telemetry event",
|
|
154
258
|
description:
|
|
155
|
-
|
|
259
|
+
"Record any outbox-backed telemetry event by its wire `type` + `fields`. For events a client observes and the daemon can't detect on its own (e.g. onboarding_research). The type must be an outbox-backed event — the watermark types (turn, llm_usage, tool_executed) have no variant — and the payload must pass the platform wire schema. Gated on share_analytics consent like every other outbox-backed event; the platform re-checks the owner's consent server-side at ingest.",
|
|
156
260
|
tags: ["telemetry"],
|
|
157
|
-
requestBody:
|
|
261
|
+
requestBody: telemetryIngestRequestSchema,
|
|
158
262
|
responseBody: z.union([
|
|
159
263
|
z.object({ id: z.string().describe("Event ID") }),
|
|
160
264
|
z.object({
|
|
161
265
|
skipped: z
|
|
162
266
|
.literal(true)
|
|
163
267
|
.describe(
|
|
164
|
-
"Event skipped: usage data collection or
|
|
268
|
+
"Event skipped: usage data collection is disabled or the telemetry database is unavailable",
|
|
165
269
|
),
|
|
166
270
|
}),
|
|
167
271
|
]),
|
|
168
|
-
handler:
|
|
272
|
+
handler: handleTelemetryIngest,
|
|
169
273
|
},
|
|
170
274
|
];
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
let mockUi: {
|
|
4
|
+
userTimezone?: string | null;
|
|
5
|
+
detectedTimezone?: string | null;
|
|
6
|
+
} = {};
|
|
7
|
+
|
|
8
|
+
mock.module("../../config/loader.js", () => ({
|
|
9
|
+
getConfigReadOnly: () => ({ ui: mockUi }),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
const { resolveScheduleTimezone, expressionCarriesOwnTimezone } =
|
|
13
|
+
await import("../schedule-timezone.js");
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
mockUi = {};
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("resolveScheduleTimezone", () => {
|
|
20
|
+
test("an explicit value wins and is canonicalized", () => {
|
|
21
|
+
mockUi = { userTimezone: "America/New_York" };
|
|
22
|
+
expect(resolveScheduleTimezone("America/Los_Angeles")).toBe(
|
|
23
|
+
"America/Los_Angeles",
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("falls back to the configured user timezone (beats detected)", () => {
|
|
28
|
+
mockUi = {
|
|
29
|
+
userTimezone: "America/New_York",
|
|
30
|
+
detectedTimezone: "America/Chicago",
|
|
31
|
+
};
|
|
32
|
+
expect(resolveScheduleTimezone(null)).toBe("America/New_York");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("falls back to the detected timezone when no user timezone is set", () => {
|
|
36
|
+
mockUi = { detectedTimezone: "America/Chicago" };
|
|
37
|
+
expect(resolveScheduleTimezone(undefined)).toBe("America/Chicago");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("returns null when nothing is known (host-local is preserved)", () => {
|
|
41
|
+
mockUi = {};
|
|
42
|
+
expect(resolveScheduleTimezone(null)).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("ignores an invalid explicit value and falls back", () => {
|
|
46
|
+
mockUi = { userTimezone: "America/New_York" };
|
|
47
|
+
expect(resolveScheduleTimezone("Not/AZone")).toBe("America/New_York");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("expressionCarriesOwnTimezone", () => {
|
|
52
|
+
test("cron never carries its own zone", () => {
|
|
53
|
+
expect(expressionCarriesOwnTimezone("cron", "30 8 * * *")).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("a plain RRULE does not carry its own zone", () => {
|
|
57
|
+
expect(expressionCarriesOwnTimezone("rrule", "FREQ=DAILY;BYHOUR=8")).toBe(
|
|
58
|
+
false,
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("an RRULE with embedded DTSTART;TZID carries its own zone (not clobbered)", () => {
|
|
63
|
+
expect(
|
|
64
|
+
expressionCarriesOwnTimezone(
|
|
65
|
+
"rrule",
|
|
66
|
+
"DTSTART;TZID=America/Chicago:20240101T083000\nRRULE:FREQ=DAILY",
|
|
67
|
+
),
|
|
68
|
+
).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("an RRULE with a Z-anchored UTC DTSTART carries its own zone", () => {
|
|
72
|
+
expect(
|
|
73
|
+
expressionCarriesOwnTimezone(
|
|
74
|
+
"rrule",
|
|
75
|
+
"DTSTART:20240101T083000Z\nRRULE:FREQ=DAILY",
|
|
76
|
+
),
|
|
77
|
+
).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("an RRULE set-construct is treated as carrying its own zone", () => {
|
|
81
|
+
// rrulestr does not thread a caller-supplied tzid into a constructed
|
|
82
|
+
// RRuleSet, so resolving a zone for a set-construct would not change firing;
|
|
83
|
+
// we therefore leave the timezone field alone for these.
|
|
84
|
+
expect(
|
|
85
|
+
expressionCarriesOwnTimezone(
|
|
86
|
+
"rrule",
|
|
87
|
+
"RRULE:FREQ=DAILY\nRDATE:20240101T083000",
|
|
88
|
+
),
|
|
89
|
+
).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Deeper guarantee behind the set-construct exclusion above: the recurrence
|
|
93
|
+
// engine's computeNextRunAt must not shift when a set-construct expression is
|
|
94
|
+
// given an explicit timezone (proving the field genuinely has no effect, so a
|
|
95
|
+
// future rrule upgrade that changes this is caught). Convert to a real test
|
|
96
|
+
// when a set-construct fixture with a known epoch is added.
|
|
97
|
+
test.todo(
|
|
98
|
+
"computeNextRunAt is unaffected by an explicit tz on a set-construct expression",
|
|
99
|
+
() => {},
|
|
100
|
+
);
|
|
101
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createWorkerSupervisor,
|
|
5
|
+
type WorkerProcessStatus,
|
|
6
|
+
} from "../../util/worker-process.js";
|
|
7
|
+
|
|
8
|
+
const RUNNING: WorkerProcessStatus = { status: "running", pid: 100 };
|
|
9
|
+
const NOT_RUNNING: WorkerProcessStatus = { status: "not_running" };
|
|
10
|
+
|
|
11
|
+
describe("createWorkerSupervisor", () => {
|
|
12
|
+
test("does not respawn a running worker", async () => {
|
|
13
|
+
let respawns = 0;
|
|
14
|
+
const sup = createWorkerSupervisor({
|
|
15
|
+
label: "t",
|
|
16
|
+
probe: () => RUNNING,
|
|
17
|
+
respawn: async () => {
|
|
18
|
+
respawns++;
|
|
19
|
+
return { pid: 1, alreadyRunning: false };
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
await sup.ensureAlive();
|
|
24
|
+
|
|
25
|
+
expect(respawns).toBe(0);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("respawns a dead worker and fires onRespawn with the pid", async () => {
|
|
29
|
+
let respawns = 0;
|
|
30
|
+
let respawnedPid: number | undefined;
|
|
31
|
+
const sup = createWorkerSupervisor({
|
|
32
|
+
label: "t",
|
|
33
|
+
probe: () => NOT_RUNNING,
|
|
34
|
+
respawn: async () => {
|
|
35
|
+
respawns++;
|
|
36
|
+
return { pid: 42, alreadyRunning: false };
|
|
37
|
+
},
|
|
38
|
+
onRespawn: (pid) => {
|
|
39
|
+
respawnedPid = pid;
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
await sup.ensureAlive();
|
|
44
|
+
|
|
45
|
+
expect(respawns).toBe(1);
|
|
46
|
+
expect(respawnedPid).toBe(42);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("does not fire onRespawn when the worker was already running", async () => {
|
|
50
|
+
let onRespawnCalls = 0;
|
|
51
|
+
const sup = createWorkerSupervisor({
|
|
52
|
+
label: "t",
|
|
53
|
+
probe: () => NOT_RUNNING,
|
|
54
|
+
respawn: async () => ({ pid: 42, alreadyRunning: true }),
|
|
55
|
+
onRespawn: () => {
|
|
56
|
+
onRespawnCalls++;
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await sup.ensureAlive();
|
|
61
|
+
|
|
62
|
+
expect(onRespawnCalls).toBe(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("backs off after a failed respawn and skips attempts within the window", async () => {
|
|
66
|
+
let respawns = 0;
|
|
67
|
+
const sup = createWorkerSupervisor({
|
|
68
|
+
label: "t",
|
|
69
|
+
probe: () => NOT_RUNNING,
|
|
70
|
+
respawn: async () => {
|
|
71
|
+
respawns++;
|
|
72
|
+
throw new Error("spawn failed");
|
|
73
|
+
},
|
|
74
|
+
minBackoffMs: 1000,
|
|
75
|
+
maxBackoffMs: 10_000,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await sup.ensureAlive(0); // attempt 1 fails → nextAttemptAt = 1000
|
|
79
|
+
expect(respawns).toBe(1);
|
|
80
|
+
await sup.ensureAlive(500); // within backoff → skipped
|
|
81
|
+
expect(respawns).toBe(1);
|
|
82
|
+
await sup.ensureAlive(1500); // past backoff → attempt 2
|
|
83
|
+
expect(respawns).toBe(2);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("fires onPersistentFailure exactly once at the threshold", async () => {
|
|
87
|
+
let persistentCalls = 0;
|
|
88
|
+
const sup = createWorkerSupervisor({
|
|
89
|
+
label: "t",
|
|
90
|
+
probe: () => NOT_RUNNING,
|
|
91
|
+
respawn: async () => {
|
|
92
|
+
throw new Error("nope");
|
|
93
|
+
},
|
|
94
|
+
minBackoffMs: 1,
|
|
95
|
+
maxBackoffMs: 1,
|
|
96
|
+
persistentFailureThreshold: 3,
|
|
97
|
+
onPersistentFailure: () => {
|
|
98
|
+
persistentCalls++;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await sup.ensureAlive(0); // fail 1
|
|
103
|
+
await sup.ensureAlive(10); // fail 2
|
|
104
|
+
await sup.ensureAlive(20); // fail 3 → fires
|
|
105
|
+
await sup.ensureAlive(30); // fail 4 → no additional fire
|
|
106
|
+
|
|
107
|
+
expect(persistentCalls).toBe(1);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("a later successful respawn resets the failure counters", async () => {
|
|
111
|
+
let mode: "fail" | "ok" = "fail";
|
|
112
|
+
let persistentCalls = 0;
|
|
113
|
+
const sup = createWorkerSupervisor({
|
|
114
|
+
label: "t",
|
|
115
|
+
probe: () => NOT_RUNNING,
|
|
116
|
+
respawn: async () => {
|
|
117
|
+
if (mode === "fail") {
|
|
118
|
+
throw new Error("nope");
|
|
119
|
+
}
|
|
120
|
+
return { pid: 7, alreadyRunning: false };
|
|
121
|
+
},
|
|
122
|
+
minBackoffMs: 1,
|
|
123
|
+
maxBackoffMs: 1,
|
|
124
|
+
persistentFailureThreshold: 2,
|
|
125
|
+
onPersistentFailure: () => {
|
|
126
|
+
persistentCalls++;
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await sup.ensureAlive(0); // fail 1
|
|
131
|
+
mode = "ok";
|
|
132
|
+
await sup.ensureAlive(10); // success → resets counter
|
|
133
|
+
mode = "fail";
|
|
134
|
+
await sup.ensureAlive(20); // fail 1 again (counter was reset)
|
|
135
|
+
|
|
136
|
+
expect(persistentCalls).toBe(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("does not respawn while suppressed (operator stop)", async () => {
|
|
140
|
+
let respawns = 0;
|
|
141
|
+
let suppressed = true;
|
|
142
|
+
const sup = createWorkerSupervisor({
|
|
143
|
+
label: "t",
|
|
144
|
+
probe: () => NOT_RUNNING,
|
|
145
|
+
respawn: async () => {
|
|
146
|
+
respawns++;
|
|
147
|
+
return { pid: 1, alreadyRunning: false };
|
|
148
|
+
},
|
|
149
|
+
isSuppressed: () => suppressed,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await sup.ensureAlive();
|
|
153
|
+
expect(respawns).toBe(0);
|
|
154
|
+
|
|
155
|
+
suppressed = false;
|
|
156
|
+
await sup.ensureAlive();
|
|
157
|
+
expect(respawns).toBe(1);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("dispose mid-respawn kills the child that resolves after disposal", async () => {
|
|
161
|
+
let releaseRespawn: () => void = () => {};
|
|
162
|
+
const respawnGate = new Promise<void>((resolve) => {
|
|
163
|
+
releaseRespawn = resolve;
|
|
164
|
+
});
|
|
165
|
+
let killedPid: number | undefined;
|
|
166
|
+
const sup = createWorkerSupervisor({
|
|
167
|
+
label: "t",
|
|
168
|
+
probe: () => NOT_RUNNING,
|
|
169
|
+
respawn: async () => {
|
|
170
|
+
await respawnGate;
|
|
171
|
+
return { pid: 99, alreadyRunning: false };
|
|
172
|
+
},
|
|
173
|
+
killChild: (pid) => {
|
|
174
|
+
killedPid = pid;
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const inflight = sup.ensureAlive(); // starts respawn, awaits the gate
|
|
179
|
+
sup.dispose(); // dispose while the respawn is in flight
|
|
180
|
+
releaseRespawn(); // respawn resolves now
|
|
181
|
+
await inflight;
|
|
182
|
+
|
|
183
|
+
expect(killedPid).toBe(99);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("concurrent ensureAlive calls do not double-spawn", async () => {
|
|
187
|
+
let respawns = 0;
|
|
188
|
+
let releaseRespawn: () => void = () => {};
|
|
189
|
+
const gate = new Promise<void>((resolve) => {
|
|
190
|
+
releaseRespawn = resolve;
|
|
191
|
+
});
|
|
192
|
+
const sup = createWorkerSupervisor({
|
|
193
|
+
label: "t",
|
|
194
|
+
probe: () => NOT_RUNNING,
|
|
195
|
+
respawn: async () => {
|
|
196
|
+
respawns++;
|
|
197
|
+
await gate;
|
|
198
|
+
return { pid: 1, alreadyRunning: false };
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const a = sup.ensureAlive();
|
|
203
|
+
const b = sup.ensureAlive(); // skipped by the inFlight guard
|
|
204
|
+
releaseRespawn();
|
|
205
|
+
await Promise.all([a, b]);
|
|
206
|
+
|
|
207
|
+
expect(respawns).toBe(1);
|
|
208
|
+
});
|
|
209
|
+
});
|