@vellumai/assistant 0.5.13 → 0.5.14
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/.env.example +1 -6
- package/AGENTS.md +4 -0
- package/ARCHITECTURE.md +0 -1
- package/bunfig.toml +1 -0
- package/docs/architecture/memory.md +3 -3
- package/openapi.yaml +127 -22
- package/package.json +1 -1
- package/src/__tests__/access-request-decision.test.ts +2 -32
- package/src/__tests__/actor-token-service.test.ts +1 -31
- package/src/__tests__/anthropic-provider.test.ts +53 -40
- package/src/__tests__/app-git-history.test.ts +9 -17
- package/src/__tests__/app-git-service.test.ts +14 -20
- package/src/__tests__/app-store-dir-names.test.ts +10 -20
- package/src/__tests__/approval-cascade.test.ts +2 -19
- package/src/__tests__/approval-primitive.test.ts +2 -27
- package/src/__tests__/approval-routes-http.test.ts +2 -30
- package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -28
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -45
- package/src/__tests__/attachments-store.test.ts +5 -32
- package/src/__tests__/audit-log-rotation.test.ts +5 -36
- package/src/__tests__/avatar-e2e.test.ts +1 -9
- package/src/__tests__/avatar-generator.test.ts +1 -7
- package/src/__tests__/browser-fill-credential.test.ts +0 -4
- package/src/__tests__/browser-manager.test.ts +0 -6
- package/src/__tests__/call-controller.test.ts +1 -22
- package/src/__tests__/call-conversation-messages.test.ts +0 -21
- package/src/__tests__/call-domain.test.ts +0 -25
- package/src/__tests__/call-pointer-messages.test.ts +0 -21
- package/src/__tests__/call-recovery.test.ts +0 -22
- package/src/__tests__/call-routes-http.test.ts +0 -24
- package/src/__tests__/call-store.test.ts +0 -21
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -24
- package/src/__tests__/canonical-guardian-store.test.ts +48 -21
- package/src/__tests__/channel-approval-routes.test.ts +6 -26
- package/src/__tests__/channel-approvals.test.ts +1 -38
- package/src/__tests__/channel-delivery-store.test.ts +0 -21
- package/src/__tests__/channel-guardian.test.ts +0 -26
- package/src/__tests__/channel-reply-delivery.test.ts +5 -0
- package/src/__tests__/channel-retry-sweep.test.ts +0 -21
- package/src/__tests__/checker.test.ts +26 -61
- package/src/__tests__/clawhub.test.ts +9 -25
- package/src/__tests__/cli-command-risk-guard.test.ts +0 -18
- package/src/__tests__/config-loader-backfill.test.ts +9 -28
- package/src/__tests__/config-schema-cmd.test.ts +5 -25
- package/src/__tests__/config-schema.test.ts +21 -40
- package/src/__tests__/config-watcher.test.ts +4 -91
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -21
- package/src/__tests__/contacts-tools.test.ts +0 -21
- package/src/__tests__/context-memory-e2e.test.ts +0 -21
- package/src/__tests__/context-window-manager.test.ts +130 -3
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -4
- package/src/__tests__/conversation-agent-loop.test.ts +0 -4
- package/src/__tests__/conversation-attachments.test.ts +1 -24
- package/src/__tests__/conversation-attention-store.test.ts +0 -21
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -22
- package/src/__tests__/conversation-clear-safety.test.ts +0 -22
- package/src/__tests__/conversation-confirmation-signals.test.ts +2 -21
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -24
- package/src/__tests__/conversation-disk-view-integration.test.ts +1 -23
- package/src/__tests__/conversation-disk-view.test.ts +5 -27
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +1 -33
- package/src/__tests__/conversation-fork-route.test.ts +0 -27
- package/src/__tests__/conversation-history-web-search.test.ts +23 -16
- package/src/__tests__/conversation-init.benchmark.test.ts +22 -43
- package/src/__tests__/conversation-key-store-disk-view.test.ts +8 -34
- package/src/__tests__/conversation-load-history-repair.test.ts +0 -4
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -4
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -4
- package/src/__tests__/conversation-queue.test.ts +8 -8
- package/src/__tests__/conversation-routes-disk-view.test.ts +13 -51
- package/src/__tests__/conversation-runtime-assembly.test.ts +64 -38
- package/src/__tests__/conversation-slash-commands.test.ts +5 -0
- package/src/__tests__/conversation-slash-queue.test.ts +0 -4
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -4
- package/src/__tests__/conversation-speed-override.test.ts +326 -0
- package/src/__tests__/conversation-starter-routes.test.ts +0 -23
- package/src/__tests__/conversation-store.test.ts +0 -21
- package/src/__tests__/conversation-unread-route.test.ts +0 -24
- package/src/__tests__/conversation-usage.test.ts +56 -21
- package/src/__tests__/conversation-wipe.test.ts +0 -21
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -4
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -4
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -4
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +8 -5
- package/src/__tests__/credential-vault-unit.test.ts +9 -428
- package/src/__tests__/credentials-cli.test.ts +10 -10
- package/src/__tests__/daemon-assistant-events.test.ts +0 -19
- package/src/__tests__/date-context.test.ts +77 -97
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +7 -24
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +29 -42
- package/src/__tests__/delete-managed-skill-tool.test.ts +2 -10
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -26
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +61 -15
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -36
- package/src/__tests__/email-cli.test.ts +6 -6
- package/src/__tests__/ephemeral-permissions.test.ts +5 -17
- package/src/__tests__/first-greeting.test.ts +4 -32
- package/src/__tests__/followup-tools.test.ts +0 -21
- package/src/__tests__/gateway-only-enforcement.test.ts +0 -20
- package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -23
- package/src/__tests__/guardian-action-followup-executor.test.ts +0 -23
- package/src/__tests__/guardian-action-followup-store.test.ts +0 -21
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -21
- package/src/__tests__/guardian-action-late-reply.test.ts +0 -21
- package/src/__tests__/guardian-action-store.test.ts +0 -21
- package/src/__tests__/guardian-action-sweep.test.ts +0 -21
- package/src/__tests__/guardian-binding-drift-heal.test.ts +0 -23
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +172 -22
- package/src/__tests__/guardian-dispatch.test.ts +0 -21
- package/src/__tests__/guardian-grant-minting.test.ts +0 -22
- package/src/__tests__/guardian-outbound-http.test.ts +0 -22
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -23
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -22
- package/src/__tests__/guardian-routing-state.test.ts +0 -22
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -24
- package/src/__tests__/headless-browser-interactions.test.ts +0 -4
- package/src/__tests__/headless-browser-navigate.test.ts +0 -4
- package/src/__tests__/headless-browser-read-tools.test.ts +0 -4
- package/src/__tests__/headless-browser-snapshot.test.ts +0 -4
- package/src/__tests__/heartbeat-service.test.ts +99 -26
- package/src/__tests__/hooks-blocking.test.ts +3 -3
- package/src/__tests__/hooks-config.test.ts +7 -7
- package/src/__tests__/hooks-discovery.test.ts +3 -3
- package/src/__tests__/hooks-integration.test.ts +5 -5
- package/src/__tests__/hooks-manager.test.ts +3 -3
- package/src/__tests__/hooks-runner.test.ts +5 -23
- package/src/__tests__/hooks-settings.test.ts +3 -3
- package/src/__tests__/hooks-templates.test.ts +3 -3
- package/src/__tests__/http-conversation-lineage.test.ts +0 -27
- package/src/__tests__/identity-intro-cache.test.ts +0 -4
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -22
- package/src/__tests__/inline-skill-load-permissions.test.ts +5 -16
- package/src/__tests__/intent-routing.test.ts +2 -55
- package/src/__tests__/invite-redemption-service.test.ts +0 -21
- package/src/__tests__/invite-routes-http.test.ts +0 -21
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +0 -17
- package/src/__tests__/journal-context.test.ts +8 -75
- package/src/__tests__/list-messages-attachments.test.ts +0 -22
- package/src/__tests__/llm-context-route-provider.test.ts +0 -21
- package/src/__tests__/llm-request-log-turn-query.test.ts +46 -28
- package/src/__tests__/llm-usage-store.test.ts +0 -21
- package/src/__tests__/log-export-workspace.test.ts +1 -1
- package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
- package/src/__tests__/managed-store.test.ts +1 -1
- package/src/__tests__/mcp-cli.test.ts +7 -10
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -21
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +0 -11
- package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -21
- package/src/__tests__/memory-recall-log-store.test.ts +0 -27
- package/src/__tests__/memory-recall-quality.test.ts +0 -21
- package/src/__tests__/memory-regressions.experimental.test.ts +31 -30
- package/src/__tests__/memory-regressions.test.ts +282 -70
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -21
- package/src/__tests__/memory-upsert-concurrency.test.ts +0 -21
- package/src/__tests__/messaging-send-tool.test.ts +201 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +18 -13
- package/src/__tests__/migration-export-http.test.ts +7 -1
- package/src/__tests__/migration-import-commit-http.test.ts +16 -14
- package/src/__tests__/migration-import-preflight-http.test.ts +27 -44
- package/src/__tests__/migration-validate-http.test.ts +1 -28
- package/src/__tests__/native-web-search.test.ts +25 -22
- package/src/__tests__/non-member-access-request.test.ts +0 -22
- package/src/__tests__/notification-guardian-path.test.ts +0 -21
- package/src/__tests__/notification-schedule-dedup.test.ts +1 -25
- package/src/__tests__/oauth-apps-routes.test.ts +103 -2
- package/src/__tests__/oauth-cli.test.ts +52 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +0 -16
- package/src/__tests__/oauth-provider-serializer.test.ts +232 -0
- package/src/__tests__/oauth-providers-routes.test.ts +257 -0
- package/src/__tests__/oauth-store.test.ts +0 -21
- package/src/__tests__/onboarding-template-contract.test.ts +2 -2
- package/src/__tests__/openai-provider.test.ts +261 -0
- package/src/__tests__/pairing-concurrent.test.ts +6 -6
- package/src/__tests__/pairing-routes.test.ts +7 -1
- package/src/__tests__/path-policy.test.ts +1 -1
- package/src/__tests__/platform.test.ts +64 -88
- package/src/__tests__/playbook-execution.test.ts +0 -21
- package/src/__tests__/playbook-tools.test.ts +0 -21
- package/src/__tests__/pricing.test.ts +100 -0
- package/src/__tests__/relay-server.test.ts +1 -25
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -24
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -24
- package/src/__tests__/runtime-events-sse.test.ts +0 -24
- package/src/__tests__/sandbox-diagnostics.test.ts +2 -1
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
- package/src/__tests__/schedule-store.test.ts +0 -21
- package/src/__tests__/schedule-tools.test.ts +0 -21
- package/src/__tests__/scheduler-recurrence.test.ts +0 -21
- package/src/__tests__/scoped-approval-grants.test.ts +0 -21
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -21
- package/src/__tests__/secret-allowlist.test.ts +1 -1
- package/src/__tests__/secret-ingress-channel.test.ts +0 -5
- package/src/__tests__/secret-ingress-cli.test.ts +0 -6
- package/src/__tests__/secret-ingress-http.test.ts +0 -5
- package/src/__tests__/secret-ingress.test.ts +0 -5
- package/src/__tests__/send-endpoint-busy.test.ts +0 -24
- package/src/__tests__/sequence-store.test.ts +0 -21
- package/src/__tests__/server-history-render.test.ts +0 -24
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -4
- package/src/__tests__/skill-load-inline-command.test.ts +9 -0
- package/src/__tests__/skill-load-inline-includes.test.ts +9 -0
- package/src/__tests__/skill-load-tool.test.ts +11 -0
- package/src/__tests__/skills-uninstall.test.ts +10 -8
- package/src/__tests__/skills.test.ts +1 -1
- package/src/__tests__/slack-channel-config.test.ts +1 -1
- package/src/__tests__/slack-inbound-verification.test.ts +0 -22
- package/src/__tests__/starter-bundle.test.ts +4 -1
- package/src/__tests__/suggestion-routes.test.ts +2 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +1 -1
- package/src/__tests__/test-preload.ts +31 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +0 -20
- package/src/__tests__/tool-input-summary.test.ts +124 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -1
- package/src/__tests__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -1
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +1 -1
- package/src/__tests__/trusted-contact-multichannel.test.ts +1 -1
- package/src/__tests__/trusted-contact-verification.test.ts +1 -1
- package/src/__tests__/turn-boundary-resolution.test.ts +1 -1
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/update-bulletin.test.ts +1 -1
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +1 -1
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
- package/src/__tests__/voice-session-bridge.test.ts +1 -1
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -4
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
- package/src/__tests__/workspace-migration-down-functions.test.ts +15 -3
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +40 -4
- package/src/agent/loop.ts +6 -9
- package/src/approvals/guardian-decision-primitive.ts +46 -18
- package/src/approvals/guardian-request-resolvers.ts +19 -2
- package/src/calls/active-call-lease.ts +2 -2
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/commands/doctor.ts +9 -9
- package/src/cli/commands/memory.ts +142 -0
- package/src/cli/commands/oauth/__tests__/connect.test.ts +13 -11
- package/src/cli/commands/oauth/__tests__/ping.test.ts +1 -1
- package/src/cli/commands/oauth/connect.ts +13 -12
- package/src/cli/commands/oauth/index.ts +1 -1
- package/src/cli/commands/oauth/providers.ts +47 -62
- package/src/cli/commands/platform/__tests__/connect.test.ts +72 -46
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +54 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +36 -0
- package/src/cli/commands/platform/connect.ts +17 -7
- package/src/cli/commands/platform/disconnect.ts +28 -3
- package/src/cli/commands/platform/index.ts +3 -3
- package/src/cli.ts +1 -299
- package/src/config/assistant-feature-flags.ts +23 -15
- package/src/config/bundled-skills/app-builder/TOOLS.json +16 -0
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +4 -0
- package/src/config/bundled-skills/app-builder/tools/app-delete.ts +5 -1
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +9 -1
- package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +5 -1
- package/src/config/bundled-skills/contacts/TOOLS.json +8 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +10 -1
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +16 -2
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +1 -0
- package/src/config/bundled-skills/messaging/SKILL.md +7 -7
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +37 -0
- package/src/config/bundled-skills/slack/SKILL.md +18 -0
- package/src/config/env-registry.ts +15 -11
- package/src/config/env.ts +1 -11
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/schema.ts +4 -0
- package/src/config/schemas/heartbeat.ts +6 -1
- package/src/config/schemas/inference.ts +14 -3
- package/src/config/schemas/memory-processing.ts +16 -8
- package/src/config/schemas/memory-retrieval.ts +3 -3
- package/src/config/skills.ts +1 -1
- package/src/context/window-manager.ts +174 -51
- package/src/credential-execution/executable-discovery.ts +2 -2
- package/src/daemon/approved-devices-store.ts +2 -2
- package/src/daemon/assistant-attachments.ts +2 -0
- package/src/daemon/config-watcher.ts +4 -50
- package/src/daemon/conversation-agent-loop-handlers.ts +9 -1
- package/src/daemon/conversation-agent-loop.ts +12 -0
- package/src/daemon/conversation-error.ts +3 -5
- package/src/daemon/conversation-history.ts +7 -3
- package/src/daemon/conversation-lifecycle.ts +16 -0
- package/src/daemon/conversation-messaging.ts +1 -0
- package/src/daemon/conversation-notifiers.ts +67 -30
- package/src/daemon/conversation-process.ts +161 -2
- package/src/daemon/conversation-queue-manager.ts +2 -0
- package/src/daemon/conversation-runtime-assembly.ts +33 -11
- package/src/daemon/conversation-slash.ts +14 -3
- package/src/daemon/conversation-tool-setup.ts +2 -0
- package/src/daemon/conversation-usage.ts +32 -4
- package/src/daemon/conversation.ts +33 -1
- package/src/daemon/daemon-control.ts +32 -16
- package/src/daemon/date-context.ts +47 -45
- package/src/daemon/dictation-profile-store.ts +2 -2
- package/src/daemon/handlers/conversations.ts +19 -0
- package/src/daemon/handlers/shared.ts +14 -21
- package/src/daemon/lifecycle.ts +5 -7
- package/src/daemon/message-types/conversations.ts +2 -0
- package/src/daemon/message-types/guardian-actions.ts +3 -17
- package/src/daemon/message-types/integrations.ts +11 -1
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/pairing-store.ts +2 -79
- package/src/daemon/server.ts +154 -8
- package/src/daemon/watch-handler.ts +65 -21
- package/src/email/guardrails.ts +3 -3
- package/src/heartbeat/heartbeat-service.ts +14 -7
- package/src/hooks/cli.ts +2 -2
- package/src/hooks/config.ts +2 -2
- package/src/hooks/discovery.ts +2 -2
- package/src/hooks/manager.ts +2 -2
- package/src/hooks/runner.ts +5 -2
- package/src/hooks/templates.ts +2 -2
- package/src/memory/admin.ts +181 -2
- package/src/memory/app-git-service.ts +61 -4
- package/src/memory/attachments-store.ts +2 -0
- package/src/memory/canonical-guardian-store.ts +16 -0
- package/src/memory/db-init.ts +8 -0
- package/src/memory/embedding-local.ts +5 -2
- package/src/memory/indexer.ts +44 -26
- package/src/memory/items-extractor.ts +34 -82
- package/src/memory/job-handlers/batch-extraction.ts +741 -0
- package/src/memory/job-handlers/journal-carry-forward.test.ts +383 -0
- package/src/memory/job-handlers/journal-carry-forward.ts +255 -0
- package/src/memory/jobs-store.ts +28 -0
- package/src/memory/jobs-worker.ts +56 -9
- package/src/memory/lifecycle-events-store.ts +4 -2
- package/src/memory/llm-request-log-store.ts +40 -2
- package/src/memory/llm-usage-store.ts +4 -3
- package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +71 -0
- package/src/memory/migrations/200-usage-llm-call-count.ts +20 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/query-expansion.ts +83 -0
- package/src/memory/retriever.test.ts +119 -0
- package/src/memory/retriever.ts +513 -105
- package/src/memory/schema/guardian.ts +4 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/search/formatting.test.ts +140 -0
- package/src/memory/search/formatting.ts +143 -198
- package/src/memory/search/mmr.ts +136 -0
- package/src/memory/search/staleness.ts +0 -15
- package/src/memory/search/tier-classifier.ts +10 -21
- package/src/memory/search/types.ts +17 -0
- package/src/messaging/providers/slack/adapter.ts +51 -5
- package/src/notifications/broadcaster.ts +13 -0
- package/src/notifications/copy-composer.ts +8 -0
- package/src/oauth/connect-orchestrator.ts +1 -1
- package/src/oauth/connection-resolver.ts +2 -2
- package/src/oauth/provider-serializer.ts +116 -0
- package/src/permissions/trust-store.ts +24 -7
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +5 -0
- package/src/prompts/journal-context.ts +50 -35
- package/src/prompts/persona-resolver.ts +1 -1
- package/src/prompts/system-prompt.ts +27 -28
- package/src/prompts/templates/BOOTSTRAP.md +14 -1
- package/src/prompts/templates/HEARTBEAT.md +10 -0
- package/src/prompts/templates/NOW.md +19 -25
- package/src/prompts/templates/SOUL.md +13 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/update-bulletin.ts +1 -1
- package/src/providers/anthropic/client.ts +89 -18
- package/src/providers/model-catalog.ts +22 -2
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/client.ts +40 -1
- package/src/providers/retry.ts +23 -4
- package/src/providers/types.ts +2 -0
- package/src/runtime/assistant-scope.ts +1 -1
- package/src/runtime/auth/__tests__/credential-service.test.ts +1 -0
- package/src/runtime/auth/route-policy.ts +1 -0
- package/src/runtime/auth/token-service.ts +51 -29
- package/src/runtime/confirmation-request-guardian-bridge.ts +3 -1
- package/src/runtime/guardian-decision-types.ts +16 -10
- package/src/runtime/http-server.ts +3 -14
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +7 -4
- package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -4
- package/src/runtime/migrations/vbundle-importer.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +40 -8
- package/src/runtime/routes/conversation-routes.ts +125 -3
- package/src/runtime/routes/guardian-action-routes.ts +9 -3
- package/src/runtime/routes/identity-routes.ts +25 -4
- package/src/runtime/routes/llm-context-normalization.ts +1 -0
- package/src/runtime/routes/log-export-routes.ts +34 -12
- package/src/runtime/routes/migration-routes.ts +6 -10
- package/src/runtime/routes/oauth-apps.ts +2 -9
- package/src/runtime/routes/oauth-providers.ts +60 -0
- package/src/runtime/routes/pairing-routes.ts +0 -8
- package/src/runtime/routes/settings-routes.ts +0 -1
- package/src/runtime/routes/telemetry-routes.ts +16 -4
- package/src/security/encrypted-store.ts +2 -2
- package/src/security/secret-allowlist.ts +3 -3
- package/src/signals/emit-event.ts +42 -0
- package/src/signals/user-message.ts +37 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +83 -19
- package/src/telemetry/usage-telemetry-reporter.ts +23 -17
- package/src/tools/browser/runtime-check.ts +2 -2
- package/src/tools/credentials/vault.ts +2 -249
- package/src/tools/memory/definitions.ts +1 -1
- package/src/tools/memory/handlers.test.ts +50 -8
- package/src/tools/memory/handlers.ts +3 -1
- package/src/tools/side-effects.ts +1 -6
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +11 -14
- package/src/tools/tool-approval-handler.ts +20 -1
- package/src/tools/tool-input-summary.ts +66 -0
- package/src/tools/types.ts +4 -0
- package/src/usage/types.ts +4 -0
- package/src/util/device-id.ts +10 -10
- package/src/util/platform.ts +71 -33
- package/src/util/pricing.ts +19 -6
- package/src/util/strip-comment-lines.ts +28 -0
- package/src/workspace/git-service.ts +8 -18
- package/src/workspace/migrations/003-seed-device-id.ts +6 -4
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +7 -1
- package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -4
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +84 -0
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +94 -0
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +86 -0
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +126 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +3 -6
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/signals/confirm.ts +0 -82
- package/src/signals/trust-rule.ts +0 -174
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle generic event signals from the CLI.
|
|
3
|
+
*
|
|
4
|
+
* When the CLI writes a JSON-encoded {@link ServerMessage} to
|
|
5
|
+
* `signals/emit-event`, the daemon's ConfigWatcher detects the file
|
|
6
|
+
* change and invokes {@link handleEmitEventSignal}, which reads the
|
|
7
|
+
* payload and publishes it to connected clients via the in-process
|
|
8
|
+
* {@link assistantEventHub}.
|
|
9
|
+
*
|
|
10
|
+
* This provides a general-purpose CLI→daemon event bridge so that any
|
|
11
|
+
* CLI command can place arbitrary events onto the hub without needing
|
|
12
|
+
* a dedicated signal handler per event type.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
|
|
18
|
+
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
19
|
+
import { buildAssistantEvent } from "../runtime/assistant-event.js";
|
|
20
|
+
import { assistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
21
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
22
|
+
import { getLogger } from "../util/logger.js";
|
|
23
|
+
import { getSignalsDir } from "../util/platform.js";
|
|
24
|
+
|
|
25
|
+
const log = getLogger("signal:emit-event");
|
|
26
|
+
|
|
27
|
+
export function handleEmitEventSignal(): void {
|
|
28
|
+
try {
|
|
29
|
+
const content = readFileSync(join(getSignalsDir(), "emit-event"), "utf-8");
|
|
30
|
+
const message = JSON.parse(content) as ServerMessage;
|
|
31
|
+
|
|
32
|
+
assistantEventHub
|
|
33
|
+
.publish(buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, message))
|
|
34
|
+
.catch((err: unknown) => {
|
|
35
|
+
log.error({ err }, "Failed to publish event from signal");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
log.info({ type: message.type }, "Emit-event signal handled");
|
|
39
|
+
} catch (err) {
|
|
40
|
+
log.error({ err }, "Failed to handle emit-event signal");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -23,6 +23,18 @@ import { getSignalsDir } from "../util/platform.js";
|
|
|
23
23
|
|
|
24
24
|
const log = getLogger("signal:user-message");
|
|
25
25
|
|
|
26
|
+
// ── Attachment descriptor ───────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/** A file-backed attachment included in a signal payload. */
|
|
29
|
+
export interface SignalAttachment {
|
|
30
|
+
/** Absolute path to the file on disk. */
|
|
31
|
+
path: string;
|
|
32
|
+
/** Display filename (e.g. "f_0001.jpg"). */
|
|
33
|
+
filename: string;
|
|
34
|
+
/** MIME type (e.g. "image/jpeg"). */
|
|
35
|
+
mimeType: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
// ── Daemon callback registry ─────────────────────────────────────────
|
|
27
39
|
|
|
28
40
|
type UserMessageCallback = (params: {
|
|
@@ -31,6 +43,7 @@ type UserMessageCallback = (params: {
|
|
|
31
43
|
sourceChannel: string;
|
|
32
44
|
sourceInterface: string;
|
|
33
45
|
bypassSecretCheck?: boolean;
|
|
46
|
+
attachments?: SignalAttachment[];
|
|
34
47
|
}) => Promise<{ accepted: boolean; error?: string; message?: string }>;
|
|
35
48
|
|
|
36
49
|
let _sendUserMessage: UserMessageCallback | null = null;
|
|
@@ -99,6 +112,11 @@ export async function handleUserMessageSignal(filename: string): Promise<void> {
|
|
|
99
112
|
interface?: string;
|
|
100
113
|
requestId?: string;
|
|
101
114
|
bypassSecretCheck?: boolean;
|
|
115
|
+
attachments?: Array<{
|
|
116
|
+
path?: string;
|
|
117
|
+
filename?: string;
|
|
118
|
+
mimeType?: string;
|
|
119
|
+
}>;
|
|
102
120
|
};
|
|
103
121
|
const { requestId } = parsed;
|
|
104
122
|
parsedRequestId = requestId;
|
|
@@ -131,12 +149,31 @@ export async function handleUserMessageSignal(filename: string): Promise<void> {
|
|
|
131
149
|
return;
|
|
132
150
|
}
|
|
133
151
|
|
|
152
|
+
// Validate and normalize attachments
|
|
153
|
+
const attachments: SignalAttachment[] = [];
|
|
154
|
+
if (Array.isArray(parsed.attachments)) {
|
|
155
|
+
for (const a of parsed.attachments) {
|
|
156
|
+
if (
|
|
157
|
+
typeof a.path === "string" &&
|
|
158
|
+
typeof a.filename === "string" &&
|
|
159
|
+
typeof a.mimeType === "string"
|
|
160
|
+
) {
|
|
161
|
+
attachments.push({
|
|
162
|
+
path: a.path,
|
|
163
|
+
filename: a.filename,
|
|
164
|
+
mimeType: a.mimeType,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
134
170
|
const result = await _sendUserMessage({
|
|
135
171
|
conversationKey: parsed.conversationKey,
|
|
136
172
|
content: parsed.content,
|
|
137
173
|
sourceChannel: parsed.sourceChannel ?? "vellum",
|
|
138
174
|
sourceInterface: parsed.interface ?? "cli",
|
|
139
175
|
bypassSecretCheck: parsed.bypassSecretCheck === true,
|
|
176
|
+
...(attachments.length > 0 ? { attachments } : {}),
|
|
140
177
|
});
|
|
141
178
|
|
|
142
179
|
log.info(
|
|
@@ -49,17 +49,15 @@ mock.module("../platform/client.js", () => ({
|
|
|
49
49
|
},
|
|
50
50
|
}));
|
|
51
51
|
|
|
52
|
-
const
|
|
53
|
-
const mockGetTelemetryAppToken = mock(() => "");
|
|
52
|
+
const mockGetPlatformBaseUrl = mock(() => "https://platform.vellum.ai");
|
|
54
53
|
|
|
55
54
|
const mockGetPlatformOrganizationId = mock(() => "");
|
|
56
55
|
const mockGetPlatformUserId = mock(() => "");
|
|
57
56
|
|
|
58
57
|
mock.module("../config/env.js", () => ({
|
|
58
|
+
getPlatformBaseUrl: mockGetPlatformBaseUrl,
|
|
59
59
|
getPlatformOrganizationId: mockGetPlatformOrganizationId,
|
|
60
60
|
getPlatformUserId: mockGetPlatformUserId,
|
|
61
|
-
getTelemetryPlatformUrl: mockGetTelemetryPlatformUrl,
|
|
62
|
-
getTelemetryAppToken: mockGetTelemetryAppToken,
|
|
63
61
|
// Re-export anything else the module might import transitively
|
|
64
62
|
str: () => undefined,
|
|
65
63
|
num: () => undefined,
|
|
@@ -91,6 +89,20 @@ mock.module("../version.js", () => ({
|
|
|
91
89
|
APP_VERSION: "1.2.3-test",
|
|
92
90
|
}));
|
|
93
91
|
|
|
92
|
+
let mockCollectUsageData = true;
|
|
93
|
+
|
|
94
|
+
mock.module("../config/loader.js", () => ({
|
|
95
|
+
getConfig: () => ({ collectUsageData: mockCollectUsageData }),
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
const mockQueryUnreportedLifecycleEvents = mock(
|
|
99
|
+
() => [] as { id: string; eventName: string; createdAt: number }[],
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
mock.module("../memory/lifecycle-events-store.js", () => ({
|
|
103
|
+
queryUnreportedLifecycleEvents: mockQueryUnreportedLifecycleEvents,
|
|
104
|
+
}));
|
|
105
|
+
|
|
94
106
|
// ---------------------------------------------------------------------------
|
|
95
107
|
// Production import (after mocks)
|
|
96
108
|
// ---------------------------------------------------------------------------
|
|
@@ -135,14 +147,16 @@ let mockFetch: ReturnType<typeof mock>;
|
|
|
135
147
|
|
|
136
148
|
beforeEach(() => {
|
|
137
149
|
eventIdCounter = 0;
|
|
150
|
+
mockCollectUsageData = true;
|
|
138
151
|
mockGetMemoryCheckpoint.mockReset();
|
|
139
152
|
mockSetMemoryCheckpoint.mockReset();
|
|
140
153
|
mockQueryUnreportedUsageEvents.mockReset();
|
|
141
154
|
mockQueryUnreportedTurnEvents.mockReset();
|
|
142
155
|
mockQueryUnreportedTurnEvents.mockReturnValue([]);
|
|
156
|
+
mockQueryUnreportedLifecycleEvents.mockReset();
|
|
157
|
+
mockQueryUnreportedLifecycleEvents.mockReturnValue([]);
|
|
143
158
|
mockPlatformClient = null;
|
|
144
|
-
|
|
145
|
-
mockGetTelemetryAppToken.mockReset();
|
|
159
|
+
mockGetPlatformBaseUrl.mockReset();
|
|
146
160
|
mockGetDeviceId.mockReset();
|
|
147
161
|
mockGetDeviceId.mockReturnValue("test-device-id");
|
|
148
162
|
mockGetExternalAssistantId.mockReset();
|
|
@@ -154,8 +168,7 @@ beforeEach(() => {
|
|
|
154
168
|
|
|
155
169
|
// Defaults
|
|
156
170
|
mockGetMemoryCheckpoint.mockReturnValue(null);
|
|
157
|
-
|
|
158
|
-
mockGetTelemetryAppToken.mockReturnValue("default-test-token");
|
|
171
|
+
mockGetPlatformBaseUrl.mockReturnValue("https://platform.vellum.ai");
|
|
159
172
|
|
|
160
173
|
mockFetch = mock(() =>
|
|
161
174
|
Promise.resolve(new Response('{"accepted":0}', { status: 200 })),
|
|
@@ -195,10 +208,9 @@ describe("UsageTelemetryReporter", () => {
|
|
|
195
208
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
196
209
|
});
|
|
197
210
|
|
|
198
|
-
test("anonymous flush
|
|
211
|
+
test("anonymous flush sends request without auth headers", async () => {
|
|
199
212
|
mockPlatformClient = null;
|
|
200
|
-
|
|
201
|
-
mockGetTelemetryAppToken.mockReturnValue("anon-token");
|
|
213
|
+
mockGetPlatformBaseUrl.mockReturnValue("https://platform.test.ai");
|
|
202
214
|
|
|
203
215
|
const events = [makeUsageEvent()];
|
|
204
216
|
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
@@ -213,9 +225,9 @@ describe("UsageTelemetryReporter", () => {
|
|
|
213
225
|
const [url, opts] = mockFetch.mock.calls[0] as [string, RequestInit];
|
|
214
226
|
expect(url).toStartWith("https://platform.test.ai");
|
|
215
227
|
expect(url).toEndWith("/telemetry/ingest/");
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
);
|
|
228
|
+
const headers = opts.headers as Record<string, string>;
|
|
229
|
+
expect(headers["Content-Type"]).toBe("application/json");
|
|
230
|
+
expect(headers["X-Telemetry-Token"]).toBeUndefined();
|
|
219
231
|
});
|
|
220
232
|
|
|
221
233
|
test("watermark advances on successful upload", async () => {
|
|
@@ -279,7 +291,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
279
291
|
const body1 = JSON.parse(
|
|
280
292
|
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
281
293
|
);
|
|
282
|
-
expect(body1.
|
|
294
|
+
expect(body1.device_id).toBe("test-device-id");
|
|
283
295
|
|
|
284
296
|
// Second flush — should use the same value
|
|
285
297
|
mockQueryUnreportedUsageEvents.mockReturnValue([makeUsageEvent()]);
|
|
@@ -288,7 +300,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
288
300
|
const body2 = JSON.parse(
|
|
289
301
|
(mockFetch.mock.calls[1] as [string, RequestInit])[1].body as string,
|
|
290
302
|
);
|
|
291
|
-
expect(body2.
|
|
303
|
+
expect(body2.device_id).toBe("test-device-id");
|
|
292
304
|
});
|
|
293
305
|
|
|
294
306
|
test("empty batch makes no HTTP call", async () => {
|
|
@@ -362,8 +374,8 @@ describe("UsageTelemetryReporter", () => {
|
|
|
362
374
|
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
363
375
|
);
|
|
364
376
|
|
|
365
|
-
// Top-level:
|
|
366
|
-
expect(body.
|
|
377
|
+
// Top-level: device_id, assistant_version, and events array (no turn_events key)
|
|
378
|
+
expect(body.device_id).toBe("test-device-id");
|
|
367
379
|
expect(body.assistant_version).toBe("1.2.3-test");
|
|
368
380
|
expect(Array.isArray(body.events)).toBe(true);
|
|
369
381
|
expect(body.events.length).toBe(1);
|
|
@@ -437,7 +449,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
437
449
|
const body = JSON.parse(
|
|
438
450
|
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
439
451
|
);
|
|
440
|
-
expect(body.
|
|
452
|
+
expect(body.device_id).toBe("test-device-id");
|
|
441
453
|
expect(body.assistant_id).toBe(DAEMON_INTERNAL_ASSISTANT_ID);
|
|
442
454
|
});
|
|
443
455
|
|
|
@@ -477,4 +489,56 @@ describe("UsageTelemetryReporter", () => {
|
|
|
477
489
|
expect(turnEvent.daemon_event_id).toBe("evt-mixed-turn");
|
|
478
490
|
expect(turnEvent.recorded_at).toBe(1700000050000);
|
|
479
491
|
});
|
|
492
|
+
|
|
493
|
+
test("flush is skipped and watermarks advanced when collectUsageData is false", async () => {
|
|
494
|
+
mockCollectUsageData = false;
|
|
495
|
+
const events = [makeUsageEvent()];
|
|
496
|
+
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
497
|
+
mockFetch.mockImplementation(() =>
|
|
498
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
const reporter = new UsageTelemetryReporter();
|
|
502
|
+
await reporter.flush();
|
|
503
|
+
|
|
504
|
+
// No HTTP call should have been made
|
|
505
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
506
|
+
|
|
507
|
+
// All 6 watermarks should have been advanced (3 timestamps + 3 IDs)
|
|
508
|
+
expect(mockSetMemoryCheckpoint).toHaveBeenCalledTimes(6);
|
|
509
|
+
|
|
510
|
+
const calls = mockSetMemoryCheckpoint.mock.calls;
|
|
511
|
+
const keys = calls.map((c) => c[0]);
|
|
512
|
+
expect(keys).toContain("telemetry:usage:last_reported_at");
|
|
513
|
+
expect(keys).toContain("telemetry:usage:last_reported_id");
|
|
514
|
+
expect(keys).toContain("telemetry:turns:last_reported_at");
|
|
515
|
+
expect(keys).toContain("telemetry:turns:last_reported_id");
|
|
516
|
+
expect(keys).toContain("telemetry:lifecycle:last_reported_at");
|
|
517
|
+
expect(keys).toContain("telemetry:lifecycle:last_reported_id");
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test("events sent normally after re-enabling collectUsageData", async () => {
|
|
521
|
+
// First flush with opt-out — watermarks advance, nothing sent
|
|
522
|
+
mockCollectUsageData = false;
|
|
523
|
+
const reporter = new UsageTelemetryReporter();
|
|
524
|
+
await reporter.flush();
|
|
525
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
526
|
+
mockSetMemoryCheckpoint.mockReset();
|
|
527
|
+
|
|
528
|
+
// Re-enable and flush with new events
|
|
529
|
+
mockCollectUsageData = true;
|
|
530
|
+
const events = [makeUsageEvent({ id: "evt-after-reenable" })];
|
|
531
|
+
mockQueryUnreportedUsageEvents.mockReturnValue(events);
|
|
532
|
+
mockFetch.mockImplementation(() =>
|
|
533
|
+
Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
await reporter.flush();
|
|
537
|
+
|
|
538
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
539
|
+
const body = JSON.parse(
|
|
540
|
+
(mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
541
|
+
);
|
|
542
|
+
expect(body.events[0].daemon_event_id).toBe("evt-after-reenable");
|
|
543
|
+
});
|
|
480
544
|
});
|
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Two auth modes:
|
|
8
8
|
* - Authenticated: Api-Key header via managed proxy context
|
|
9
|
-
* - Anonymous:
|
|
9
|
+
* - Anonymous: unauthenticated POST (telemetry endpoints are public)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
|
+
getPlatformBaseUrl,
|
|
13
14
|
getPlatformOrganizationId,
|
|
14
15
|
getPlatformUserId,
|
|
15
|
-
getTelemetryAppToken,
|
|
16
|
-
getTelemetryPlatformUrl,
|
|
17
16
|
} from "../config/env.js";
|
|
17
|
+
import { getConfig } from "../config/loader.js";
|
|
18
18
|
import {
|
|
19
19
|
getMemoryCheckpoint,
|
|
20
20
|
setMemoryCheckpoint,
|
|
@@ -93,6 +93,22 @@ export class UsageTelemetryReporter {
|
|
|
93
93
|
try {
|
|
94
94
|
if (batchCount >= MAX_CONSECUTIVE_BATCHES) return;
|
|
95
95
|
|
|
96
|
+
// Respect runtime opt-out: if the user has disabled usage data collection,
|
|
97
|
+
// skip the flush and advance watermarks so events recorded during the
|
|
98
|
+
// opt-out window are never sent retroactively.
|
|
99
|
+
if (!getConfig().collectUsageData) {
|
|
100
|
+
// Advance only the timestamp watermarks. Leave the ID watermarks
|
|
101
|
+
// untouched so the compound-cursor branch stays active — setting them
|
|
102
|
+
// to "" would make the truthy check fail, falling back to a
|
|
103
|
+
// timestamp-only `gt(createdAt, watermark)` query that silently drops
|
|
104
|
+
// events created in the same millisecond as the opt-out watermark.
|
|
105
|
+
const now = String(Date.now());
|
|
106
|
+
setMemoryCheckpoint(CHECKPOINT_KEY_WATERMARK, now);
|
|
107
|
+
setMemoryCheckpoint(CHECKPOINT_KEY_TURN_WATERMARK, now);
|
|
108
|
+
setMemoryCheckpoint(CHECKPOINT_KEY_LIFECYCLE_WATERMARK, now);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
96
112
|
// Read usage watermark (compound cursor: createdAt + id)
|
|
97
113
|
const watermark = Number(
|
|
98
114
|
getMemoryCheckpoint(CHECKPOINT_KEY_WATERMARK) ?? "0",
|
|
@@ -138,11 +154,9 @@ export class UsageTelemetryReporter {
|
|
|
138
154
|
)
|
|
139
155
|
return;
|
|
140
156
|
|
|
141
|
-
// Resolve auth context —
|
|
157
|
+
// Resolve auth context — authenticated path uses client, anonymous path
|
|
158
|
+
// sends unauthenticated (telemetry endpoints are public).
|
|
142
159
|
const client = await VellumPlatformClient.create();
|
|
143
|
-
if (!client && (!getTelemetryAppToken() || !getTelemetryPlatformUrl())) {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
160
|
|
|
147
161
|
// Build payload
|
|
148
162
|
const typedEvents: TelemetryEvent[] = [
|
|
@@ -203,16 +217,8 @@ export class UsageTelemetryReporter {
|
|
|
203
217
|
if (client) {
|
|
204
218
|
resp = await client.fetch(TELEMETRY_PATH, fetchInit);
|
|
205
219
|
} else {
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
const url = `${platformUrl}${TELEMETRY_PATH}`;
|
|
209
|
-
resp = await fetch(url, {
|
|
210
|
-
...fetchInit,
|
|
211
|
-
headers: {
|
|
212
|
-
"Content-Type": "application/json",
|
|
213
|
-
"X-Telemetry-Token": getTelemetryAppToken(),
|
|
214
|
-
},
|
|
215
|
-
});
|
|
220
|
+
const url = `${getPlatformBaseUrl()}${TELEMETRY_PATH}`;
|
|
221
|
+
resp = await fetch(url, fetchInit);
|
|
216
222
|
}
|
|
217
223
|
|
|
218
224
|
if (!resp.ok) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { getExternalDir } from "../../util/platform.js";
|
|
5
5
|
|
|
6
6
|
export interface BrowserRuntimeStatus {
|
|
7
7
|
playwrightAvailable: boolean;
|
|
@@ -75,7 +75,7 @@ export async function importPlaywright(): Promise<typeof import("playwright")> {
|
|
|
75
75
|
// filesystem instead of the compiled module cache.
|
|
76
76
|
// Use the internal assistant root (outside tool sandbox working dir)
|
|
77
77
|
// so untrusted workspace writes cannot plant a forged playwright package.
|
|
78
|
-
const externalDir =
|
|
78
|
+
const externalDir = getExternalDir();
|
|
79
79
|
const pwPkg = join(externalDir, "node_modules", "playwright");
|
|
80
80
|
|
|
81
81
|
if (!existsSync(join(pwPkg, "package.json"))) {
|
|
@@ -3,25 +3,16 @@ import {
|
|
|
3
3
|
setSlackChannelConfig,
|
|
4
4
|
type SlackChannelConfigResult,
|
|
5
5
|
} from "../../daemon/handlers/config-slack-channel.js";
|
|
6
|
-
import { orchestrateOAuthConnect } from "../../oauth/connect-orchestrator.js";
|
|
7
6
|
import { syncManualTokenConnection } from "../../oauth/manual-token-connection.js";
|
|
8
7
|
import {
|
|
9
8
|
disconnectOAuthProvider,
|
|
10
9
|
getActiveConnection,
|
|
11
|
-
getAppByProviderAndClientId,
|
|
12
|
-
getMostRecentAppByProvider,
|
|
13
|
-
getProvider,
|
|
14
|
-
listProviders,
|
|
15
10
|
} from "../../oauth/oauth-store.js";
|
|
16
11
|
import { RiskLevel } from "../../permissions/types.js";
|
|
17
12
|
import type { ToolDefinition } from "../../providers/types.js";
|
|
18
|
-
import { buildAssistantEvent } from "../../runtime/assistant-event.js";
|
|
19
|
-
import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
|
|
20
|
-
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../runtime/assistant-scope.js";
|
|
21
13
|
import { credentialKey } from "../../security/credential-key.js";
|
|
22
14
|
import {
|
|
23
15
|
deleteSecureKeyAsync,
|
|
24
|
-
getSecureKeyAsync,
|
|
25
16
|
listSecureKeysAsync,
|
|
26
17
|
setSecureKeyAsync,
|
|
27
18
|
} from "../../security/secure-keys.js";
|
|
@@ -90,16 +81,9 @@ class CredentialStoreTool implements Tool {
|
|
|
90
81
|
properties: {
|
|
91
82
|
action: {
|
|
92
83
|
type: "string",
|
|
93
|
-
enum: [
|
|
94
|
-
"store",
|
|
95
|
-
"list",
|
|
96
|
-
"delete",
|
|
97
|
-
"prompt",
|
|
98
|
-
"oauth2_connect",
|
|
99
|
-
"describe",
|
|
100
|
-
],
|
|
84
|
+
enum: ["store", "list", "delete", "prompt"],
|
|
101
85
|
description:
|
|
102
|
-
'The operation to perform. Use "prompt" to ask the user for a secret via secure UI - the value never enters the conversation.
|
|
86
|
+
'The operation to perform. Use "prompt" to ask the user for a secret via secure UI - the value never enters the conversation.',
|
|
103
87
|
},
|
|
104
88
|
service: {
|
|
105
89
|
type: "string",
|
|
@@ -150,22 +134,6 @@ class CredentialStoreTool implements Tool {
|
|
|
150
134
|
description:
|
|
151
135
|
'Human-readable description of intended usage (for store/prompt actions), e.g. "GitHub login for pushing changes"',
|
|
152
136
|
},
|
|
153
|
-
scopes: {
|
|
154
|
-
type: "array",
|
|
155
|
-
items: { type: "string" },
|
|
156
|
-
description:
|
|
157
|
-
"OAuth2 scopes to request (only for oauth2_connect action). Auto-filled for well-known services (google, slack).",
|
|
158
|
-
},
|
|
159
|
-
client_id: {
|
|
160
|
-
type: "string",
|
|
161
|
-
description:
|
|
162
|
-
"OAuth2 client ID (only for oauth2_connect action). If omitted, looked up from previously stored credentials.",
|
|
163
|
-
},
|
|
164
|
-
client_secret: {
|
|
165
|
-
type: "string",
|
|
166
|
-
description:
|
|
167
|
-
"OAuth2 client secret for providers that require it (e.g. Google, Slack). If omitted, looked up from previously stored credentials; if still absent, PKCE-only is used (only for oauth2_connect action)",
|
|
168
|
-
},
|
|
169
137
|
alias: {
|
|
170
138
|
type: "string",
|
|
171
139
|
description:
|
|
@@ -817,221 +785,6 @@ class CredentialStoreTool implements Tool {
|
|
|
817
785
|
};
|
|
818
786
|
}
|
|
819
787
|
|
|
820
|
-
case "oauth2_connect": {
|
|
821
|
-
const service = input.service as string | undefined;
|
|
822
|
-
if (!service)
|
|
823
|
-
return {
|
|
824
|
-
content: "Error: service is required for oauth2_connect action",
|
|
825
|
-
isError: true,
|
|
826
|
-
};
|
|
827
|
-
|
|
828
|
-
// Protocol-level config from the DB (authUrl, tokenUrl, scopes, etc.)
|
|
829
|
-
const providerRow = getProvider(service);
|
|
830
|
-
|
|
831
|
-
// Resolve client_id and client_secret.
|
|
832
|
-
// Priority:
|
|
833
|
-
// 1. Explicit input from the caller
|
|
834
|
-
// 2. oauth-store DB - when clientId is already known, look up the
|
|
835
|
-
// matching app so the secret comes from the same app. Only fall
|
|
836
|
-
// back to the most-recent-app heuristic when clientId is unknown.
|
|
837
|
-
let clientId = input.client_id as string | undefined;
|
|
838
|
-
let clientSecret = input.client_secret as string | undefined;
|
|
839
|
-
|
|
840
|
-
if (!clientId || !clientSecret) {
|
|
841
|
-
const dbApp = clientId
|
|
842
|
-
? getAppByProviderAndClientId(service, clientId)
|
|
843
|
-
: getMostRecentAppByProvider(service);
|
|
844
|
-
if (dbApp) {
|
|
845
|
-
if (!clientId) clientId = dbApp.clientId;
|
|
846
|
-
if (!clientSecret) {
|
|
847
|
-
clientSecret = await getSecureKeyAsync(
|
|
848
|
-
dbApp.clientSecretCredentialPath,
|
|
849
|
-
);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
// Early guardrails that stay in vault.ts (credential resolution is vault-specific)
|
|
855
|
-
const inputScopes = input.scopes as string[] | undefined;
|
|
856
|
-
|
|
857
|
-
if (!providerRow) {
|
|
858
|
-
return {
|
|
859
|
-
content: `Error: no OAuth provider registered for "${service}". Ensure the provider is seeded in the database.`,
|
|
860
|
-
isError: true,
|
|
861
|
-
};
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
if (!clientId)
|
|
865
|
-
return {
|
|
866
|
-
content:
|
|
867
|
-
"Error: client_id is required for oauth2_connect action. Provide it directly or store it first with credential_store.",
|
|
868
|
-
isError: true,
|
|
869
|
-
};
|
|
870
|
-
|
|
871
|
-
// Fail early when client_secret is required but missing - guide the
|
|
872
|
-
// agent to collect it from the user rather than letting it improvise
|
|
873
|
-
// browser-automation workarounds that inevitably fail.
|
|
874
|
-
const requiresSecret = !!providerRow.requiresClientSecret;
|
|
875
|
-
if (requiresSecret && !clientSecret) {
|
|
876
|
-
return {
|
|
877
|
-
content: `Error: client_secret is required for ${service} but not found in the vault.\n\nUse credential_store with action "prompt" to securely collect the client_secret from the user before calling oauth2_connect again.`,
|
|
878
|
-
isError: true,
|
|
879
|
-
};
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
// Delegate to the shared orchestrator - it resolves authUrl, tokenUrl,
|
|
883
|
-
// extraParams, userinfoUrl, and tokenEndpointAuthMethod from the DB.
|
|
884
|
-
const result = await orchestrateOAuthConnect({
|
|
885
|
-
service,
|
|
886
|
-
clientId,
|
|
887
|
-
clientSecret,
|
|
888
|
-
isInteractive: !!context.isInteractive,
|
|
889
|
-
sendToClient: context.sendToClient,
|
|
890
|
-
...(inputScopes ? { requestedScopes: inputScopes } : {}),
|
|
891
|
-
onDeferredComplete: (deferredResult) => {
|
|
892
|
-
// Emit oauth_connect_result to all connected SSE clients so the
|
|
893
|
-
// UI can update immediately when the deferred browser flow completes.
|
|
894
|
-
assistantEventHub
|
|
895
|
-
.publish(
|
|
896
|
-
buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, {
|
|
897
|
-
type: "oauth_connect_result",
|
|
898
|
-
success: deferredResult.success,
|
|
899
|
-
service: deferredResult.service,
|
|
900
|
-
accountInfo: deferredResult.accountInfo,
|
|
901
|
-
error: deferredResult.error,
|
|
902
|
-
}),
|
|
903
|
-
)
|
|
904
|
-
.catch((err) => {
|
|
905
|
-
log.warn(
|
|
906
|
-
{ err, service: deferredResult.service },
|
|
907
|
-
"Failed to publish oauth_connect_result event",
|
|
908
|
-
);
|
|
909
|
-
});
|
|
910
|
-
|
|
911
|
-
if (deferredResult.success) {
|
|
912
|
-
log.info(
|
|
913
|
-
{
|
|
914
|
-
service: deferredResult.service,
|
|
915
|
-
accountInfo: deferredResult.accountInfo,
|
|
916
|
-
},
|
|
917
|
-
"Deferred OAuth connect completed successfully",
|
|
918
|
-
);
|
|
919
|
-
} else {
|
|
920
|
-
log.warn(
|
|
921
|
-
{
|
|
922
|
-
service: deferredResult.service,
|
|
923
|
-
err: deferredResult.error,
|
|
924
|
-
},
|
|
925
|
-
"Deferred OAuth connect failed",
|
|
926
|
-
);
|
|
927
|
-
}
|
|
928
|
-
},
|
|
929
|
-
});
|
|
930
|
-
|
|
931
|
-
if (!result.success) {
|
|
932
|
-
return { content: `Error: ${result.error}`, isError: true };
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
if (result.deferred) {
|
|
936
|
-
return {
|
|
937
|
-
content: `To connect ${service}, open this link and authorize access:\n\n${result.authUrl}\n\nOnce you authorize, the connection will be set up automatically. You can verify by asking me to check your inbox.`,
|
|
938
|
-
isError: false,
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
return {
|
|
943
|
-
content: `Successfully connected "${service}"${
|
|
944
|
-
result.accountInfo ? ` as ${result.accountInfo}` : ""
|
|
945
|
-
}. The service is now ready to use.`,
|
|
946
|
-
isError: false,
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
case "describe": {
|
|
951
|
-
const descService = (input.service as string | undefined) ?? "";
|
|
952
|
-
if (!descService) {
|
|
953
|
-
return {
|
|
954
|
-
content: "Error: service is required for describe action",
|
|
955
|
-
isError: true,
|
|
956
|
-
};
|
|
957
|
-
}
|
|
958
|
-
const descProviderRow = getProvider(descService);
|
|
959
|
-
if (!descProviderRow) {
|
|
960
|
-
const availableServices = listProviders().map((p) => p.providerKey);
|
|
961
|
-
return {
|
|
962
|
-
content: `No well-known OAuth config found for "${descService}". Available services: ${availableServices.join(", ")}`,
|
|
963
|
-
isError: false,
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
// Compute the redirect URI based on callback transport
|
|
968
|
-
let redirectUri: string;
|
|
969
|
-
const transport =
|
|
970
|
-
(descProviderRow.callbackTransport as
|
|
971
|
-
| "loopback"
|
|
972
|
-
| "gateway"
|
|
973
|
-
| null) ?? "loopback";
|
|
974
|
-
const loopbackPort = descProviderRow.loopbackPort;
|
|
975
|
-
if (transport === "loopback" && loopbackPort) {
|
|
976
|
-
redirectUri = `http://localhost:${loopbackPort}/oauth/callback`;
|
|
977
|
-
} else if (transport === "loopback") {
|
|
978
|
-
redirectUri =
|
|
979
|
-
"(automatic - no redirect URI needed, uses random localhost port)";
|
|
980
|
-
} else {
|
|
981
|
-
// Try to compute the actual URL from config/env
|
|
982
|
-
try {
|
|
983
|
-
const { loadConfig } = await import("../../config/loader.js");
|
|
984
|
-
const { getPublicBaseUrl } =
|
|
985
|
-
await import("../../inbound/public-ingress-urls.js");
|
|
986
|
-
const baseUrl = getPublicBaseUrl(loadConfig());
|
|
987
|
-
redirectUri = `${baseUrl}/webhooks/oauth/callback`;
|
|
988
|
-
} catch {
|
|
989
|
-
redirectUri =
|
|
990
|
-
"(requires ingress.publicBaseUrl - not currently configured)";
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
const requiresClientSecret = !!descProviderRow.requiresClientSecret;
|
|
995
|
-
|
|
996
|
-
const descDefaultScopes: string[] = descProviderRow.defaultScopes
|
|
997
|
-
? JSON.parse(descProviderRow.defaultScopes)
|
|
998
|
-
: [];
|
|
999
|
-
|
|
1000
|
-
const info: Record<string, unknown> = {
|
|
1001
|
-
service: descService,
|
|
1002
|
-
authUrl: descProviderRow.authUrl,
|
|
1003
|
-
tokenUrl: descProviderRow.tokenUrl,
|
|
1004
|
-
scopes: descDefaultScopes,
|
|
1005
|
-
callbackTransport: transport,
|
|
1006
|
-
redirectUri,
|
|
1007
|
-
requiresClientSecret,
|
|
1008
|
-
};
|
|
1009
|
-
if (
|
|
1010
|
-
descProviderRow.displayName &&
|
|
1011
|
-
descProviderRow.dashboardUrl &&
|
|
1012
|
-
descProviderRow.appType
|
|
1013
|
-
) {
|
|
1014
|
-
info.setup = {
|
|
1015
|
-
displayName: descProviderRow.displayName,
|
|
1016
|
-
dashboardUrl: descProviderRow.dashboardUrl,
|
|
1017
|
-
appType: descProviderRow.appType,
|
|
1018
|
-
requiresClientSecret: !!descProviderRow.requiresClientSecret,
|
|
1019
|
-
...(descProviderRow.setupNotes
|
|
1020
|
-
? { notes: JSON.parse(descProviderRow.setupNotes) }
|
|
1021
|
-
: {}),
|
|
1022
|
-
};
|
|
1023
|
-
}
|
|
1024
|
-
if (descProviderRow.extraParams) {
|
|
1025
|
-
try {
|
|
1026
|
-
info.extraParams = JSON.parse(descProviderRow.extraParams);
|
|
1027
|
-
} catch {
|
|
1028
|
-
// Non-fatal
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
return { content: JSON.stringify(info, null, 2), isError: false };
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
788
|
default:
|
|
1036
789
|
return { content: `Error: unknown action "${action}"`, isError: true };
|
|
1037
790
|
}
|