@vellumai/assistant 0.11.6 → 0.11.7-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 +31 -19
- package/Dockerfile +1 -0
- package/docs/architecture/security.md +3 -3
- package/docs/flux-turn-detection-spike.md +8 -7
- package/docs/runbook-trusted-contacts.md +14 -0
- package/docs/stt-provider-onboarding.md +8 -4
- package/knip.json +1 -0
- package/node_modules/@vellumai/avatar-manifest/package.json +18 -0
- package/node_modules/@vellumai/avatar-manifest/src/__tests__/manifest.test.ts +173 -0
- package/node_modules/@vellumai/avatar-manifest/src/__tests__/read.test.ts +114 -0
- package/node_modules/@vellumai/avatar-manifest/src/index.ts +29 -0
- package/node_modules/@vellumai/avatar-manifest/src/layout.ts +18 -0
- package/node_modules/@vellumai/avatar-manifest/src/manifest.ts +156 -0
- package/node_modules/@vellumai/avatar-manifest/src/read.ts +109 -0
- package/node_modules/@vellumai/avatar-manifest/tsconfig.json +20 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
- package/node_modules/@vellumai/ces-client/src/http-credentials.ts +161 -0
- package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/shell.test.ts +56 -0
- package/node_modules/@vellumai/environments/src/shell.ts +65 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +32 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +82 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +56 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +18 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -11
- package/node_modules/@vellumai/gateway-client/src/channel-permission-contract.ts +3 -1
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +9 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +4 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +75 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +17 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +8 -0
- package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +47 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +11 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
- package/openapi.yaml +419 -55
- package/package.json +4 -1
- package/scripts/sync-llm-catalog.ts +6 -0
- package/src/__tests__/approval-card-edit-inbound-handoff.test.ts +199 -0
- package/src/__tests__/approval-card-edit.test.ts +153 -0
- package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
- package/src/__tests__/available-skills.test.ts +54 -0
- package/src/__tests__/avatar-identity-sync.test.ts +5 -1
- package/src/__tests__/catalog-install-normalize.test.ts +9 -1
- package/src/__tests__/channel-policy.test.ts +1 -3
- package/src/__tests__/channel-readiness-discord.test.ts +9 -70
- package/src/__tests__/compactor-call-site-logging.test.ts +61 -0
- package/src/__tests__/config-loader-backfill.test.ts +69 -0
- package/src/__tests__/config-schema.test.ts +2 -3
- package/src/__tests__/conversation-agent-loop.test.ts +238 -0
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +44 -0
- package/src/__tests__/conversation-evictor.test.ts +19 -0
- package/src/__tests__/conversation-fork-referential.test.ts +73 -63
- package/src/__tests__/conversation-fork-retrospective.test.ts +99 -74
- package/src/__tests__/conversation-runtime-assembly.test.ts +23 -0
- package/src/__tests__/conversation-slash.test.ts +9 -0
- package/src/__tests__/conversation-title-service.test.ts +97 -0
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +16 -0
- package/src/__tests__/credential-broker-server-use.test.ts +0 -218
- package/src/__tests__/credential-broker.test.ts +43 -420
- package/src/__tests__/credential-record-write-through.test.ts +78 -0
- package/src/__tests__/credential-routes.test.ts +17 -1
- package/src/__tests__/credential-security-invariants.test.ts +8 -9
- package/src/__tests__/default-profile-catalog-fallback.test.ts +289 -0
- package/src/__tests__/delete-propagation.test.ts +92 -2
- package/src/__tests__/discord-callback-round-trip.test.ts +93 -0
- package/src/__tests__/discord-channel-config.test.ts +237 -0
- package/src/__tests__/edit-propagation.test.ts +42 -2
- package/src/__tests__/evict-conversations-for-reload.test.ts +51 -1
- package/src/__tests__/fallback-breaker.test.ts +1335 -0
- package/src/__tests__/gateway-threshold-reader-mock.ts +6 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +30 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -2
- package/src/__tests__/image-conversion.test.ts +38 -4
- package/src/__tests__/install-skill-routing.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +4 -0
- package/src/__tests__/llm-resolver.test.ts +74 -0
- package/src/__tests__/llm-schema-fallback-profile.test.ts +359 -0
- package/src/__tests__/managed-fallback-dispatch.test.ts +430 -0
- package/src/__tests__/managed-profile-guard.test.ts +28 -0
- package/src/__tests__/managed-speech-defaults.test.ts +196 -1
- package/src/__tests__/mcp-cli.test.ts +23 -1
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +43 -34
- package/src/__tests__/media-stream-stt-session.test.ts +2 -0
- package/src/__tests__/notification-decision-fallback.test.ts +106 -0
- package/src/__tests__/notification-discord-adapter.test.ts +162 -0
- package/src/__tests__/oauth-connect-orchestrator.test.ts +95 -0
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -8
- package/src/__tests__/persist-unsendable-image.test.ts +3 -3
- package/src/__tests__/platform.test.ts +19 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
- package/src/__tests__/provider-catalog-visibility.test.ts +17 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +8 -1
- package/src/__tests__/provider-usage-tracking.test.ts +56 -0
- package/src/__tests__/require-fresh-approval.test.ts +5 -2
- package/src/__tests__/retry-fallback-escalation.test.ts +1093 -0
- package/src/__tests__/schedule-retry.test.ts +183 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +6 -6
- package/src/__tests__/secret-routes-platform-proxy.test.ts +55 -0
- package/src/__tests__/skill-load-tool.test.ts +34 -0
- package/src/__tests__/skill-tool-factory.test.ts +6 -1
- package/src/__tests__/skill-tool-manifest.test.ts +20 -0
- package/src/__tests__/skills.test.ts +25 -0
- package/src/__tests__/subagent-manager-notify.test.ts +38 -0
- package/src/__tests__/terminal-tools.test.ts +2 -0
- package/src/__tests__/tool-approval-handler.test.ts +146 -2
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +108 -2
- package/src/__tests__/tool-audit.test.ts +111 -40
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +228 -18
- package/src/__tests__/ui-shape-teaching.test.ts +1 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +6 -2
- package/src/__tests__/voice-config-update.test.ts +43 -0
- package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +480 -0
- package/src/__tests__/workspace-migration-148-strip-unsupported-fallback-profiles.test.ts +167 -0
- package/src/__tests__/workspace-migration-149-repoint-backup-profile-selections.test.ts +144 -0
- package/src/agent/loop.ts +31 -2
- package/src/approvals/guardian-card-withdrawal.ts +22 -0
- package/src/avatar/__tests__/ensure-raster.test.ts +291 -0
- package/src/avatar/avatar-manifest.ts +48 -100
- package/src/avatar/avatar-store.ts +7 -7
- package/src/avatar/ensure-raster.ts +146 -0
- package/src/avatar/resvg-lazy.test.ts +0 -1
- package/src/avatar/traits-png-sync.ts +9 -10
- package/src/background-wake/wake-intent-hooks.test.ts +0 -1
- package/src/backup/__tests__/paths.test.ts +6 -2
- package/src/backup/paths.ts +6 -4
- package/src/bundler/app-compiler.ts +2 -2
- package/src/bundler/compiler-tools.test.ts +36 -0
- package/src/bundler/compiler-tools.ts +54 -18
- package/src/calls/__tests__/telephony-synthesis-language.test.ts +81 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +57 -0
- package/src/calls/media-stream-stt-session.ts +2 -1
- package/src/calls/telephony-synthesis-language.ts +13 -5
- package/src/calls/voice-session-bridge.ts +19 -4
- package/src/channels/config.ts +23 -12
- package/src/cli/AGENTS.md +3 -1
- package/src/cli/commands/backup.help.ts +10 -13
- package/src/cli/commands/contacts.help.ts +16 -4
- package/src/cli/commands/contacts.ts +5 -1
- package/src/cli/commands/credentials.help.ts +1 -1
- package/src/cli/commands/inference-providers.ts +18 -53
- package/src/cli/commands/mcp.help.ts +6 -5
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/memory/memory-retrospective.ts +1 -0
- package/src/cli/commands/stt.help.ts +2 -2
- package/src/config/__tests__/backup-schema.test.ts +1 -1
- package/src/config/__tests__/default-profile-catalog.test.ts +83 -2
- package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +1 -1
- package/src/config/__tests__/memory-retrospective-schema.test.ts +12 -15
- package/src/config/bundled-skills/computer-use/SKILL.md +6 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +9 -6
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +3 -1
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +16 -15
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -1
- package/src/config/bundled-skills/settings/TOOLS.json +11 -5
- package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +85 -0
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +33 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +54 -3
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +1 -1
- package/src/config/default-profile-catalog.ts +195 -10
- package/src/config/default-profile-names.ts +41 -0
- package/src/config/feature-flag-registry.json +34 -2
- package/src/config/inference-profile-validation.ts +11 -2
- package/src/config/llm-resolver.ts +25 -0
- package/src/config/loader.ts +3 -0
- package/src/config/managed-speech-defaults.ts +160 -10
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/live-voice.test.ts +7 -6
- package/src/config/schemas/__tests__/stt.test.ts +94 -0
- package/src/config/schemas/backup.ts +1 -1
- package/src/config/schemas/channels.ts +32 -0
- package/src/config/schemas/live-voice.ts +13 -4
- package/src/config/schemas/llm.ts +263 -15
- package/src/config/schemas/mcp.ts +10 -2
- package/src/config/schemas/memory-retrospective.ts +1 -19
- package/src/config/schemas/services.ts +24 -0
- package/src/config/schemas/stt.ts +128 -5
- package/src/config/seed-inference-profiles.ts +32 -3
- package/src/config/skill-state.ts +5 -0
- package/src/config/skills.ts +21 -0
- package/src/contacts/notify-contacts-changed.ts +5 -2
- package/src/context/compactor.ts +74 -1
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +138 -1
- package/src/daemon/config-watcher.ts +2 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +27 -0
- package/src/daemon/conversation-agent-loop.ts +86 -10
- package/src/daemon/conversation-error.ts +25 -0
- package/src/daemon/conversation-evictor.ts +2 -4
- package/src/daemon/conversation-runtime-assembly.ts +19 -7
- package/src/daemon/conversation-skill-tools.ts +5 -1
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-store.ts +8 -1
- package/src/daemon/conversation-tool-setup.ts +37 -0
- package/src/daemon/conversation.ts +0 -4
- package/src/daemon/handlers/channel-config-result.ts +30 -0
- package/src/daemon/handlers/config-discord-channel.ts +301 -0
- package/src/daemon/handlers/config-ingress.ts +9 -0
- package/src/daemon/handlers/config-slack-channel.ts +13 -15
- package/src/daemon/handlers/config-telegram.ts +2 -5
- package/src/daemon/handlers/conversations.ts +0 -2
- package/src/daemon/handlers/skills.ts +25 -7
- package/src/daemon/lifecycle.ts +3 -1
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/tool-setup-types.ts +5 -3
- package/src/daemon/trust-context-types.test.ts +33 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +58 -4
- package/src/live-voice/__tests__/live-voice-managed-speech-fallback.test.ts +8 -3
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +73 -11
- package/src/live-voice/__tests__/live-voice-session-preflight.test.ts +148 -2
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +64 -0
- package/src/live-voice/__tests__/live-voice-stt.test.ts +3 -0
- package/src/live-voice/__tests__/live-voice-text-turn.test.ts +391 -0
- package/src/live-voice/__tests__/protocol.test.ts +224 -0
- package/src/live-voice/live-voice-credential-preflight.ts +5 -1
- package/src/live-voice/live-voice-session-manager.ts +39 -2
- package/src/live-voice/live-voice-session.ts +224 -9
- package/src/live-voice/protocol.ts +187 -1
- package/src/messaging/providers/discord/api.ts +2 -1
- package/src/messaging/providers/discord/withdraw.ts +62 -0
- package/src/messaging/read-provider-metadata.ts +32 -0
- package/src/monitoring/__tests__/file-descriptors.test.ts +32 -0
- package/src/monitoring/__tests__/process-memory.test.ts +34 -1
- package/src/monitoring/db-integrity-sample.ts +1 -0
- package/src/monitoring/file-descriptors.ts +39 -6
- package/src/monitoring/process-memory.ts +37 -5
- package/src/monitoring/resource-sampler.ts +25 -8
- package/src/notifications/__tests__/activity-failed-dedupe.test.ts +161 -0
- package/src/notifications/__tests__/background-failure-signal.test.ts +107 -0
- package/src/notifications/__tests__/connected-channels.test.ts +12 -0
- package/src/notifications/__tests__/copy-composer.test.ts +83 -0
- package/src/notifications/__tests__/destination-resolver.test.ts +38 -0
- package/src/notifications/activity-failed-dedupe.ts +95 -0
- package/src/notifications/adapters/discord.ts +132 -0
- package/src/notifications/adapters/shared.ts +16 -0
- package/src/notifications/adapters/slack.ts +6 -1
- package/src/notifications/adapters/telegram.ts +5 -8
- package/src/notifications/approval-card-data.ts +94 -1
- package/src/notifications/background-failure-signal.ts +116 -0
- package/src/notifications/broadcaster.ts +3 -9
- package/src/notifications/copy-composer.ts +41 -8
- package/src/notifications/decision-engine.ts +55 -0
- package/src/notifications/destination-resolver.ts +45 -5
- package/src/notifications/emit-signal.ts +19 -5
- package/src/notifications/guardian-question-mode.ts +57 -0
- package/src/notifications/types.ts +2 -8
- package/src/oauth/__tests__/seed-providers-managed.test.ts +18 -0
- package/src/oauth/connect-orchestrator.ts +16 -0
- package/src/oauth/seed-providers.ts +262 -0
- package/src/permissions/checker.test.ts +21 -0
- package/src/permissions/checker.ts +6 -0
- package/src/permissions/gateway-threshold-reader.test.ts +184 -0
- package/src/permissions/gateway-threshold-reader.ts +144 -10
- package/src/permissions/prompter.ts +0 -6
- package/src/permissions/types.ts +5 -4
- package/src/persistence/__tests__/bulk-write-gate.test.ts +0 -38
- package/src/persistence/bulk-write-gate.ts +9 -10
- package/src/persistence/conversation-crud.ts +88 -134
- package/src/persistence/conversation-title-service.ts +43 -29
- package/src/persistence/embeddings/embedding-local.ts +14 -85
- package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -0
- package/src/persistence/lifecycle-events-store.test.ts +50 -0
- package/src/persistence/lifecycle-events-store.ts +75 -5
- package/src/platform/platform-patch-queue.test.ts +368 -0
- package/src/platform/platform-patch-queue.ts +190 -0
- package/src/platform/sync-avatar.test.ts +527 -0
- package/src/platform/sync-avatar.ts +209 -0
- package/src/platform/sync-identity.test.ts +75 -0
- package/src/platform/sync-identity.ts +17 -110
- package/src/plugin-api/model-profiles.test.ts +2 -2
- package/src/plugin-api/model-profiles.ts +7 -7
- package/src/plugin-api/transcription-session.ts +1 -1
- package/src/plugins/defaults/compaction/window-manager.ts +4 -0
- package/src/plugins/defaults/image-recovery/recover.ts +2 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +133 -34
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +21 -31
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +13 -9
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +31 -4
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +0 -11
- package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +27 -13
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +183 -113
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +13 -13
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +2 -2
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +1 -1
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +4 -2
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +9 -42
- package/src/plugins/defaults/memory/v2/rerank-local.ts +1 -0
- package/src/plugins/defaults/memory/worker.ts +7 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +55 -0
- package/src/providers/__tests__/vellum-connection-routing.test.ts +21 -3
- package/src/providers/connection-resolution.ts +2 -17
- package/src/providers/fallback-breaker.ts +389 -0
- package/src/providers/inference/__tests__/adapter-factory-opencode.test.ts +92 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +16 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +12 -0
- package/src/providers/inference/adapter-factory.ts +246 -3
- package/src/providers/inference/auth.ts +15 -14
- package/src/providers/inference/chatgpt-subscription-auth.ts +70 -0
- package/src/providers/inference/chatgpt-subscription-credentials.ts +84 -0
- package/src/providers/inference/resolve-auth.ts +1 -1
- package/src/providers/media-resolve.ts +2 -3
- package/src/providers/model-catalog.ts +100 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +22 -0
- package/src/providers/openai/__tests__/chat-template-flatten.test.ts +198 -0
- package/src/providers/openai/api-error-normalization.ts +13 -0
- package/src/providers/openai/chat-completions-provider.ts +84 -0
- package/src/providers/opencode/client.test.ts +150 -0
- package/src/providers/opencode/client.ts +61 -0
- package/src/providers/platform-proxy/constants.ts +5 -0
- package/src/providers/provider-resolvability.ts +20 -0
- package/src/providers/provider-secret-catalog.ts +3 -2
- package/src/providers/retry.ts +956 -68
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +42 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +136 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +120 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +316 -12
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +83 -3
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +132 -4
- package/src/providers/speech-to-text/provider-catalog.ts +191 -3
- package/src/providers/speech-to-text/resolve.ts +132 -24
- package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +296 -0
- package/src/providers/types.ts +39 -0
- package/src/providers/usage-tracking.ts +12 -1
- package/src/providers/vellum/client.ts +29 -0
- package/src/providers/vellum-model-routing.test.ts +2 -0
- package/src/providers/vellum-model-routing.ts +3 -3
- package/src/runtime/AGENTS.md +1 -2
- package/src/runtime/__tests__/agent-wake.test.ts +105 -3
- package/src/runtime/__tests__/background-job-runner.test.ts +89 -3
- package/src/runtime/agent-wake.ts +44 -5
- package/src/runtime/background-job-runner.ts +68 -50
- package/src/runtime/channel-readiness-service.ts +0 -39
- package/src/runtime/http-server.ts +12 -4
- package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +18 -18
- package/src/runtime/routes/__tests__/backup-routes.test.ts +9 -5
- package/src/runtime/routes/__tests__/chatgpt-subscription-auth-routes.test.ts +372 -0
- package/src/runtime/routes/__tests__/contact-routes.test.ts +7 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +96 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +37 -0
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +2 -1
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +87 -0
- package/src/runtime/routes/acp-claude-auth-routes.ts +25 -54
- package/src/runtime/routes/approval-routes.ts +0 -12
- package/src/runtime/routes/avatar-routes.ts +17 -30
- package/src/runtime/routes/backup-routes.ts +2 -2
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +296 -113
- package/src/runtime/routes/contact-prompt-routes.ts +65 -6
- package/src/runtime/routes/contact-routes.ts +20 -15
- package/src/runtime/routes/conversation-query-routes.ts +98 -10
- package/src/runtime/routes/conversation-routes.ts +2 -2
- package/src/runtime/routes/credential-routes.ts +2 -1
- package/src/runtime/routes/guardian-approval-interception.ts +12 -9
- package/src/runtime/routes/inbound-message-handler.ts +47 -48
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +18 -13
- package/src/runtime/routes/inbound-stages/admission-policy.ts +2 -2
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +20 -2
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +9 -8
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
- package/src/runtime/routes/inference-profiles-routes.ts +12 -7
- package/src/runtime/routes/inference-provider-connection-routes.ts +2 -2
- package/src/runtime/routes/inference-send-routes.ts +2 -2
- package/src/runtime/routes/ingress-status-routes.ts +2 -2
- package/src/runtime/routes/integrations/discord.ts +119 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +2 -2
- package/src/runtime/routes/mcp-auth-routes.ts +12 -6
- package/src/runtime/routes/migration-routes.ts +3 -1
- package/src/runtime/routes/oauth-pending-flows.ts +136 -0
- package/src/runtime/routes/secret-routes.ts +7 -0
- package/src/runtime/routes/stt-routes.ts +38 -6
- package/src/runtime/routes/wake-conversation-routes.ts +8 -1
- package/src/runtime/routes/watch-routes.ts +9 -5
- package/src/runtime/sync/resource-sync-events.ts +2 -0
- package/src/runtime/trust-verdict-consumer.ts +3 -3
- package/src/schedule/__tests__/worker-feature-flags.test.ts +58 -0
- package/src/schedule/retry-policy.ts +8 -8
- package/src/schedule/run-script.ts +14 -23
- package/src/schedule/schedule-recovery.ts +0 -1
- package/src/schedule/scheduler.ts +89 -51
- package/src/schedule/worker.ts +57 -0
- package/src/security/__tests__/openai-device-auth.test.ts +355 -0
- package/src/security/ces-rpc-record-backend.ts +123 -0
- package/src/security/oauth2.ts +7 -0
- package/src/security/openai-device-auth.ts +364 -0
- package/src/security/secure-keys.ts +15 -0
- package/src/skills/available-skills.ts +10 -1
- package/src/skills/catalog-install.ts +19 -1
- package/src/skills/inline-command-runner.ts +6 -2
- package/src/skills/platform-compatibility.test.ts +49 -0
- package/src/skills/platform-compatibility.ts +65 -0
- package/src/skills/tool-manifest.ts +20 -0
- package/src/stt/__tests__/roles.test.ts +152 -0
- package/src/stt/daemon-batch-transcriber.ts +4 -1
- package/src/stt/roles.ts +180 -0
- package/src/stt/stt-stream-session.ts +3 -2
- package/src/stt/types.ts +33 -3
- package/src/subagent/manager.ts +12 -0
- package/src/telemetry/__tests__/live-voice-funnel.test.ts +34 -0
- package/src/telemetry/live-voice-funnel.ts +12 -2
- package/src/telemetry/telemetry-wire.generated.ts +6 -0
- package/src/telemetry/tool-audit.ts +83 -30
- package/src/telemetry/tool-executed-events-store.test.ts +0 -1
- package/src/telemetry/turn-outcome.ts +75 -4
- package/src/tools/client-os.ts +115 -0
- package/src/tools/computer-use/definitions.ts +11 -7
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -2
- package/src/tools/credentials/broker-types.ts +0 -71
- package/src/tools/credentials/broker.ts +6 -201
- package/src/tools/credentials/metadata-store.ts +65 -16
- package/src/tools/credentials/store.ts +2 -0
- package/src/tools/executor.ts +2 -8
- package/src/tools/host-shell.test.ts +75 -0
- package/src/tools/host-shell.ts +24 -0
- package/src/tools/host-terminal/host-shell.ts +15 -44
- package/src/tools/mcp/mcp-tool-factory.ts +44 -9
- package/src/tools/permission-checker.ts +73 -21
- package/src/tools/policy-context.ts +1 -0
- package/src/tools/shared/filesystem/image-read.ts +1 -1
- package/src/tools/skills/find-similar-skills.ts +10 -2
- package/src/tools/skills/load.ts +41 -6
- package/src/tools/skills/sandbox-runner.ts +8 -16
- package/src/tools/skills/skill-tool-factory.ts +1 -0
- package/src/tools/terminal/__tests__/safe-env.test.ts +64 -1
- package/src/tools/terminal/safe-env.ts +39 -9
- package/src/tools/terminal/shell.ts +12 -31
- package/src/tools/tool-approval-handler.ts +64 -10
- package/src/tools/tool-types.ts +10 -0
- package/src/tools/types.ts +19 -3
- package/src/tools/ui-surface/surface-shape-docs.ts +4 -3
- package/src/tools/workflows/manage-workflows.ts +2 -2
- package/src/tools/workflows/run-workflow.test.ts +2 -1
- package/src/util/__tests__/file-use.test.ts +61 -0
- package/src/util/__tests__/process-table.test.ts +66 -0
- package/src/util/__tests__/process-tree.test.ts +46 -0
- package/src/util/errors.ts +1 -0
- package/src/util/file-use.ts +91 -0
- package/src/util/host-process.test.ts +17 -0
- package/src/util/host-process.ts +75 -0
- package/src/util/image-conversion.ts +39 -7
- package/src/util/platform.ts +32 -13
- package/src/util/process-table.ts +249 -0
- package/src/util/process-tree.ts +85 -129
- package/src/util/provider-error-patterns.ts +20 -0
- package/src/util/spawn.ts +9 -2
- package/src/watch/__tests__/watch-retro.test.ts +48 -1
- package/src/watch/watch-retro.ts +46 -4
- package/src/workspace/git-service.ts +4 -14
- package/src/workspace/migrations/147-rename-colliding-backup-profile-names.ts +360 -0
- package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +117 -0
- package/src/workspace/migrations/149-repoint-backup-profile-selections.ts +204 -0
- package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +139 -0
- package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +160 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/permission-types.test.ts +0 -17
- package/src/channels/gateway-discord-admission.ts +0 -32
- package/src/persistence/fork-message-copy.ts +0 -239
- package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +0 -221
- package/src/security/__tests__/oauth2-device-code.test.ts +0 -431
- package/src/security/oauth2-device-code.ts +0 -312
|
@@ -103,13 +103,16 @@ const auditCalls = {
|
|
|
103
103
|
denied: [] as any[],
|
|
104
104
|
error: [] as any[],
|
|
105
105
|
executed: [] as any[],
|
|
106
|
-
prompted: [] as
|
|
106
|
+
prompted: [] as any[],
|
|
107
|
+
decided: [] as any[],
|
|
107
108
|
};
|
|
108
109
|
mock.module("../telemetry/tool-audit.js", () => ({
|
|
109
110
|
recordToolDenied: (e: any) => auditCalls.denied.push(e),
|
|
110
111
|
recordToolError: (e: any) => auditCalls.error.push(e),
|
|
111
112
|
recordToolExecuted: (e: any) => auditCalls.executed.push(e),
|
|
112
|
-
recordToolPermissionPrompted: (
|
|
113
|
+
recordToolPermissionPrompted: (e: any) => auditCalls.prompted.push(e),
|
|
114
|
+
recordToolPermissionDecided: (e: any, outcome: string) =>
|
|
115
|
+
auditCalls.decided.push({ entry: e, outcome }),
|
|
113
116
|
}));
|
|
114
117
|
|
|
115
118
|
function resetAuditCalls(): void {
|
|
@@ -117,6 +120,7 @@ function resetAuditCalls(): void {
|
|
|
117
120
|
auditCalls.error.length = 0;
|
|
118
121
|
auditCalls.executed.length = 0;
|
|
119
122
|
auditCalls.prompted.length = 0;
|
|
123
|
+
auditCalls.decided.length = 0;
|
|
120
124
|
}
|
|
121
125
|
|
|
122
126
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
@@ -946,6 +950,8 @@ describe("ToolApprovalHandler / approval cell lifts the sensitive-tool floor", (
|
|
|
946
950
|
}
|
|
947
951
|
|
|
948
952
|
// Running code in the workspace is how you write everything else in it.
|
|
953
|
+
// A room cell cannot lift bash. A contact-level ceiling can; that path
|
|
954
|
+
// is covered in the contact-ceiling describe below.
|
|
949
955
|
test("sandbox bash", async () => {
|
|
950
956
|
await expectFloored("bash", { command: "ls" });
|
|
951
957
|
});
|
|
@@ -1024,6 +1030,144 @@ describe("ToolApprovalHandler / approval cell lifts the sensitive-tool floor", (
|
|
|
1024
1030
|
});
|
|
1025
1031
|
});
|
|
1026
1032
|
|
|
1033
|
+
describe("contact ceiling lifts sandbox bash", () => {
|
|
1034
|
+
beforeEach(() => {
|
|
1035
|
+
// A Full-access cell still cannot lift bash. These cases pass a
|
|
1036
|
+
// contact ceiling so the lift comes from the person, not the room.
|
|
1037
|
+
thresholdReaderMock.cell = cell("high");
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
test.each(["low", "medium", "high"] as const)(
|
|
1041
|
+
"a %s contact ceiling lifts sandbox bash — no scoped grant required",
|
|
1042
|
+
async (threshold) => {
|
|
1043
|
+
thresholdReaderMock.contactThreshold = threshold;
|
|
1044
|
+
const result = await handler.checkPreExecutionGates(
|
|
1045
|
+
"bash",
|
|
1046
|
+
{ command: "ls" },
|
|
1047
|
+
channelContext({ requesterContactId: "contact-1" }),
|
|
1048
|
+
"low",
|
|
1049
|
+
Date.now(),
|
|
1050
|
+
);
|
|
1051
|
+
|
|
1052
|
+
expect(result.allowed).toBe(true);
|
|
1053
|
+
if (!result.allowed) {
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
expect(result.grantConsumed).toBeFalsy();
|
|
1057
|
+
expect(auditCalls.denied.length).toBe(0);
|
|
1058
|
+
// Contact ceiling is already on the turn; the cell is not consulted.
|
|
1059
|
+
expect(thresholdReaderMock.cellLookups).toBe(0);
|
|
1060
|
+
},
|
|
1061
|
+
);
|
|
1062
|
+
|
|
1063
|
+
test("a none contact ceiling leaves sandbox bash on the floor", async () => {
|
|
1064
|
+
thresholdReaderMock.contactThreshold = "none";
|
|
1065
|
+
const result = await handler.checkPreExecutionGates(
|
|
1066
|
+
"bash",
|
|
1067
|
+
{ command: "ls" },
|
|
1068
|
+
channelContext({ requesterContactId: "contact-1" }),
|
|
1069
|
+
"low",
|
|
1070
|
+
Date.now(),
|
|
1071
|
+
);
|
|
1072
|
+
|
|
1073
|
+
expect(result.allowed).toBe(false);
|
|
1074
|
+
if (result.allowed) {
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
expect(result.result.content).toContain("requires guardian approval");
|
|
1078
|
+
expect(thresholdReaderMock.cellLookups).toBe(0);
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
test("an unset contact ceiling leaves sandbox bash on the floor", async () => {
|
|
1082
|
+
const result = await handler.checkPreExecutionGates(
|
|
1083
|
+
"bash",
|
|
1084
|
+
{ command: "ls" },
|
|
1085
|
+
channelContext(),
|
|
1086
|
+
"low",
|
|
1087
|
+
Date.now(),
|
|
1088
|
+
);
|
|
1089
|
+
|
|
1090
|
+
expect(result.allowed).toBe(false);
|
|
1091
|
+
expect(thresholdReaderMock.cellLookups).toBe(0);
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
test("a high contact ceiling does not lift host_bash", async () => {
|
|
1095
|
+
thresholdReaderMock.contactThreshold = "high";
|
|
1096
|
+
const result = await handler.checkPreExecutionGates(
|
|
1097
|
+
"host_bash",
|
|
1098
|
+
{ command: "ls" },
|
|
1099
|
+
channelContext({ requesterContactId: "contact-1" }),
|
|
1100
|
+
"low",
|
|
1101
|
+
Date.now(),
|
|
1102
|
+
);
|
|
1103
|
+
|
|
1104
|
+
expect(result.allowed).toBe(false);
|
|
1105
|
+
if (result.allowed) {
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
expect(result.result.content).toContain("requires guardian approval");
|
|
1109
|
+
expect(thresholdReaderMock.cellLookups).toBe(0);
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
test("a high contact ceiling does not lift a control-plane write", async () => {
|
|
1113
|
+
thresholdReaderMock.contactThreshold = "high";
|
|
1114
|
+
const result = await handler.checkPreExecutionGates(
|
|
1115
|
+
"file_write",
|
|
1116
|
+
{ path: "SOUL.md", content: "x" },
|
|
1117
|
+
channelContext({ requesterContactId: "contact-1" }),
|
|
1118
|
+
"low",
|
|
1119
|
+
Date.now(),
|
|
1120
|
+
);
|
|
1121
|
+
|
|
1122
|
+
expect(result.allowed).toBe(false);
|
|
1123
|
+
if (result.allowed) {
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
expect(result.result.content).toContain("requires guardian approval");
|
|
1127
|
+
expect(thresholdReaderMock.cellLookups).toBe(0);
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
test("a high contact ceiling lifts an ordinary workspace write even when the cell is Strict", async () => {
|
|
1131
|
+
thresholdReaderMock.cell = cell("none");
|
|
1132
|
+
thresholdReaderMock.contactThreshold = "high";
|
|
1133
|
+
|
|
1134
|
+
const result = await handler.checkPreExecutionGates(
|
|
1135
|
+
"file_write",
|
|
1136
|
+
{ path: "notes/todo.md", content: "x" },
|
|
1137
|
+
channelContext({ requesterContactId: "contact-1" }),
|
|
1138
|
+
"low",
|
|
1139
|
+
Date.now(),
|
|
1140
|
+
);
|
|
1141
|
+
|
|
1142
|
+
expect(result.allowed).toBe(true);
|
|
1143
|
+
if (!result.allowed) {
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
expect(result.grantConsumed).toBeFalsy();
|
|
1147
|
+
expect(thresholdReaderMock.cellLookups).toBe(0);
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1150
|
+
test("a none contact ceiling leaves an ordinary workspace write on the floor even when the cell is Full access", async () => {
|
|
1151
|
+
thresholdReaderMock.cell = cell("high");
|
|
1152
|
+
thresholdReaderMock.contactThreshold = "none";
|
|
1153
|
+
|
|
1154
|
+
const result = await handler.checkPreExecutionGates(
|
|
1155
|
+
"file_write",
|
|
1156
|
+
{ path: "notes/todo.md", content: "x" },
|
|
1157
|
+
channelContext({ requesterContactId: "contact-1" }),
|
|
1158
|
+
"low",
|
|
1159
|
+
Date.now(),
|
|
1160
|
+
);
|
|
1161
|
+
|
|
1162
|
+
expect(result.allowed).toBe(false);
|
|
1163
|
+
if (result.allowed) {
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
expect(result.result.content).toContain("requires guardian approval");
|
|
1167
|
+
expect(thresholdReaderMock.cellLookups).toBe(0);
|
|
1168
|
+
});
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1027
1171
|
test("a public web_fetch is still lifted", async () => {
|
|
1028
1172
|
thresholdReaderMock.cell = cell("low");
|
|
1029
1173
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
+
import { buildApprovalNotificationBlocks } from "../notifications/adapters/slack.js";
|
|
3
4
|
import type {
|
|
4
5
|
ApprovalCardBlock,
|
|
5
6
|
ApprovalCardFallbackBlock,
|
|
@@ -90,9 +91,11 @@ describe("buildToolApprovalSeedContentBlocks", () => {
|
|
|
90
91
|
});
|
|
91
92
|
});
|
|
92
93
|
|
|
93
|
-
test("
|
|
94
|
+
test("pending_question without toolName renders as a question, not an approval", () => {
|
|
94
95
|
const payload = { ...voiceToolApprovalPayload, toolName: undefined };
|
|
95
|
-
|
|
96
|
+
const surface = surfaceBlock(buildToolApprovalSeedContentBlocks(payload)!);
|
|
97
|
+
expect(surface.title).toBe("Question");
|
|
98
|
+
expect(surface.data.subtitle).toBe("Waiting on your answer");
|
|
96
99
|
});
|
|
97
100
|
|
|
98
101
|
test("produces a ui_surface block and a text fallback block for tool_approval", () => {
|
|
@@ -317,3 +320,106 @@ describe("buildToolApprovalSeedContentBlocks", () => {
|
|
|
317
320
|
expect(fallback.text).toContain("Approve tool: bash (requested by Bob)");
|
|
318
321
|
});
|
|
319
322
|
});
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* An `ask_question` prompt promoted to a guardian request. It renders as a
|
|
326
|
+
* question rather than an approval, and carries no buttons: the in-app surface
|
|
327
|
+
* route resolves approval actions only, so answering happens on the channel or
|
|
328
|
+
* by reply.
|
|
329
|
+
*/
|
|
330
|
+
describe("ask_question card", () => {
|
|
331
|
+
const questionPayload: Record<string, unknown> = {
|
|
332
|
+
requestId: "req-ask-321",
|
|
333
|
+
requestCode: "08B619",
|
|
334
|
+
requestKind: "pending_question",
|
|
335
|
+
questionText: "What should I dig into?",
|
|
336
|
+
sourceChannel: "slack",
|
|
337
|
+
options: [
|
|
338
|
+
{ id: "opt-thread", label: "This Slack thread" },
|
|
339
|
+
{ id: "opt-pr", label: "The pull request" },
|
|
340
|
+
{ id: "opt-ticket", label: "The Linear ticket" },
|
|
341
|
+
],
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
test("the question leads the card rather than generic guardian copy", () => {
|
|
345
|
+
const surface = surfaceBlock(
|
|
346
|
+
buildToolApprovalSeedContentBlocks(questionPayload)!,
|
|
347
|
+
);
|
|
348
|
+
expect(surface.title).toBe("Question");
|
|
349
|
+
expect(surface.data.title).toBe("What should I dig into?");
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("a question card offers no buttons, not even Approve and Reject", () => {
|
|
353
|
+
const surface = surfaceBlock(
|
|
354
|
+
buildToolApprovalSeedContentBlocks(questionPayload)!,
|
|
355
|
+
);
|
|
356
|
+
// Empty rather than absent: the builder reads an absent action set as a
|
|
357
|
+
// request for its generic Approve/Reject pair, which is the wrong verb for
|
|
358
|
+
// a question and would resolve as "Yes" or a skip if tapped. The answer
|
|
359
|
+
// options are not offered here either, because the in-app surface route
|
|
360
|
+
// resolves only approval actions, so they would be inert.
|
|
361
|
+
expect(surface.actions).toEqual([]);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test("a channel without buttons still gets the question and its options", () => {
|
|
365
|
+
const fallback = fallbackBlock(
|
|
366
|
+
buildToolApprovalSeedContentBlocks(questionPayload)!,
|
|
367
|
+
);
|
|
368
|
+
expect(fallback.text).toContain("What should I dig into?");
|
|
369
|
+
expect(fallback.text).toContain("1. This Slack thread");
|
|
370
|
+
expect(fallback.text).toContain("3. The Linear ticket");
|
|
371
|
+
expect(fallback.text).toContain("08B619");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test("a question with no options still offers no buttons", () => {
|
|
375
|
+
const payload = { ...questionPayload, options: undefined };
|
|
376
|
+
const surface = surfaceBlock(buildToolApprovalSeedContentBlocks(payload)!);
|
|
377
|
+
expect(surface.data.title).toBe("What should I dig into?");
|
|
378
|
+
expect(surface.actions).toEqual([]);
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* The Slack card heads itself from the approval's stated intent, so a question
|
|
384
|
+
* is not titled as an approval. Producers resolve the intent; the adapter only
|
|
385
|
+
* draws it.
|
|
386
|
+
*/
|
|
387
|
+
describe("Slack card intent", () => {
|
|
388
|
+
const base = {
|
|
389
|
+
sourceEventName: "guardian.question",
|
|
390
|
+
copy: { deliveryText: "What should I dig into?" },
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
test("a question card is headed Question", () => {
|
|
394
|
+
const blocks = buildApprovalNotificationBlocks(
|
|
395
|
+
{
|
|
396
|
+
...base,
|
|
397
|
+
approvalContext: {
|
|
398
|
+
requestId: "req-ask-321",
|
|
399
|
+
actions: [{ id: "answer_0", label: "This Slack thread" }],
|
|
400
|
+
plainTextFallback: 'Reply "08B619 <your answer>".',
|
|
401
|
+
intent: "question",
|
|
402
|
+
},
|
|
403
|
+
} as never,
|
|
404
|
+
"What should I dig into?",
|
|
405
|
+
);
|
|
406
|
+
const card = blocks[0] as { title?: { text?: string } };
|
|
407
|
+
expect(card.title?.text).toBe("Question");
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test("an approval with no tool details keeps its own heading", () => {
|
|
411
|
+
const blocks = buildApprovalNotificationBlocks(
|
|
412
|
+
{
|
|
413
|
+
...base,
|
|
414
|
+
approvalContext: {
|
|
415
|
+
requestId: "req-appr-1",
|
|
416
|
+
actions: [{ id: "approve_once", label: "Approve" }],
|
|
417
|
+
plainTextFallback: 'Reply "ABC123 approve".',
|
|
418
|
+
},
|
|
419
|
+
} as never,
|
|
420
|
+
"Approve this",
|
|
421
|
+
);
|
|
422
|
+
const card = blocks[0] as { title?: { text?: string } };
|
|
423
|
+
expect(card.title?.text).toBe("Approval Request");
|
|
424
|
+
});
|
|
425
|
+
});
|
|
@@ -8,10 +8,18 @@ mock.module("../platform/consent-cache.js", () => ({
|
|
|
8
8
|
getRawShareAnalytics: () => shareAnalytics,
|
|
9
9
|
}));
|
|
10
10
|
|
|
11
|
-
// Capture the audit rows the terminals would insert, and the lifecycle
|
|
12
|
-
//
|
|
11
|
+
// Capture the audit rows the terminals would insert, and the lifecycle events
|
|
12
|
+
// they would record, without touching the database.
|
|
13
13
|
const records: Array<Record<string, unknown>> = [];
|
|
14
|
-
const lifecycleEvents:
|
|
14
|
+
const lifecycleEvents: Array<{
|
|
15
|
+
eventName: string;
|
|
16
|
+
attributes: Record<string, unknown>;
|
|
17
|
+
}> = [];
|
|
18
|
+
const lifecycleEventNames = (): string[] =>
|
|
19
|
+
lifecycleEvents.map((event) => event.eventName);
|
|
20
|
+
// Simulates the telemetry store failing (degraded DB) for the one test that
|
|
21
|
+
// asserts the terminals swallow it.
|
|
22
|
+
let lifecycleRecorderThrows = false;
|
|
15
23
|
|
|
16
24
|
mock.module("../telemetry/tool-usage-store.js", () => ({
|
|
17
25
|
recordToolInvocation: (record: Record<string, unknown>) =>
|
|
@@ -19,13 +27,22 @@ mock.module("../telemetry/tool-usage-store.js", () => ({
|
|
|
19
27
|
}));
|
|
20
28
|
|
|
21
29
|
mock.module("../persistence/lifecycle-events-store.js", () => ({
|
|
22
|
-
recordLifecycleEvent: (
|
|
30
|
+
recordLifecycleEvent: (
|
|
31
|
+
eventName: string,
|
|
32
|
+
attributes: Record<string, unknown> = {},
|
|
33
|
+
) => {
|
|
34
|
+
if (lifecycleRecorderThrows) {
|
|
35
|
+
throw new Error("telemetry db down");
|
|
36
|
+
}
|
|
37
|
+
lifecycleEvents.push({ eventName, attributes });
|
|
38
|
+
},
|
|
23
39
|
}));
|
|
24
40
|
|
|
25
41
|
import {
|
|
26
42
|
recordToolDenied,
|
|
27
43
|
recordToolError,
|
|
28
44
|
recordToolExecuted,
|
|
45
|
+
recordToolPermissionDecided,
|
|
29
46
|
recordToolPermissionPrompted,
|
|
30
47
|
} from "../telemetry/tool-audit.js";
|
|
31
48
|
import {
|
|
@@ -48,6 +65,7 @@ const ATTRIBUTION = {
|
|
|
48
65
|
describe("tool audit terminals", () => {
|
|
49
66
|
beforeEach(() => {
|
|
50
67
|
shareAnalytics = true;
|
|
68
|
+
lifecycleRecorderThrows = false;
|
|
51
69
|
records.length = 0;
|
|
52
70
|
lifecycleEvents.length = 0;
|
|
53
71
|
});
|
|
@@ -63,7 +81,6 @@ describe("tool audit terminals", () => {
|
|
|
63
81
|
riskLevel: "low",
|
|
64
82
|
durationMs: 12,
|
|
65
83
|
attribution: null,
|
|
66
|
-
wasPrompted: false,
|
|
67
84
|
});
|
|
68
85
|
|
|
69
86
|
expect(records).toHaveLength(1);
|
|
@@ -84,7 +101,6 @@ describe("tool audit terminals", () => {
|
|
|
84
101
|
reason: "Blocked by deny rule: rm *",
|
|
85
102
|
riskLevel: "high",
|
|
86
103
|
durationMs: 20,
|
|
87
|
-
wasPrompted: false,
|
|
88
104
|
});
|
|
89
105
|
recordToolDenied({
|
|
90
106
|
conversationId: "conv-2",
|
|
@@ -93,7 +109,6 @@ describe("tool audit terminals", () => {
|
|
|
93
109
|
reason: "Permission denied by user",
|
|
94
110
|
riskLevel: "high",
|
|
95
111
|
durationMs: 22,
|
|
96
|
-
wasPrompted: false,
|
|
97
112
|
});
|
|
98
113
|
|
|
99
114
|
expect(records).toHaveLength(2);
|
|
@@ -118,7 +133,6 @@ describe("tool audit terminals", () => {
|
|
|
118
133
|
riskLevel: "low",
|
|
119
134
|
durationMs: 5,
|
|
120
135
|
attribution: null,
|
|
121
|
-
wasPrompted: false,
|
|
122
136
|
});
|
|
123
137
|
|
|
124
138
|
expect(records).toHaveLength(1);
|
|
@@ -141,7 +155,6 @@ describe("tool audit terminals", () => {
|
|
|
141
155
|
riskLevel: "low",
|
|
142
156
|
durationMs: 5,
|
|
143
157
|
attribution: null,
|
|
144
|
-
wasPrompted: false,
|
|
145
158
|
});
|
|
146
159
|
recordToolError({
|
|
147
160
|
conversationId: "conv-input-redact",
|
|
@@ -160,7 +173,6 @@ describe("tool audit terminals", () => {
|
|
|
160
173
|
reason: "Permission denied by user",
|
|
161
174
|
riskLevel: "high",
|
|
162
175
|
durationMs: 5,
|
|
163
|
-
wasPrompted: false,
|
|
164
176
|
});
|
|
165
177
|
|
|
166
178
|
expect(records).toHaveLength(3);
|
|
@@ -192,7 +204,6 @@ describe("tool audit terminals", () => {
|
|
|
192
204
|
riskLevel: "low",
|
|
193
205
|
durationMs: 2,
|
|
194
206
|
attribution: null,
|
|
195
|
-
wasPrompted: false,
|
|
196
207
|
});
|
|
197
208
|
|
|
198
209
|
expect(records).toHaveLength(1);
|
|
@@ -215,7 +226,6 @@ describe("tool audit terminals", () => {
|
|
|
215
226
|
riskLevel: "low",
|
|
216
227
|
durationMs: 2,
|
|
217
228
|
attribution: null,
|
|
218
|
-
wasPrompted: false,
|
|
219
229
|
});
|
|
220
230
|
|
|
221
231
|
expect(records).toHaveLength(1);
|
|
@@ -246,7 +256,6 @@ describe("tool audit terminals", () => {
|
|
|
246
256
|
riskLevel: "low",
|
|
247
257
|
durationMs: 3,
|
|
248
258
|
attribution: null,
|
|
249
|
-
wasPrompted: false,
|
|
250
259
|
});
|
|
251
260
|
|
|
252
261
|
expect(records).toHaveLength(1);
|
|
@@ -289,7 +298,6 @@ describe("tool audit terminals", () => {
|
|
|
289
298
|
riskLevel: "low",
|
|
290
299
|
durationMs: 12,
|
|
291
300
|
attribution: null,
|
|
292
|
-
wasPrompted: false,
|
|
293
301
|
});
|
|
294
302
|
|
|
295
303
|
expect(records).toHaveLength(1);
|
|
@@ -316,7 +324,6 @@ describe("tool audit terminals", () => {
|
|
|
316
324
|
riskLevel: "low",
|
|
317
325
|
durationMs: 4,
|
|
318
326
|
attribution: null,
|
|
319
|
-
wasPrompted: false,
|
|
320
327
|
});
|
|
321
328
|
|
|
322
329
|
expect(records).toHaveLength(1);
|
|
@@ -339,7 +346,6 @@ describe("tool audit terminals", () => {
|
|
|
339
346
|
riskLevel: "low",
|
|
340
347
|
durationMs: 12,
|
|
341
348
|
attribution: ATTRIBUTION,
|
|
342
|
-
wasPrompted: false,
|
|
343
349
|
});
|
|
344
350
|
recordToolError({
|
|
345
351
|
conversationId: "conv-opt-out",
|
|
@@ -395,7 +401,6 @@ describe("tool audit terminals", () => {
|
|
|
395
401
|
riskLevel: "low",
|
|
396
402
|
durationMs: 12,
|
|
397
403
|
attribution: ATTRIBUTION,
|
|
398
|
-
wasPrompted: false,
|
|
399
404
|
});
|
|
400
405
|
|
|
401
406
|
expect(records).toHaveLength(1);
|
|
@@ -420,7 +425,6 @@ describe("tool audit terminals", () => {
|
|
|
420
425
|
riskLevel: "low",
|
|
421
426
|
durationMs: 12,
|
|
422
427
|
attribution: ATTRIBUTION,
|
|
423
|
-
wasPrompted: false,
|
|
424
428
|
});
|
|
425
429
|
recordToolExecuted({
|
|
426
430
|
conversationId: "conv-attr",
|
|
@@ -432,7 +436,6 @@ describe("tool audit terminals", () => {
|
|
|
432
436
|
riskLevel: "low",
|
|
433
437
|
durationMs: 3,
|
|
434
438
|
attribution: null,
|
|
435
|
-
wasPrompted: false,
|
|
436
439
|
});
|
|
437
440
|
recordToolDenied({
|
|
438
441
|
conversationId: "conv-attr",
|
|
@@ -441,7 +444,6 @@ describe("tool audit terminals", () => {
|
|
|
441
444
|
reason: "Permission denied by user",
|
|
442
445
|
riskLevel: "high",
|
|
443
446
|
durationMs: 1,
|
|
444
|
-
wasPrompted: false,
|
|
445
447
|
});
|
|
446
448
|
|
|
447
449
|
expect(records).toHaveLength(3);
|
|
@@ -462,9 +464,7 @@ describe("tool audit terminals", () => {
|
|
|
462
464
|
expect(records[2].resultBytes).toBeUndefined();
|
|
463
465
|
});
|
|
464
466
|
|
|
465
|
-
test("
|
|
466
|
-
recordToolPermissionPrompted("bash");
|
|
467
|
-
// Prompted allow → executed records a decided event.
|
|
467
|
+
test("audit terminals record no permission lifecycle telemetry", () => {
|
|
468
468
|
recordToolExecuted({
|
|
469
469
|
conversationId: "conv-prompt",
|
|
470
470
|
toolName: "bash",
|
|
@@ -475,9 +475,7 @@ describe("tool audit terminals", () => {
|
|
|
475
475
|
riskLevel: "high",
|
|
476
476
|
durationMs: 3,
|
|
477
477
|
attribution: null,
|
|
478
|
-
wasPrompted: true,
|
|
479
478
|
});
|
|
480
|
-
// Prompted denial → denied records a decided event.
|
|
481
479
|
recordToolDenied({
|
|
482
480
|
conversationId: "conv-prompt",
|
|
483
481
|
toolName: "bash",
|
|
@@ -485,26 +483,99 @@ describe("tool audit terminals", () => {
|
|
|
485
483
|
reason: "Permission denied by user",
|
|
486
484
|
riskLevel: "high",
|
|
487
485
|
durationMs: 1,
|
|
488
|
-
wasPrompted: true,
|
|
489
486
|
});
|
|
490
|
-
|
|
491
|
-
|
|
487
|
+
|
|
488
|
+
expect(lifecycleEvents).toEqual([]);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("prompt and decision carry the same grouping dimensions", () => {
|
|
492
|
+
const prompt = {
|
|
493
|
+
toolName: "bash",
|
|
494
|
+
riskLevel: "high",
|
|
495
|
+
riskThreshold: "low",
|
|
496
|
+
surface: "slack",
|
|
492
497
|
conversationId: "conv-prompt",
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
decision: "allow",
|
|
498
|
-
riskLevel: "low",
|
|
499
|
-
durationMs: 1,
|
|
500
|
-
attribution: null,
|
|
501
|
-
wasPrompted: false,
|
|
502
|
-
});
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
recordToolPermissionPrompted(prompt);
|
|
501
|
+
recordToolPermissionDecided(prompt, "deny");
|
|
503
502
|
|
|
504
|
-
expect(
|
|
503
|
+
expect(lifecycleEventNames()).toEqual([
|
|
505
504
|
"permission_prompt:bash",
|
|
506
|
-
"permission_decided:bash:allow",
|
|
507
505
|
"permission_decided:bash:deny",
|
|
508
506
|
]);
|
|
507
|
+
for (const event of lifecycleEvents) {
|
|
508
|
+
expect(event.attributes).toEqual({
|
|
509
|
+
toolName: "bash",
|
|
510
|
+
riskLevel: "high",
|
|
511
|
+
riskThreshold: "low",
|
|
512
|
+
surface: "slack",
|
|
513
|
+
conversationId: "conv-prompt",
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test("optional grouping dimensions are left unset when unknown", () => {
|
|
519
|
+
recordToolPermissionPrompted({ toolName: "file_read", riskLevel: "low" });
|
|
520
|
+
|
|
521
|
+
const attributes = lifecycleEvents[0]!.attributes;
|
|
522
|
+
expect(attributes.toolName).toBe("file_read");
|
|
523
|
+
expect(attributes.riskLevel).toBe("low");
|
|
524
|
+
expect(attributes.riskThreshold).toBeUndefined();
|
|
525
|
+
expect(attributes.surface).toBeUndefined();
|
|
526
|
+
expect(attributes.conversationId).toBeUndefined();
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
test("records an abandoned decision distinctly from a user denial", () => {
|
|
530
|
+
const prompt = { toolName: "bash", riskLevel: "medium" };
|
|
531
|
+
|
|
532
|
+
recordToolPermissionPrompted(prompt);
|
|
533
|
+
recordToolPermissionDecided(prompt, "abandoned");
|
|
534
|
+
|
|
535
|
+
expect(lifecycleEventNames()).toEqual([
|
|
536
|
+
"permission_prompt:bash",
|
|
537
|
+
"permission_decided:bash:abandoned",
|
|
538
|
+
]);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test("truncates a long tool name to the wire event-name bound", () => {
|
|
542
|
+
const toolName = `mcp__${"a".repeat(80)}__run`;
|
|
543
|
+
const prompt = { toolName, riskLevel: "high" };
|
|
544
|
+
|
|
545
|
+
recordToolPermissionPrompted(prompt);
|
|
546
|
+
recordToolPermissionDecided(prompt, "abandoned");
|
|
547
|
+
|
|
548
|
+
for (const event of lifecycleEvents) {
|
|
549
|
+
expect(event.eventName.length).toBeLessThanOrEqual(64);
|
|
550
|
+
// The full name still joins the pair.
|
|
551
|
+
expect(event.attributes.toolName).toBe(toolName);
|
|
552
|
+
}
|
|
553
|
+
expect(lifecycleEvents[1]!.eventName.endsWith(":abandoned")).toBe(true);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
test("clamps a tool name past the wire tool_name bound", () => {
|
|
557
|
+
// A field over its bound fails validation, and a failed event is acked and
|
|
558
|
+
// discarded rather than retried.
|
|
559
|
+
const toolName = `mcp__${"a".repeat(300)}__run`;
|
|
560
|
+
|
|
561
|
+
recordToolPermissionPrompted({ toolName, riskLevel: "high" });
|
|
562
|
+
|
|
563
|
+
expect(lifecycleEvents[0]!.attributes.toolName).toBe(
|
|
564
|
+
toolName.slice(0, 255),
|
|
565
|
+
);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
test("a failing lifecycle recorder never propagates out of the terminals", () => {
|
|
569
|
+
lifecycleRecorderThrows = true;
|
|
570
|
+
|
|
571
|
+
expect(() =>
|
|
572
|
+
recordToolPermissionPrompted({ toolName: "bash", riskLevel: "high" }),
|
|
573
|
+
).not.toThrow();
|
|
574
|
+
expect(() =>
|
|
575
|
+
recordToolPermissionDecided(
|
|
576
|
+
{ toolName: "bash", riskLevel: "high" },
|
|
577
|
+
"abandoned",
|
|
578
|
+
),
|
|
579
|
+
).not.toThrow();
|
|
509
580
|
});
|
|
510
581
|
});
|