@vellumai/assistant 0.9.0 → 0.10.0-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +18 -34
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +28 -22
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +976 -63
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/access-request-card-view.test.ts +98 -0
- package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
- package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-compiler.test.ts +15 -1
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/cancel-clears-processing.test.ts +89 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -4
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
- package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
- package/src/__tests__/config-loader-backfill.test.ts +268 -27
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +0 -6
- package/src/__tests__/contacts-tools.test.ts +29 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +58 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-lifecycle.test.ts +7 -9
- package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
- package/src/__tests__/conversation-title-service.test.ts +62 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +4 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +4 -13
- package/src/__tests__/disk-pressure-policy.test.ts +12 -0
- package/src/__tests__/disk-usage.test.ts +65 -0
- package/src/__tests__/dynamic-page-surface.test.ts +152 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-flag-listener.test.ts +110 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +3 -35
- package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/headless-browser-mode.test.ts +10 -0
- package/src/__tests__/headless-browser-navigate.test.ts +8 -3
- package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
- package/src/__tests__/host-browser-proxy.test.ts +87 -0
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/injector-v3-suppression.test.ts +27 -20
- package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
- package/src/__tests__/invite-redemption-service.test.ts +4 -7
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +30 -23
- package/src/__tests__/llm-resolver.test.ts +70 -24
- package/src/__tests__/llm-schema.test.ts +1 -0
- package/src/__tests__/managed-profile-guard.test.ts +163 -4
- package/src/__tests__/mcp-health-check.test.ts +6 -7
- package/src/__tests__/media-stream-server-integration.test.ts +317 -13
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/path-policy.test.ts +34 -0
- package/src/__tests__/persona-resolver.test.ts +49 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/plugin-api-provider.test.ts +24 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
- package/src/__tests__/reaction-persistence.test.ts +150 -29
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/relay-server.test.ts +285 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
- package/src/__tests__/skill-execute-input.test.ts +51 -1
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/skills.test.ts +51 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
- package/src/__tests__/subagent-tools.test.ts +266 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +100 -3
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
- package/src/__tests__/tool-audit-listener.test.ts +7 -7
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +8 -10
- package/src/__tests__/twilio-routes.test.ts +81 -1
- package/src/__tests__/voice-invite-redemption.test.ts +2 -3
- package/src/__tests__/weak-open-model.test.ts +30 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
- package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
- package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
- package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
- package/src/agent/loop.ts +49 -29
- package/src/api/README.md +6 -6
- package/src/api/events/tool-result.ts +6 -0
- package/src/api/events/workflow-completed.ts +53 -0
- package/src/api/events/workflow-leaf-finished.ts +38 -0
- package/src/api/events/workflow-leaf-started.ts +35 -0
- package/src/api/events/workflow-progress.ts +32 -0
- package/src/api/events/workflow-started.ts +31 -0
- package/src/api/index.ts +40 -0
- package/src/api/responses/conversation-message.ts +28 -4
- package/src/api/responses/home.ts +26 -4
- package/src/api/responses/workflow-journal.ts +53 -0
- package/src/approvals/guardian-card-withdrawal.ts +145 -0
- package/src/approvals/guardian-decision-primitive.ts +26 -3
- package/src/approvals/guardian-request-resolvers.ts +183 -80
- package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
- package/src/calls/call-pointer-messages.ts +10 -4
- package/src/calls/channel-admission-reader.ts +104 -0
- package/src/calls/guardian-dispatch.ts +17 -45
- package/src/calls/media-stream-server.ts +84 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/relay-server.ts +66 -0
- package/src/calls/relay-setup-router.ts +82 -1
- package/src/calls/twilio-routes.ts +17 -8
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/clients.ts +3 -0
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
- package/src/cli/commands/memory/index.ts +30 -0
- package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
- package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
- package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
- package/src/cli/commands/oauth/status.test.ts +36 -0
- package/src/cli/commands/oauth/status.ts +23 -3
- package/src/cli/commands/plugins.ts +197 -4
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/inspect-plugin.ts +12 -1
- package/src/cli/lib/install-from-github.ts +105 -17
- package/src/cli/lib/merge-plugin-tree.ts +328 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/plugin-surfaces.ts +104 -0
- package/src/cli/lib/upgrade-plugin.ts +298 -10
- package/src/cli/program.ts +2 -6
- package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
- package/src/config/assistant-feature-flags.ts +22 -7
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
- package/src/config/bundled-skills/messaging/SKILL.md +6 -4
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/workflows/SKILL.md +14 -8
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +15 -2
- package/src/config/feature-flag-registry.json +46 -31
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/llm-resolver.ts +3 -0
- package/src/config/loader.ts +4 -0
- package/src/config/memory-v3-gate.ts +11 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schema.ts +8 -6
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/channels.ts +11 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/llm.ts +31 -0
- package/src/config/schemas/memory-lifecycle.ts +3 -7
- package/src/config/schemas/memory-v3.ts +6 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/services.ts +18 -0
- package/src/config/seed-inference-profiles.ts +109 -44
- package/src/config/skills.ts +21 -0
- package/src/config/sync-gated-profiles.ts +220 -0
- package/src/contacts/contact-store.ts +89 -106
- package/src/contacts/contacts-write.ts +5 -22
- package/src/contacts/types.ts +0 -1
- package/src/context/compactor.ts +88 -54
- package/src/context/strip-injections.ts +58 -10
- package/src/context/token-estimator.ts +1 -1
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
- package/src/daemon/conversation-agent-loop.ts +114 -22
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +3 -5
- package/src/daemon/conversation-process.ts +13 -5
- package/src/daemon/conversation-runtime-assembly.ts +13 -15
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-surfaces.ts +26 -0
- package/src/daemon/conversation-tool-setup.ts +27 -14
- package/src/daemon/conversation.ts +66 -14
- package/src/daemon/disk-pressure-policy.ts +5 -3
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
- package/src/daemon/handlers/config-a2a.ts +0 -2
- package/src/daemon/handlers/config-channels.ts +15 -16
- package/src/daemon/handlers/config-slack-channel.ts +22 -3
- package/src/daemon/handlers/conversations.ts +107 -0
- package/src/daemon/host-browser-proxy.ts +41 -0
- package/src/daemon/lifecycle.ts +55 -27
- package/src/daemon/message-provenance.ts +2 -0
- package/src/daemon/message-types/contacts.ts +0 -1
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/message-types/web-activity.ts +7 -1
- package/src/daemon/message-types/workflows.ts +83 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/daemon/tool-setup-types.ts +4 -0
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +2 -2
- package/src/home/feed-source-enrichment.test.ts +151 -0
- package/src/home/feed-source-enrichment.ts +176 -0
- package/src/home/relationship-state.ts +2 -4
- package/src/instrument.ts +18 -6
- package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
- package/src/ipc/assistant-server.ts +37 -4
- package/src/ipc/gateway-flag-listener.ts +18 -2
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
- package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
- package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
- package/src/memory/auth-fallback-events-store.ts +2 -2
- package/src/memory/auto-analysis-enqueue.ts +3 -5
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/canonical-guardian-store.ts +39 -1
- package/src/memory/conversation-crud.ts +9 -4
- package/src/memory/conversation-key-store.ts +17 -2
- package/src/memory/conversation-title-service.ts +64 -7
- package/src/memory/db-init.ts +17 -17
- package/src/memory/embedding-backend.ts +38 -1
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +54 -1
- package/src/memory/lifecycle-events-store.ts +2 -2
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-enqueue.ts +31 -6
- package/src/memory/memory-retrospective-job.ts +28 -227
- package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
- package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
- package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
- package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
- package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/onboarding-events-store.ts +3 -3
- package/src/memory/schema/contacts.ts +0 -5
- package/src/memory/skill-loaded-events-store.test.ts +7 -15
- package/src/memory/skill-loaded-events-store.ts +2 -2
- package/src/memory/tool-executed-events-store.test.ts +7 -7
- package/src/memory/turn-trace-store.test.ts +736 -0
- package/src/memory/turn-trace-store.ts +364 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
- package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
- package/src/memory/v2/consolidation-job.ts +2 -2
- package/src/memory/v2/skill-content.ts +25 -7
- package/src/memory/v2/skill-store.ts +7 -1
- package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
- package/src/memory/v3-eval/eval-packets.ts +546 -0
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/messaging/providers/slack/api.ts +31 -0
- package/src/messaging/providers/slack/send.test.ts +114 -2
- package/src/messaging/providers/slack/send.ts +30 -7
- package/src/messaging/providers/slack/withdraw.test.ts +200 -0
- package/src/messaging/providers/slack/withdraw.ts +161 -0
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/access-request-copy.ts +72 -59
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +58 -103
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/approval-card-data.ts +333 -0
- package/src/notifications/broadcaster.ts +16 -3
- package/src/notifications/canonical-delivery-recorder.ts +139 -0
- package/src/notifications/copy-composer.ts +3 -3
- package/src/notifications/decision-engine.ts +4 -2
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/guardian-question-mode.ts +10 -0
- package/src/notifications/home-feed-side-effect.ts +7 -16
- package/src/notifications/notification-utils.ts +19 -20
- package/src/notifications/signal.ts +79 -43
- package/src/notifications/types.ts +98 -121
- package/src/oauth/AGENTS.md +5 -24
- package/src/permissions/checker.test.ts +51 -0
- package/src/permissions/checker.ts +185 -26
- package/src/permissions/ipc-risk-types.ts +24 -0
- package/src/permissions/question-prompter.test.ts +27 -0
- package/src/permissions/question-prompter.ts +4 -0
- package/src/platform/client.test.ts +119 -0
- package/src/platform/client.ts +66 -0
- package/src/platform/consent-cache.test.ts +267 -0
- package/src/platform/consent-cache.ts +174 -0
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +33 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
- package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
- package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
- package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
- package/src/plugins/defaults/advisor/config.ts +21 -0
- package/src/plugins/defaults/advisor/consult.ts +93 -0
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/advisor/package.json +14 -0
- package/src/plugins/defaults/advisor/steering.ts +67 -0
- package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
- package/src/plugins/defaults/advisor/transcript.ts +76 -0
- package/src/plugins/defaults/index.ts +60 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
- package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
- package/src/prompts/persona-resolver.ts +14 -4
- package/src/prompts/templates/system-sections.ts +7 -2
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +176 -0
- package/src/providers/atlascloud/client.ts +85 -0
- package/src/providers/fetch-provider-catalog.ts +85 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/model-catalog.ts +58 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
- package/src/providers/openai/chat-completions-provider.ts +7 -0
- package/src/providers/openai/responses-provider.ts +10 -0
- package/src/providers/provider-send-message.ts +11 -3
- package/src/providers/retry.ts +53 -12
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/weak-open-model.ts +22 -0
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/__tests__/client-health.test.ts +44 -0
- package/src/runtime/access-request-helper.ts +21 -53
- package/src/runtime/actor-trust-resolver.ts +59 -63
- package/src/runtime/agent-wake.ts +52 -0
- package/src/runtime/assistant-event-hub.ts +18 -4
- package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/capabilities.test.ts +120 -0
- package/src/runtime/capabilities.ts +197 -0
- package/src/runtime/channel-approval-types.ts +22 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/channel-verification-service.ts +3 -3
- package/src/runtime/client-health.ts +26 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
- package/src/runtime/effective-capabilities.test.ts +128 -0
- package/src/runtime/effective-capabilities.ts +84 -0
- package/src/runtime/guardian-reply-router.ts +106 -21
- package/src/runtime/invite-redemption-service.ts +9 -25
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
- package/src/runtime/migrations/vbundle-builder.ts +49 -20
- package/src/runtime/pending-interactions.ts +15 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/browser-tabs-routes.ts +9 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
- package/src/runtime/routes/client-routes.ts +10 -0
- package/src/runtime/routes/contact-routes.ts +31 -8
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +80 -1
- package/src/runtime/routes/conversation-query-routes.ts +68 -22
- package/src/runtime/routes/conversation-routes.ts +39 -14
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/guardian-approval-interception.ts +14 -73
- package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
- package/src/runtime/routes/home-feed-routes.ts +8 -3
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-message-handler.ts +214 -228
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
- package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
- package/src/runtime/routes/integrations/slack/channel.ts +36 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
- package/src/runtime/routes/mcp-auth-routes.ts +233 -41
- package/src/runtime/routes/memory-eval-routes.ts +87 -0
- package/src/runtime/routes/notification-routes.ts +122 -133
- package/src/runtime/routes/platform-routes.ts +2 -2
- package/src/runtime/routes/plugins-routes.ts +202 -3
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/surface-action-routes.ts +2 -1
- package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
- package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
- package/src/runtime/routes/workflow-routes.test.ts +229 -44
- package/src/runtime/routes/workflow-routes.ts +131 -29
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/runtime/tool-grant-request-helper.ts +18 -16
- package/src/runtime/trust-context-resolver.ts +8 -5
- package/src/schedule/inference-profile.ts +2 -14
- package/src/schedule/schedule-store.ts +1 -1
- package/src/schedule/scheduler-types.ts +5 -1
- package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
- package/src/security/secret-patterns.ts +3 -0
- package/src/subagent/manager.ts +17 -4
- package/src/subagent/types.ts +6 -0
- package/src/telemetry/trace-collection-policy.test.ts +28 -0
- package/src/telemetry/trace-collection-policy.ts +30 -0
- package/src/telemetry/types.ts +89 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
- package/src/telemetry/usage-telemetry-reporter.ts +148 -41
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
- package/src/tools/browser/browser-execution.ts +30 -19
- package/src/tools/document/document-tool.ts +2 -3
- package/src/tools/executor.ts +5 -3
- package/src/tools/host-terminal/host-shell.ts +5 -4
- package/src/tools/memory/register.ts +2 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
- package/src/tools/network/__tests__/web-search.test.ts +143 -0
- package/src/tools/network/web-fetch.ts +372 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/network/web-search.ts +213 -10
- package/src/tools/permission-checker.ts +4 -3
- package/src/tools/registry.ts +20 -0
- package/src/tools/schedule/create.ts +7 -12
- package/src/tools/schedule/update.ts +4 -11
- package/src/tools/shared/filesystem/path-policy.ts +39 -13
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +33 -0
- package/src/tools/subagent/spawn.ts +61 -12
- package/src/tools/terminal/shell.ts +10 -4
- package/src/tools/tool-approval-handler.ts +18 -13
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/types.ts +9 -0
- package/src/tools/ui-surface/definitions.ts +64 -3
- package/src/tools/verification-control-plane-policy.ts +3 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/tools/workflows/run-workflow.ts +1 -0
- package/src/util/disk-usage.ts +78 -23
- package/src/util/platform.ts +10 -3
- package/src/watcher/telemetry.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/engine.test.ts +175 -1
- package/src/workflows/engine.ts +82 -0
- package/src/workflows/journal-store.test.ts +70 -0
- package/src/workflows/journal-store.ts +18 -3
- package/src/workflows/run-manager.test.ts +171 -28
- package/src/workflows/run-manager.ts +66 -24
- package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
- package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
- package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
- package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/notifications/tool-approval-copy.ts +0 -142
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- package/src/tools/credentials/vault.ts +0 -712
|
@@ -125,6 +125,16 @@ mock.module("../ipc/gateway-client.js", () => ({
|
|
|
125
125
|
|
|
126
126
|
// ── Import the module under test AFTER mocks are set up ──────────────────────
|
|
127
127
|
|
|
128
|
+
import {
|
|
129
|
+
mkdtempSync,
|
|
130
|
+
rmSync,
|
|
131
|
+
symlinkSync,
|
|
132
|
+
unlinkSync,
|
|
133
|
+
writeFileSync,
|
|
134
|
+
} from "node:fs";
|
|
135
|
+
import { tmpdir } from "node:os";
|
|
136
|
+
import { join } from "node:path";
|
|
137
|
+
|
|
128
138
|
import {
|
|
129
139
|
check,
|
|
130
140
|
classifyRisk,
|
|
@@ -231,6 +241,47 @@ describe("Permission Checker (gateway IPC)", () => {
|
|
|
231
241
|
expect(result2.reason).toBe("Cached test");
|
|
232
242
|
});
|
|
233
243
|
|
|
244
|
+
test("file-tool cache misses when a symlink target is retargeted", async () => {
|
|
245
|
+
// File risk depends on filesystem state: the cache key folds in the
|
|
246
|
+
// symlink-resolved target, so the same raw input must NOT return a stale
|
|
247
|
+
// cached result after the symlink is pointed somewhere new.
|
|
248
|
+
const dir = mkdtempSync(join(tmpdir(), "risk-cache-symlink-"));
|
|
249
|
+
try {
|
|
250
|
+
const benign = join(dir, "benign.txt");
|
|
251
|
+
const other = join(dir, "other.txt");
|
|
252
|
+
writeFileSync(benign, "ok");
|
|
253
|
+
writeFileSync(other, "ok");
|
|
254
|
+
const link = join(dir, "link.txt");
|
|
255
|
+
symlinkSync(benign, link);
|
|
256
|
+
|
|
257
|
+
mockIpcClassifyRiskResult = {
|
|
258
|
+
risk: "low",
|
|
259
|
+
reason: "benign",
|
|
260
|
+
matchType: "registry",
|
|
261
|
+
scopeOptions: [],
|
|
262
|
+
};
|
|
263
|
+
const first = await classifyRisk("file_read", { path: link });
|
|
264
|
+
expect(first.level).toBe(RiskLevel.Low);
|
|
265
|
+
|
|
266
|
+
// Retarget the symlink to a different real file; raw input unchanged.
|
|
267
|
+
unlinkSync(link);
|
|
268
|
+
symlinkSync(other, link);
|
|
269
|
+
|
|
270
|
+
mockIpcClassifyRiskResult = {
|
|
271
|
+
risk: "high",
|
|
272
|
+
reason: "now sensitive",
|
|
273
|
+
matchType: "registry",
|
|
274
|
+
scopeOptions: [],
|
|
275
|
+
};
|
|
276
|
+
const second = await classifyRisk("file_read", { path: link });
|
|
277
|
+
// Cache must have missed and re-classified against the new target.
|
|
278
|
+
expect(second.level).toBe(RiskLevel.High);
|
|
279
|
+
expect(second.reason).toBe("now sensitive");
|
|
280
|
+
} finally {
|
|
281
|
+
rmSync(dir, { recursive: true, force: true });
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
234
285
|
test("preserves commandCandidates from gateway response", async () => {
|
|
235
286
|
mockIpcClassifyRiskResult = {
|
|
236
287
|
risk: "low",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
|
|
5
5
|
import { getIsContainerized } from "../config/env-registry.js";
|
|
6
6
|
import { getConfig } from "../config/loader.js";
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
looksLikePathOnlyInput,
|
|
17
17
|
} from "../tools/network/url-safety.js";
|
|
18
18
|
import { getTool, getToolOwner } from "../tools/registry.js";
|
|
19
|
+
import { resolveRealPath } from "../tools/shared/filesystem/path-policy.js";
|
|
19
20
|
import type { Tool } from "../tools/types.js";
|
|
20
21
|
import {
|
|
21
22
|
getDeprecatedDir,
|
|
@@ -23,6 +24,8 @@ import {
|
|
|
23
24
|
getWorkspaceDir,
|
|
24
25
|
getWorkspaceHooksDir,
|
|
25
26
|
getWorkspacePluginsDir,
|
|
27
|
+
getWorkspaceRoutesDir,
|
|
28
|
+
getWorkspaceToolsDir,
|
|
26
29
|
} from "../util/platform.js";
|
|
27
30
|
import {
|
|
28
31
|
type ApprovalContext,
|
|
@@ -99,6 +102,7 @@ function riskCacheKey(
|
|
|
99
102
|
input: Record<string, unknown>,
|
|
100
103
|
workingDir?: string,
|
|
101
104
|
manifestOverride?: ManifestOverride,
|
|
105
|
+
fsStateKey?: string,
|
|
102
106
|
): string {
|
|
103
107
|
// Strip `reason` and `activity` before computing the cache key — they are
|
|
104
108
|
// cosmetic status text that varies per invocation even for identical tool
|
|
@@ -111,10 +115,31 @@ function riskCacheKey(
|
|
|
111
115
|
.update(workingDir ?? "")
|
|
112
116
|
.update("\0")
|
|
113
117
|
.update(manifestOverride ? JSON.stringify(manifestOverride) : "")
|
|
118
|
+
// For file tools, fold in the symlink-resolved target path(s). File risk
|
|
119
|
+
// depends on filesystem state (a symlink can be retargeted under a
|
|
120
|
+
// protected dir between calls), so the same raw input must miss the cache
|
|
121
|
+
// when its canonicalized target changes.
|
|
122
|
+
.update("\0")
|
|
123
|
+
.update(fsStateKey ?? "")
|
|
114
124
|
.digest("hex");
|
|
115
125
|
return `${toolName}\0${hash}`;
|
|
116
126
|
}
|
|
117
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Compute the filesystem-state component of the risk cache key for file tools:
|
|
130
|
+
* the symlink-resolved target path(s). Returns `undefined` for non-file tools
|
|
131
|
+
* (whose risk does not depend on filesystem state).
|
|
132
|
+
*/
|
|
133
|
+
function fileToolFsStateKey(
|
|
134
|
+
toolName: string,
|
|
135
|
+
input: Record<string, unknown>,
|
|
136
|
+
workingDir?: string,
|
|
137
|
+
): string | undefined {
|
|
138
|
+
if (!FILE_TOOL_NAMES.has(toolName)) return undefined;
|
|
139
|
+
const resolved = resolveFileToolPaths(toolName, input, workingDir);
|
|
140
|
+
return `${resolved.resolvedPath ?? ""}\0${resolved.resolvedTransferDestPath ?? ""}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
118
143
|
/** Clear the risk classification cache. Called when trust rules change. Exported for test setup. */
|
|
119
144
|
export function clearRiskCache(): void {
|
|
120
145
|
riskCache.clear();
|
|
@@ -273,16 +298,153 @@ import type {
|
|
|
273
298
|
|
|
274
299
|
function buildFileContext(): FileContext {
|
|
275
300
|
const config = getConfig();
|
|
301
|
+
// Canonicalize the protected directories via realpath so that a symlinked
|
|
302
|
+
// component anywhere in their path still prefix-matches the canonicalized
|
|
303
|
+
// target path computed in buildClassifyRiskParams. Both sides must be
|
|
304
|
+
// symlink-resolved for the gateway's lexical prefix checks to be sound.
|
|
305
|
+
const protectedDir = resolveRealPath(getProtectedDir());
|
|
276
306
|
return {
|
|
277
|
-
protectedDir
|
|
278
|
-
deprecatedDir: getDeprecatedDir(),
|
|
279
|
-
hooksDir: getWorkspaceHooksDir(),
|
|
280
|
-
pluginsDir: getWorkspacePluginsDir(),
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
307
|
+
protectedDir,
|
|
308
|
+
deprecatedDir: resolveRealPath(getDeprecatedDir()),
|
|
309
|
+
hooksDir: resolveRealPath(getWorkspaceHooksDir()),
|
|
310
|
+
pluginsDir: resolveRealPath(getWorkspacePluginsDir()),
|
|
311
|
+
toolsDir: resolveRealPath(getWorkspaceToolsDir()),
|
|
312
|
+
routesDir: resolveRealPath(getWorkspaceRoutesDir()),
|
|
313
|
+
actorTokenSigningKeyPath: join(protectedDir, "actor-token-signing-key"),
|
|
314
|
+
skillSourceDirs: getSkillRoots(config.skills.load.extraDirs).map(
|
|
315
|
+
resolveRealPath,
|
|
284
316
|
),
|
|
285
|
-
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Canonicalize the security-sensitive path of a file tool invocation by
|
|
322
|
+
* resolving symlinks before it is sent to the gateway risk classifier.
|
|
323
|
+
*
|
|
324
|
+
* The gateway classifies file risk by lexically prefix-matching the target
|
|
325
|
+
* path against protected directories (skill source, hooks, plugins, the actor
|
|
326
|
+
* token signing key). Lexical resolution alone does not follow symlinks, so a
|
|
327
|
+
* symlink whose name looks benign but whose real target is a protected
|
|
328
|
+
* directory would be under-classified and could skip the High-risk approval
|
|
329
|
+
* prompt. Resolving symlinks here — on the daemon, which owns the workspace
|
|
330
|
+
* filesystem — closes that gap while keeping the gateway free of filesystem
|
|
331
|
+
* access (it cannot see the workspace in Docker mode).
|
|
332
|
+
*
|
|
333
|
+
* `resolveRealPath` falls back to the lexical path when the target lives on a
|
|
334
|
+
* filesystem this process cannot see (e.g. host_file paths proxied to a remote
|
|
335
|
+
* client), so this never regresses below today's lexical behavior.
|
|
336
|
+
*/
|
|
337
|
+
// The Docker sandbox mounts the workspace at /workspace, and the model emits
|
|
338
|
+
// container-scoped paths (e.g. "/workspace/tools/evil.ts") even on local turns.
|
|
339
|
+
// Mirror the gateway's resolveSandboxPath remap so the symlink-resolved path we
|
|
340
|
+
// forward lines up with the gateway's lexical fallback and the protected dirs.
|
|
341
|
+
const CONTAINER_WORKSPACE_PREFIX = "/workspace/";
|
|
342
|
+
const CONTAINER_WORKSPACE_EXACT = "/workspace";
|
|
343
|
+
|
|
344
|
+
function resolveSandboxBase(rawPath: string, workingDir: string): string {
|
|
345
|
+
let effectivePath = rawPath;
|
|
346
|
+
if (!rawPath.startsWith(workingDir + "/") && rawPath !== workingDir) {
|
|
347
|
+
if (rawPath.startsWith(CONTAINER_WORKSPACE_PREFIX)) {
|
|
348
|
+
effectivePath = rawPath.slice(CONTAINER_WORKSPACE_PREFIX.length);
|
|
349
|
+
} else if (rawPath === CONTAINER_WORKSPACE_EXACT) {
|
|
350
|
+
effectivePath = ".";
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return resolve(workingDir, effectivePath);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function resolveClassificationPath(
|
|
357
|
+
filePath: string,
|
|
358
|
+
workingDir: string,
|
|
359
|
+
isHostTool: boolean,
|
|
360
|
+
): string | undefined {
|
|
361
|
+
if (!filePath) return undefined;
|
|
362
|
+
// Mirror the gateway classifier's lexical base: host tools resolve the path
|
|
363
|
+
// as absolute/relative-to-cwd; sandbox tools apply the /workspace remap and
|
|
364
|
+
// resolve against workingDir. Then follow symlinks so a benign-looking name
|
|
365
|
+
// whose real target is a protected directory is still escalated.
|
|
366
|
+
const base = isHostTool
|
|
367
|
+
? resolve(filePath)
|
|
368
|
+
: resolveSandboxBase(filePath, workingDir);
|
|
369
|
+
return resolveRealPath(base);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const FILE_TOOL_NAMES = new Set([
|
|
373
|
+
"file_read",
|
|
374
|
+
"file_write",
|
|
375
|
+
"file_edit",
|
|
376
|
+
"host_file_read",
|
|
377
|
+
"host_file_write",
|
|
378
|
+
"host_file_edit",
|
|
379
|
+
"host_file_transfer",
|
|
380
|
+
]);
|
|
381
|
+
|
|
382
|
+
interface FileToolResolution {
|
|
383
|
+
filePath: string;
|
|
384
|
+
effectiveWorkingDir: string;
|
|
385
|
+
isHostTool: boolean;
|
|
386
|
+
resolvedPath?: string;
|
|
387
|
+
transferSandboxDestPath?: string;
|
|
388
|
+
transferSandboxWorkingDir?: string;
|
|
389
|
+
resolvedTransferDestPath?: string;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Resolve the security-sensitive path(s) of a file tool invocation, including
|
|
394
|
+
* symlink canonicalization. Shared by the IPC param builder and the risk cache
|
|
395
|
+
* key so both observe the same filesystem state — file risk now depends on
|
|
396
|
+
* symlink targets, so the cache must key on the canonicalized path, not just
|
|
397
|
+
* the raw tool input.
|
|
398
|
+
*/
|
|
399
|
+
function resolveFileToolPaths(
|
|
400
|
+
toolName: string,
|
|
401
|
+
input: Record<string, unknown>,
|
|
402
|
+
workingDir?: string,
|
|
403
|
+
): FileToolResolution {
|
|
404
|
+
const isHostTool = toolName.startsWith("host_");
|
|
405
|
+
let filePath: string;
|
|
406
|
+
// For host_file_transfer to_sandbox, the file is written into the workspace
|
|
407
|
+
// at dest_path — capture it (plus the sandbox working dir) so the gateway can
|
|
408
|
+
// escalate writes that land in a code-injection sink, since `path` carries
|
|
409
|
+
// the host-side source.
|
|
410
|
+
let transferSandboxDestPath: string | undefined;
|
|
411
|
+
let transferSandboxWorkingDir: string | undefined;
|
|
412
|
+
if (toolName === "host_file_transfer") {
|
|
413
|
+
// The security-sensitive host-side path is source_path when reading from
|
|
414
|
+
// the host (to_sandbox), dest_path when writing to the host (to_host).
|
|
415
|
+
const direction = getStringField(input, "direction");
|
|
416
|
+
if (direction === "to_sandbox") {
|
|
417
|
+
filePath = getStringField(input, "source_path");
|
|
418
|
+
transferSandboxDestPath = getStringField(input, "dest_path");
|
|
419
|
+
transferSandboxWorkingDir = workingDir ?? process.cwd();
|
|
420
|
+
} else {
|
|
421
|
+
filePath = getStringField(input, "dest_path");
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
filePath = getStringField(input, "path", "file_path");
|
|
425
|
+
}
|
|
426
|
+
const effectiveWorkingDir = isHostTool ? "/" : (workingDir ?? process.cwd());
|
|
427
|
+
return {
|
|
428
|
+
filePath,
|
|
429
|
+
effectiveWorkingDir,
|
|
430
|
+
isHostTool,
|
|
431
|
+
resolvedPath: resolveClassificationPath(
|
|
432
|
+
filePath,
|
|
433
|
+
effectiveWorkingDir,
|
|
434
|
+
isHostTool,
|
|
435
|
+
),
|
|
436
|
+
transferSandboxDestPath,
|
|
437
|
+
transferSandboxWorkingDir,
|
|
438
|
+
// The to_sandbox destination is a workspace write — symlink-resolve it too
|
|
439
|
+
// so it can't mask a code-injection sink.
|
|
440
|
+
resolvedTransferDestPath:
|
|
441
|
+
transferSandboxDestPath != null
|
|
442
|
+
? resolveClassificationPath(
|
|
443
|
+
transferSandboxDestPath,
|
|
444
|
+
transferSandboxWorkingDir ?? process.cwd(),
|
|
445
|
+
false,
|
|
446
|
+
)
|
|
447
|
+
: undefined,
|
|
286
448
|
};
|
|
287
449
|
}
|
|
288
450
|
|
|
@@ -347,25 +509,16 @@ function buildClassifyRiskParams(
|
|
|
347
509
|
"host_file_transfer",
|
|
348
510
|
].includes(toolName)
|
|
349
511
|
) {
|
|
350
|
-
const
|
|
351
|
-
let filePath: string;
|
|
352
|
-
if (toolName === "host_file_transfer") {
|
|
353
|
-
// For host_file_transfer the security-sensitive path is the host-side
|
|
354
|
-
// path: source_path when reading from the host (to_sandbox), dest_path
|
|
355
|
-
// when writing to the host (to_host).
|
|
356
|
-
const direction = getStringField(input, "direction");
|
|
357
|
-
filePath =
|
|
358
|
-
direction === "to_sandbox"
|
|
359
|
-
? getStringField(input, "source_path")
|
|
360
|
-
: getStringField(input, "dest_path");
|
|
361
|
-
} else {
|
|
362
|
-
filePath = getStringField(input, "path", "file_path");
|
|
363
|
-
}
|
|
512
|
+
const resolved = resolveFileToolPaths(toolName, input, workingDir);
|
|
364
513
|
return {
|
|
365
514
|
tool: toolName,
|
|
366
|
-
path: filePath,
|
|
367
|
-
|
|
515
|
+
path: resolved.filePath,
|
|
516
|
+
resolvedPath: resolved.resolvedPath,
|
|
517
|
+
workingDir: resolved.effectiveWorkingDir,
|
|
368
518
|
fileContext: buildFileContext(),
|
|
519
|
+
transferSandboxDestPath: resolved.transferSandboxDestPath,
|
|
520
|
+
transferSandboxWorkingDir: resolved.transferSandboxWorkingDir,
|
|
521
|
+
resolvedTransferDestPath: resolved.resolvedTransferDestPath,
|
|
369
522
|
};
|
|
370
523
|
}
|
|
371
524
|
|
|
@@ -448,7 +601,13 @@ export async function classifyRisk(
|
|
|
448
601
|
signal?.throwIfAborted();
|
|
449
602
|
|
|
450
603
|
// Check cache first.
|
|
451
|
-
const cacheKey = riskCacheKey(
|
|
604
|
+
const cacheKey = riskCacheKey(
|
|
605
|
+
toolName,
|
|
606
|
+
input,
|
|
607
|
+
workingDir,
|
|
608
|
+
manifestOverride,
|
|
609
|
+
fileToolFsStateKey(toolName, input, workingDir),
|
|
610
|
+
);
|
|
452
611
|
const cached = riskCache.get(cacheKey);
|
|
453
612
|
if (cached !== undefined) {
|
|
454
613
|
// LRU refresh
|
|
@@ -53,6 +53,8 @@ export interface FileContext {
|
|
|
53
53
|
deprecatedDir: string;
|
|
54
54
|
hooksDir: string;
|
|
55
55
|
pluginsDir: string;
|
|
56
|
+
toolsDir: string;
|
|
57
|
+
routesDir: string;
|
|
56
58
|
actorTokenSigningKeyPath: string;
|
|
57
59
|
skillSourceDirs: string[];
|
|
58
60
|
}
|
|
@@ -81,6 +83,13 @@ export interface ClassifyRiskParams {
|
|
|
81
83
|
command?: string;
|
|
82
84
|
url?: string;
|
|
83
85
|
path?: string;
|
|
86
|
+
/**
|
|
87
|
+
* The file tool's target path with symlinks resolved (canonicalized by the
|
|
88
|
+
* daemon, which owns the workspace filesystem). The gateway uses this for its
|
|
89
|
+
* security escalation prefix checks so a symlink cannot mask a write into a
|
|
90
|
+
* protected directory. Falls back to lexical `path` resolution when absent.
|
|
91
|
+
*/
|
|
92
|
+
resolvedPath?: string;
|
|
84
93
|
skill?: string;
|
|
85
94
|
mode?: string;
|
|
86
95
|
script?: string;
|
|
@@ -95,4 +104,19 @@ export interface ClassifyRiskParams {
|
|
|
95
104
|
registryDefaultRisk?: string;
|
|
96
105
|
/** Number of credential references attached to this tool invocation. */
|
|
97
106
|
credentialRefCount?: number;
|
|
107
|
+
/**
|
|
108
|
+
* For host_file_transfer with `direction: "to_sandbox"`: the workspace-side
|
|
109
|
+
* destination path and the sandbox working directory it resolves against, so
|
|
110
|
+
* the gateway can escalate transfers that install an executable file in a
|
|
111
|
+
* code-injection sink (tools/routes/hooks/plugins/skills).
|
|
112
|
+
*/
|
|
113
|
+
transferSandboxDestPath?: string;
|
|
114
|
+
transferSandboxWorkingDir?: string;
|
|
115
|
+
/**
|
|
116
|
+
* The `to_sandbox` workspace destination with symlinks resolved
|
|
117
|
+
* (canonicalized by the daemon). Used for the code-injection-sink check so a
|
|
118
|
+
* symlinked destination cannot mask the real target. Falls back to lexical
|
|
119
|
+
* resolution of `transferSandboxDestPath` when absent.
|
|
120
|
+
*/
|
|
121
|
+
resolvedTransferDestPath?: string;
|
|
98
122
|
}
|
|
@@ -48,6 +48,8 @@ interface MockInteraction {
|
|
|
48
48
|
orderedIds: string[];
|
|
49
49
|
optionsById: Record<string, string[]>;
|
|
50
50
|
};
|
|
51
|
+
toolUseId?: string;
|
|
52
|
+
questionDetails?: { entries: Array<{ id: string; question: string }> };
|
|
51
53
|
}
|
|
52
54
|
const _piStore = new Map<string, MockInteraction>();
|
|
53
55
|
mock.module("../runtime/pending-interactions.js", () => ({
|
|
@@ -205,6 +207,31 @@ describe("QuestionPrompter", () => {
|
|
|
205
207
|
expect(_piStore.has(req.requestId)).toBe(false);
|
|
206
208
|
});
|
|
207
209
|
|
|
210
|
+
test("persists the full question entries on the interaction for rehydration", async () => {
|
|
211
|
+
// GIVEN a batched prompt with a tool-use id
|
|
212
|
+
const { prompter, sent } = makePrompter();
|
|
213
|
+
|
|
214
|
+
const promise = prompter.prompt({
|
|
215
|
+
...threeQuestionParams,
|
|
216
|
+
toolUseId: "tool-q",
|
|
217
|
+
});
|
|
218
|
+
const req = sent[0] as QuestionRequestEvent;
|
|
219
|
+
|
|
220
|
+
// THEN the registered interaction carries the full entries (not just the
|
|
221
|
+
// id maps in `metadata`) so a history-load render can rehydrate the card
|
|
222
|
+
const interaction = _piStore.get(req.requestId);
|
|
223
|
+
expect(interaction?.toolUseId).toBe("tool-q");
|
|
224
|
+
expect(interaction?.questionDetails?.entries).toHaveLength(3);
|
|
225
|
+
expect(interaction?.questionDetails?.entries).toEqual(req.questions);
|
|
226
|
+
|
|
227
|
+
resolveBatch(req.requestId, [
|
|
228
|
+
{ questionId: "q1", kind: "option", optionId: "a" },
|
|
229
|
+
{ questionId: "q2", kind: "option", optionId: "x" },
|
|
230
|
+
{ questionId: "q3", kind: "skip" },
|
|
231
|
+
]);
|
|
232
|
+
await promise;
|
|
233
|
+
});
|
|
234
|
+
|
|
208
235
|
test("free-text resolution", async () => {
|
|
209
236
|
const { prompter, sent } = makePrompter();
|
|
210
237
|
|
|
@@ -269,6 +269,10 @@ export class QuestionPrompter {
|
|
|
269
269
|
timer,
|
|
270
270
|
toolUseId,
|
|
271
271
|
metadata: { orderedIds, optionsById } satisfies QuestionBatchMetadata,
|
|
272
|
+
// Persist the full entries (not just the id maps in `metadata`) so a
|
|
273
|
+
// history-load render can stamp this outstanding prompt back onto its
|
|
274
|
+
// tool call and rehydrate the question card after a reconnect.
|
|
275
|
+
questionDetails: { entries },
|
|
272
276
|
});
|
|
273
277
|
|
|
274
278
|
// Populate both shapes on the wire: `questions[]` is the canonical
|
|
@@ -178,4 +178,123 @@ describe("VellumPlatformClient", () => {
|
|
|
178
178
|
});
|
|
179
179
|
});
|
|
180
180
|
});
|
|
181
|
+
|
|
182
|
+
describe("getOwnerConsent()", () => {
|
|
183
|
+
test("maps snake_case body to camelCase on 200", async () => {
|
|
184
|
+
globalThis.fetch = mock(async (url: string | URL | Request) => {
|
|
185
|
+
expect(String(url)).toBe(
|
|
186
|
+
"https://platform.example.com/v1/assistants/asst-123/owner-consent/",
|
|
187
|
+
);
|
|
188
|
+
return new Response(
|
|
189
|
+
JSON.stringify({
|
|
190
|
+
share_analytics: true,
|
|
191
|
+
share_diagnostics: false,
|
|
192
|
+
share_diagnostics_accepted_version: "2026-06-18",
|
|
193
|
+
}),
|
|
194
|
+
{ status: 200 },
|
|
195
|
+
);
|
|
196
|
+
}) as unknown as typeof globalThis.fetch;
|
|
197
|
+
|
|
198
|
+
const client = await VellumPlatformClient.create();
|
|
199
|
+
const consent = await client!.getOwnerConsent();
|
|
200
|
+
expect(consent).toEqual({
|
|
201
|
+
shareAnalytics: true,
|
|
202
|
+
shareDiagnostics: false,
|
|
203
|
+
shareDiagnosticsAcceptedVersion: "2026-06-18",
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("defaults shareDiagnosticsAcceptedVersion to '' when the platform omits it (back-compat)", async () => {
|
|
208
|
+
globalThis.fetch = mock(
|
|
209
|
+
async () =>
|
|
210
|
+
new Response(
|
|
211
|
+
JSON.stringify({ share_analytics: true, share_diagnostics: true }),
|
|
212
|
+
{ status: 200 },
|
|
213
|
+
),
|
|
214
|
+
) as unknown as typeof globalThis.fetch;
|
|
215
|
+
|
|
216
|
+
const client = await VellumPlatformClient.create();
|
|
217
|
+
const consent = await client!.getOwnerConsent();
|
|
218
|
+
expect(consent).toEqual({
|
|
219
|
+
shareAnalytics: true,
|
|
220
|
+
shareDiagnostics: true,
|
|
221
|
+
shareDiagnosticsAcceptedVersion: "",
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("uses the authenticated Api-Key header", async () => {
|
|
226
|
+
globalThis.fetch = mock(
|
|
227
|
+
async (_url: string | URL | Request, init?: RequestInit) => {
|
|
228
|
+
const headers = new Headers(init?.headers);
|
|
229
|
+
expect(headers.get("Authorization")).toBe("Api-Key sk-test-key");
|
|
230
|
+
return new Response(
|
|
231
|
+
JSON.stringify({ share_analytics: true, share_diagnostics: true }),
|
|
232
|
+
{ status: 200 },
|
|
233
|
+
);
|
|
234
|
+
},
|
|
235
|
+
) as unknown as typeof globalThis.fetch;
|
|
236
|
+
|
|
237
|
+
const client = await VellumPlatformClient.create();
|
|
238
|
+
await client!.getOwnerConsent();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("returns null on 404", async () => {
|
|
242
|
+
globalThis.fetch = mock(
|
|
243
|
+
async () => new Response("not found", { status: 404 }),
|
|
244
|
+
) as unknown as typeof globalThis.fetch;
|
|
245
|
+
|
|
246
|
+
const client = await VellumPlatformClient.create();
|
|
247
|
+
expect(await client!.getOwnerConsent()).toBeNull();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("returns null on 500", async () => {
|
|
251
|
+
globalThis.fetch = mock(
|
|
252
|
+
async () => new Response("error", { status: 500 }),
|
|
253
|
+
) as unknown as typeof globalThis.fetch;
|
|
254
|
+
|
|
255
|
+
const client = await VellumPlatformClient.create();
|
|
256
|
+
expect(await client!.getOwnerConsent()).toBeNull();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("returns null on network error", async () => {
|
|
260
|
+
globalThis.fetch = mock(async () => {
|
|
261
|
+
throw new Error("network down");
|
|
262
|
+
}) as unknown as typeof globalThis.fetch;
|
|
263
|
+
|
|
264
|
+
const client = await VellumPlatformClient.create();
|
|
265
|
+
expect(await client!.getOwnerConsent()).toBeNull();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("returns null on malformed body (non-boolean fields)", async () => {
|
|
269
|
+
globalThis.fetch = mock(
|
|
270
|
+
async () =>
|
|
271
|
+
new Response(
|
|
272
|
+
JSON.stringify({ share_analytics: "yes", share_diagnostics: 1 }),
|
|
273
|
+
{ status: 200 },
|
|
274
|
+
),
|
|
275
|
+
) as unknown as typeof globalThis.fetch;
|
|
276
|
+
|
|
277
|
+
const client = await VellumPlatformClient.create();
|
|
278
|
+
expect(await client!.getOwnerConsent()).toBeNull();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("returns null without fetching when assistantId is empty", async () => {
|
|
282
|
+
mockAssistantId = "";
|
|
283
|
+
mockSecureKeys = {};
|
|
284
|
+
|
|
285
|
+
const fetchSpy = mock(
|
|
286
|
+
async () =>
|
|
287
|
+
new Response(
|
|
288
|
+
JSON.stringify({ share_analytics: true, share_diagnostics: true }),
|
|
289
|
+
{ status: 200 },
|
|
290
|
+
),
|
|
291
|
+
);
|
|
292
|
+
globalThis.fetch = fetchSpy as unknown as typeof globalThis.fetch;
|
|
293
|
+
|
|
294
|
+
const client = await VellumPlatformClient.create();
|
|
295
|
+
expect(client!.platformAssistantId).toBe("");
|
|
296
|
+
expect(await client!.getOwnerConsent()).toBeNull();
|
|
297
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
298
|
+
});
|
|
299
|
+
});
|
|
181
300
|
});
|
package/src/platform/client.ts
CHANGED
|
@@ -16,6 +16,18 @@ const log = getLogger("platform-client");
|
|
|
16
16
|
|
|
17
17
|
let _missingPrereqsWarned = false;
|
|
18
18
|
|
|
19
|
+
export interface OwnerConsent {
|
|
20
|
+
shareAnalytics: boolean;
|
|
21
|
+
shareDiagnostics: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Version of the diagnostics-sharing consent the owner accepted
|
|
24
|
+
* ("YYYY-MM-DD", or "" if never accepted). Composes the per-turn
|
|
25
|
+
* trace-collection gate: traces are only collected once this is >= the
|
|
26
|
+
* disclosing version (see telemetry/trace-collection-policy.ts).
|
|
27
|
+
*/
|
|
28
|
+
shareDiagnosticsAcceptedVersion: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
export class VellumPlatformClient {
|
|
20
32
|
private readonly platformBaseUrl: string;
|
|
21
33
|
private readonly apiKey: string;
|
|
@@ -109,6 +121,60 @@ export class VellumPlatformClient {
|
|
|
109
121
|
return fetch(url, { ...init, headers });
|
|
110
122
|
}
|
|
111
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Fetch the platform owner's telemetry consent for this assistant.
|
|
126
|
+
*
|
|
127
|
+
* Returns `null` whenever the consent is unknown — missing assistant id,
|
|
128
|
+
* any non-2xx response (e.g. 404 before the endpoint is deployed), a
|
|
129
|
+
* malformed body, or a network error. Callers treat `null` as default-off.
|
|
130
|
+
* Never throws.
|
|
131
|
+
*/
|
|
132
|
+
async getOwnerConsent(): Promise<OwnerConsent | null> {
|
|
133
|
+
if (!this.assistantId) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const res = await this.fetch(
|
|
139
|
+
`/v1/assistants/${this.assistantId}/owner-consent/`,
|
|
140
|
+
);
|
|
141
|
+
if (!res.ok) {
|
|
142
|
+
log.debug(
|
|
143
|
+
{ status: res.status },
|
|
144
|
+
"owner-consent fetch returned non-2xx — treating as unknown",
|
|
145
|
+
);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const body = (await res.json()) as {
|
|
150
|
+
share_analytics?: unknown;
|
|
151
|
+
share_diagnostics?: unknown;
|
|
152
|
+
share_diagnostics_accepted_version?: unknown;
|
|
153
|
+
};
|
|
154
|
+
if (
|
|
155
|
+
typeof body.share_analytics !== "boolean" ||
|
|
156
|
+
typeof body.share_diagnostics !== "boolean"
|
|
157
|
+
) {
|
|
158
|
+
log.debug("owner-consent body malformed — treating as unknown");
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
shareAnalytics: body.share_analytics,
|
|
164
|
+
shareDiagnostics: body.share_diagnostics,
|
|
165
|
+
// Back-compat: an older platform that doesn't return this field yields
|
|
166
|
+
// "" → fails the trace-collection version gate → fail-closed (no trace).
|
|
167
|
+
shareDiagnosticsAcceptedVersion:
|
|
168
|
+
typeof body.share_diagnostics_accepted_version === "string"
|
|
169
|
+
? body.share_diagnostics_accepted_version
|
|
170
|
+
: "",
|
|
171
|
+
};
|
|
172
|
+
} catch (err) {
|
|
173
|
+
log.debug({ err }, "owner-consent fetch failed — treating as unknown");
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
112
178
|
get baseUrl(): string {
|
|
113
179
|
return this.platformBaseUrl;
|
|
114
180
|
}
|