@vellumai/assistant 0.11.6 → 0.11.7-staging.1
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/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/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/openapi.yaml +419 -55
- package/package.json +4 -1
- 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-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__/discord-callback-round-trip.test.ts +93 -0
- package/src/__tests__/discord-channel-config.test.ts +237 -0
- 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-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__/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-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 +20 -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 +6 -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 +18 -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 +259 -11
- 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-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 +213 -8
- 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/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/seed-providers.ts +261 -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 +85 -133
- 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__/retry-callsite.test.ts +55 -0
- 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 +231 -3
- package/src/providers/inference/auth.ts +1 -0
- 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 +75 -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/provider-resolvability.ts +20 -0
- 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/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 +21 -9
- 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/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/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/oauth2.ts +7 -0
- package/src/security/openai-device-auth.ts +364 -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/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/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
|
@@ -1,23 +1,14 @@
|
|
|
1
|
-
import { v4 as uuid } from "uuid";
|
|
2
|
-
|
|
3
1
|
import { credentialKey } from "../../security/credential-key.js";
|
|
4
2
|
import { getSecureKeyAsync } from "../../security/secure-keys.js";
|
|
5
3
|
import { getLogger } from "../../util/logger.js";
|
|
6
4
|
import type {
|
|
7
|
-
AuthorizeRequest,
|
|
8
|
-
AuthorizeResult,
|
|
9
5
|
BrowserFillRequest,
|
|
10
6
|
BrowserFillResult,
|
|
11
|
-
ConsumeResult,
|
|
12
|
-
ServerUseByIdRequest,
|
|
13
|
-
ServerUseByIdResult,
|
|
14
7
|
ServerUseRequest,
|
|
15
8
|
ServerUseResult,
|
|
16
|
-
UsageToken,
|
|
17
9
|
} from "./broker-types.js";
|
|
18
10
|
import { isDomainAllowed } from "./domain-policy.js";
|
|
19
11
|
import { getCredentialMetadata } from "./metadata-store.js";
|
|
20
|
-
import { resolveById } from "./resolve.js";
|
|
21
12
|
import {
|
|
22
13
|
isToolAllowed,
|
|
23
14
|
serverUseDenialReason,
|
|
@@ -26,21 +17,17 @@ import {
|
|
|
26
17
|
|
|
27
18
|
const log = getLogger("credential-broker");
|
|
28
19
|
|
|
29
|
-
/** Tokens expire after 5 minutes to limit the window for using stale/revoked credentials. */
|
|
30
|
-
const TOKEN_TTL_MS = 5 * 60 * 1000;
|
|
31
|
-
|
|
32
20
|
/**
|
|
33
|
-
* Credential broker that
|
|
21
|
+
* Credential broker that mediates policy-checked credential access.
|
|
34
22
|
*
|
|
35
|
-
* The broker never exposes plaintext secret values.
|
|
23
|
+
* The broker never exposes plaintext secret values to callers. It:
|
|
36
24
|
* 1. Checks that a credential exists and has metadata
|
|
37
|
-
* 2.
|
|
38
|
-
* 3.
|
|
25
|
+
* 2. Enforces tool and domain policy
|
|
26
|
+
* 3. Reads the secret internally and passes it to an opaque callback
|
|
39
27
|
*
|
|
40
|
-
* Tool policy is enforced at
|
|
28
|
+
* Tool policy is enforced at fill/use time; domain policy is enforced at fill time.
|
|
41
29
|
*/
|
|
42
30
|
export class CredentialBroker {
|
|
43
|
-
private tokens = new Map<string, UsageToken>();
|
|
44
31
|
/** Transient values for one-time send: consumed on first read, never persisted.
|
|
45
32
|
* Values are wrapped in objects so post-await guards use reference identity
|
|
46
33
|
* (not string value equality) to detect concurrent replacements. */
|
|
@@ -48,7 +35,7 @@ export class CredentialBroker {
|
|
|
48
35
|
|
|
49
36
|
/**
|
|
50
37
|
* Inject a value for one-time use. The value is consumed on the next
|
|
51
|
-
* browserFill or
|
|
38
|
+
* browserFill or serverUse call for this service/field pair, then discarded.
|
|
52
39
|
*/
|
|
53
40
|
injectTransient(service: string, field: string, value: string): void {
|
|
54
41
|
const key = credentialKey(service, field);
|
|
@@ -59,113 +46,6 @@ export class CredentialBroker {
|
|
|
59
46
|
);
|
|
60
47
|
}
|
|
61
48
|
|
|
62
|
-
/**
|
|
63
|
-
* Authorize the use of a credential for a specific tool and optional domain.
|
|
64
|
-
* Returns a single-use token on success, or a denial reason on failure.
|
|
65
|
-
*/
|
|
66
|
-
authorize(request: AuthorizeRequest): AuthorizeResult {
|
|
67
|
-
const metadata = getCredentialMetadata(request.service, request.field);
|
|
68
|
-
if (!metadata) {
|
|
69
|
-
return {
|
|
70
|
-
authorized: false,
|
|
71
|
-
reason: `No credential found for ${request.service}/${request.field}`,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Tool policy enforcement - deny if tool is not in the credential's allowed list
|
|
76
|
-
if (!isToolAllowed(request.toolName, metadata.allowedTools)) {
|
|
77
|
-
return {
|
|
78
|
-
authorized: false,
|
|
79
|
-
reason: toolNotAllowedReason(
|
|
80
|
-
request.toolName,
|
|
81
|
-
request.service,
|
|
82
|
-
request.field,
|
|
83
|
-
metadata.allowedTools,
|
|
84
|
-
),
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const token: UsageToken = {
|
|
89
|
-
tokenId: uuid(),
|
|
90
|
-
credentialId: metadata.credentialId,
|
|
91
|
-
service: request.service,
|
|
92
|
-
field: request.field,
|
|
93
|
-
toolName: request.toolName,
|
|
94
|
-
createdAt: Date.now(),
|
|
95
|
-
consumed: false,
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
this.tokens.set(token.tokenId, token);
|
|
99
|
-
log.info(
|
|
100
|
-
{
|
|
101
|
-
tokenId: token.tokenId,
|
|
102
|
-
service: request.service,
|
|
103
|
-
field: request.field,
|
|
104
|
-
tool: request.toolName,
|
|
105
|
-
},
|
|
106
|
-
"Usage token issued",
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
return { authorized: true, token: { ...token } };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Consume a previously issued token. Returns the storage key on success.
|
|
114
|
-
* Each token can only be consumed once.
|
|
115
|
-
*/
|
|
116
|
-
consume(tokenId: string): ConsumeResult {
|
|
117
|
-
const token = this.tokens.get(tokenId);
|
|
118
|
-
if (!token) {
|
|
119
|
-
return { success: false, reason: "Token not found or already revoked" };
|
|
120
|
-
}
|
|
121
|
-
if (token.consumed) {
|
|
122
|
-
return { success: false, reason: "Token already consumed" };
|
|
123
|
-
}
|
|
124
|
-
if (Date.now() - token.createdAt > TOKEN_TTL_MS) {
|
|
125
|
-
this.tokens.delete(tokenId);
|
|
126
|
-
log.info({ tokenId }, "Token expired (TTL exceeded)");
|
|
127
|
-
return { success: false, reason: "Token expired" };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
token.consumed = true;
|
|
131
|
-
const storageKey = credentialKey(token.service, token.field);
|
|
132
|
-
// Check for transient value first (one-time send) - consume and return the value
|
|
133
|
-
// directly since transient values are never persisted to secure storage.
|
|
134
|
-
const transient = this.transientValues.get(storageKey);
|
|
135
|
-
if (transient !== undefined) {
|
|
136
|
-
this.transientValues.delete(storageKey);
|
|
137
|
-
log.info(
|
|
138
|
-
{ tokenId, storageKey, transient: true },
|
|
139
|
-
"Usage token consumed (transient)",
|
|
140
|
-
);
|
|
141
|
-
return { success: true, storageKey, value: transient.value };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
log.info({ tokenId, storageKey }, "Usage token consumed");
|
|
145
|
-
return { success: true, storageKey };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Revoke a token, removing it from the active set.
|
|
150
|
-
* Returns true if the token existed and was revoked.
|
|
151
|
-
*/
|
|
152
|
-
revoke(tokenId: string): boolean {
|
|
153
|
-
const existed = this.tokens.delete(tokenId);
|
|
154
|
-
if (existed) {
|
|
155
|
-
log.info({ tokenId }, "Usage token revoked");
|
|
156
|
-
}
|
|
157
|
-
return existed;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/** Revoke all tokens (e.g. on conversation teardown). */
|
|
161
|
-
revokeAll(): void {
|
|
162
|
-
const count = this.tokens.size;
|
|
163
|
-
this.tokens.clear();
|
|
164
|
-
if (count > 0) {
|
|
165
|
-
log.info({ count }, "All usage tokens revoked");
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
49
|
/**
|
|
170
50
|
* Fill a browser field using a credential without exposing plaintext to the caller.
|
|
171
51
|
*
|
|
@@ -317,81 +197,6 @@ export class CredentialBroker {
|
|
|
317
197
|
return { success: false, reason: "Credential use failed" };
|
|
318
198
|
}
|
|
319
199
|
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Look up a credential by its opaque ID for proxy injection.
|
|
323
|
-
*
|
|
324
|
-
* Returns metadata and injection templates so the proxy knows how to
|
|
325
|
-
* inject the credential into outbound requests. The secret value is
|
|
326
|
-
* never included in the result - the proxy reads it separately via
|
|
327
|
-
* the secure key backend at injection time.
|
|
328
|
-
*/
|
|
329
|
-
async serverUseById(
|
|
330
|
-
request: ServerUseByIdRequest,
|
|
331
|
-
): Promise<ServerUseByIdResult> {
|
|
332
|
-
const resolved = resolveById(request.credentialId);
|
|
333
|
-
if (!resolved) {
|
|
334
|
-
return {
|
|
335
|
-
success: false,
|
|
336
|
-
reason: `No credential found for id "${request.credentialId}"`,
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const { metadata } = resolved;
|
|
341
|
-
|
|
342
|
-
// Shared server-use policy: the ID lookup above is the only by-ID-specific
|
|
343
|
-
// gate, so tool and domain enforcement come from the same helper serverUse
|
|
344
|
-
// uses.
|
|
345
|
-
const denialReason = serverUseDenialReason(
|
|
346
|
-
metadata,
|
|
347
|
-
request.requestingTool,
|
|
348
|
-
metadata.service,
|
|
349
|
-
metadata.field,
|
|
350
|
-
);
|
|
351
|
-
if (denialReason) {
|
|
352
|
-
return { success: false, reason: denialReason };
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Fail-closed: verify the secret value actually exists in secure storage.
|
|
356
|
-
// Without this, downstream proxy code would attempt unauthenticated requests.
|
|
357
|
-
const value = await getSecureKeyAsync(resolved.storageKey);
|
|
358
|
-
if (!value) {
|
|
359
|
-
return {
|
|
360
|
-
success: false,
|
|
361
|
-
reason: `Credential metadata exists but no stored value for ${metadata.service}/${metadata.field}`,
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
log.info(
|
|
366
|
-
{
|
|
367
|
-
credentialId: request.credentialId,
|
|
368
|
-
service: metadata.service,
|
|
369
|
-
field: metadata.field,
|
|
370
|
-
tool: request.requestingTool,
|
|
371
|
-
},
|
|
372
|
-
"Server-side credential lookup by ID completed",
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
return {
|
|
376
|
-
success: true,
|
|
377
|
-
credentialId: resolved.credentialId,
|
|
378
|
-
service: resolved.service,
|
|
379
|
-
field: resolved.field,
|
|
380
|
-
injectionTemplates: resolved.injectionTemplates,
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/** Return the number of active (non-consumed, non-revoked, non-expired) tokens. */
|
|
385
|
-
get activeTokenCount(): number {
|
|
386
|
-
const now = Date.now();
|
|
387
|
-
let count = 0;
|
|
388
|
-
for (const token of this.tokens.values()) {
|
|
389
|
-
if (!token.consumed && now - token.createdAt <= TOKEN_TTL_MS) {
|
|
390
|
-
count++;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return count;
|
|
394
|
-
}
|
|
395
200
|
}
|
|
396
201
|
|
|
397
202
|
/** Shared singleton broker instance used by vault and browser tools. */
|
package/src/tools/executor.ts
CHANGED
|
@@ -33,6 +33,7 @@ import { getWorkflowRunManager } from "../workflows/run-manager.js";
|
|
|
33
33
|
import { executeWithTimeout, safeTimeoutMs } from "./execution-timeout.js";
|
|
34
34
|
import { fileEditInputSchema } from "./filesystem/edit.js";
|
|
35
35
|
import { fileWriteInputSchema } from "./filesystem/write.js";
|
|
36
|
+
import { getHostShell } from "./host-shell.js";
|
|
36
37
|
import { PermissionChecker } from "./permission-checker.js";
|
|
37
38
|
import { getToolOwner } from "./registry.js";
|
|
38
39
|
import { extractAndSanitize } from "./sensitive-output-placeholders.js";
|
|
@@ -89,6 +90,7 @@ export class ToolExecutor {
|
|
|
89
90
|
undefined,
|
|
90
91
|
undefined,
|
|
91
92
|
context.signal,
|
|
93
|
+
getHostShell(context, input),
|
|
92
94
|
);
|
|
93
95
|
} catch {
|
|
94
96
|
// Stays undefined; the audit row records RISK_LEVEL_UNCLASSIFIED.
|
|
@@ -108,12 +110,6 @@ export class ToolExecutor {
|
|
|
108
110
|
let permApprovalMode: string | undefined;
|
|
109
111
|
let permApprovalReason: string | undefined;
|
|
110
112
|
let permRiskThreshold: string | undefined;
|
|
111
|
-
// Whether THIS invocation was interactively prompted. Distinct from the
|
|
112
|
-
// turn-level `context.approvedViaPrompt` (seeded from
|
|
113
|
-
// `approvedViaPromptThisTurn`, which stays true for later auto-approved
|
|
114
|
-
// tools in the same turn): the `permission_decided` telemetry must reflect
|
|
115
|
-
// a prompt for this specific call, not any prompt earlier in the turn.
|
|
116
|
-
let wasPromptedThisInvocation = false;
|
|
117
113
|
// Run pre-execution approval gates (abort, guardian policy,
|
|
118
114
|
// allowed-tool-set, task-run preflight, tool registry lookup). The gate
|
|
119
115
|
// resolves the tool's sandbox/host target internally.
|
|
@@ -253,7 +249,6 @@ export class ToolExecutor {
|
|
|
253
249
|
|
|
254
250
|
if (permResult.wasPrompted) {
|
|
255
251
|
context.approvedViaPrompt = true;
|
|
256
|
-
wasPromptedThisInvocation = true;
|
|
257
252
|
}
|
|
258
253
|
} else {
|
|
259
254
|
// Grant consumed — permission check was skipped. Set provenance explicitly
|
|
@@ -324,7 +319,6 @@ export class ToolExecutor {
|
|
|
324
319
|
matchedTrustRuleId: permMatchedTrustRuleId,
|
|
325
320
|
durationMs,
|
|
326
321
|
attribution: context.attribution ?? null,
|
|
327
|
-
wasPrompted: wasPromptedThisInvocation,
|
|
328
322
|
});
|
|
329
323
|
recordToolCompletion(
|
|
330
324
|
context.conversationId,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { assistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
4
|
+
import { getHostShell } from "./host-shell.js";
|
|
5
|
+
|
|
6
|
+
const subscriptions: Array<{ dispose(): void }> = [];
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
for (const subscription of subscriptions.splice(0)) {
|
|
10
|
+
subscription.dispose();
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function connectHostBashClient(
|
|
15
|
+
clientId: string,
|
|
16
|
+
interfaceId: "macos" | "windows",
|
|
17
|
+
): void {
|
|
18
|
+
subscriptions.push(
|
|
19
|
+
assistantEventHub.subscribe({
|
|
20
|
+
type: "client",
|
|
21
|
+
clientId,
|
|
22
|
+
interfaceId,
|
|
23
|
+
capabilities: ["host_bash"],
|
|
24
|
+
actorPrincipalId: "actor-1",
|
|
25
|
+
callback: () => {},
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("getHostShell", () => {
|
|
31
|
+
test("uses the explicitly targeted Windows client's shell", () => {
|
|
32
|
+
connectHostBashClient("windows-client", "windows");
|
|
33
|
+
|
|
34
|
+
expect(
|
|
35
|
+
getHostShell(
|
|
36
|
+
{
|
|
37
|
+
clientOs: "web",
|
|
38
|
+
transportInterface: "web",
|
|
39
|
+
sourceActorPrincipalId: "actor-1",
|
|
40
|
+
},
|
|
41
|
+
{ target_client_id: "windows-client" },
|
|
42
|
+
),
|
|
43
|
+
).toBe("powershell");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("uses the single same-user Windows client's shell", () => {
|
|
47
|
+
connectHostBashClient("windows-client", "windows");
|
|
48
|
+
|
|
49
|
+
expect(
|
|
50
|
+
getHostShell(
|
|
51
|
+
{
|
|
52
|
+
clientOs: "web",
|
|
53
|
+
transportInterface: "web",
|
|
54
|
+
sourceActorPrincipalId: "actor-1",
|
|
55
|
+
},
|
|
56
|
+
{},
|
|
57
|
+
),
|
|
58
|
+
).toBe("powershell");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("keeps Bash classification for a targeted macOS client", () => {
|
|
62
|
+
connectHostBashClient("mac-client", "macos");
|
|
63
|
+
|
|
64
|
+
expect(
|
|
65
|
+
getHostShell(
|
|
66
|
+
{
|
|
67
|
+
clientOs: "web",
|
|
68
|
+
transportInterface: "web",
|
|
69
|
+
sourceActorPrincipalId: "actor-1",
|
|
70
|
+
},
|
|
71
|
+
{ target_client_id: "mac-client" },
|
|
72
|
+
),
|
|
73
|
+
).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ClassifyRiskIpcParams } from "@vellumai/gateway-client";
|
|
2
|
+
|
|
3
|
+
import { resolveInvocationHostClientOs } from "./client-os.js";
|
|
4
|
+
import type { ToolContext } from "./types.js";
|
|
5
|
+
|
|
6
|
+
export function getHostShell(
|
|
7
|
+
context: Pick<
|
|
8
|
+
ToolContext,
|
|
9
|
+
"clientOs" | "transportInterface" | "sourceActorPrincipalId"
|
|
10
|
+
>,
|
|
11
|
+
input: Record<string, unknown>,
|
|
12
|
+
): ClassifyRiskIpcParams["shell"] {
|
|
13
|
+
const targetClientOs = resolveInvocationHostClientOs(
|
|
14
|
+
"host_bash",
|
|
15
|
+
input,
|
|
16
|
+
context,
|
|
17
|
+
);
|
|
18
|
+
return targetClientOs === "windows" ||
|
|
19
|
+
(targetClientOs === undefined &&
|
|
20
|
+
(context.clientOs === "windows" ||
|
|
21
|
+
context.transportInterface === "windows"))
|
|
22
|
+
? "powershell"
|
|
23
|
+
: undefined;
|
|
24
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
|
-
import { isAbsolute } from "node:path";
|
|
12
|
+
import { isAbsolute, join } from "node:path";
|
|
13
13
|
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
|
|
@@ -24,6 +24,11 @@ import {
|
|
|
24
24
|
} from "../../runtime/assistant-event-hub.js";
|
|
25
25
|
import { conversationRevealNonce } from "../../runtime/reveal-nonce.js";
|
|
26
26
|
import { redactSecrets } from "../../security/secret-scanner.js";
|
|
27
|
+
import {
|
|
28
|
+
buildShellInvocation,
|
|
29
|
+
prependUniquePathEntries,
|
|
30
|
+
terminateProcessTree,
|
|
31
|
+
} from "../../util/host-process.js";
|
|
27
32
|
import { getLogger } from "../../util/logger.js";
|
|
28
33
|
import type { CompletedBackgroundTool } from "../background-tool-registry.js";
|
|
29
34
|
import {
|
|
@@ -67,15 +72,10 @@ const HOST_BASH_PROXY_ENV_KEYS = [
|
|
|
67
72
|
function buildHostShellEnv(): Record<string, string> {
|
|
68
73
|
const env = buildSanitizedEnv();
|
|
69
74
|
// Ensure ~/.local/bin and ~/.bun/bin are in PATH so `vellum` and `bun` are
|
|
70
|
-
//
|
|
71
|
-
// bundle that inherits a minimal PATH.
|
|
75
|
+
// reachable when a desktop app launches the assistant with a minimal PATH.
|
|
72
76
|
const home = homedir();
|
|
73
|
-
const extraDirs = [
|
|
74
|
-
|
|
75
|
-
const missing = extraDirs.filter((d) => !currentPath.split(":").includes(d));
|
|
76
|
-
if (missing.length > 0) {
|
|
77
|
-
env.PATH = [...missing, currentPath].filter(Boolean).join(":");
|
|
78
|
-
}
|
|
77
|
+
const extraDirs = [join(home, ".local", "bin"), join(home, ".bun", "bin")];
|
|
78
|
+
env.PATH = prependUniquePathEntries(env.PATH, extraDirs);
|
|
79
79
|
return env;
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -466,7 +466,8 @@ export const hostShellTool = {
|
|
|
466
466
|
const bgId = generateBackgroundToolId();
|
|
467
467
|
const startedAt = Date.now();
|
|
468
468
|
|
|
469
|
-
const
|
|
469
|
+
const wrapped = buildShellInvocation(command);
|
|
470
|
+
const child = spawn(wrapped.command, wrapped.args, {
|
|
470
471
|
cwd: workingDir,
|
|
471
472
|
env: hostEnv,
|
|
472
473
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -480,21 +481,7 @@ export const hostShellTool = {
|
|
|
480
481
|
// event reports "cancelled" rather than "failed".
|
|
481
482
|
let aborted = false;
|
|
482
483
|
|
|
483
|
-
const killTree = () =>
|
|
484
|
-
if (child.pid != null) {
|
|
485
|
-
try {
|
|
486
|
-
process.kill(-child.pid, "SIGKILL");
|
|
487
|
-
return;
|
|
488
|
-
} catch {
|
|
489
|
-
// Process group may have already exited — fall through.
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
try {
|
|
493
|
-
child.kill("SIGKILL");
|
|
494
|
-
} catch {
|
|
495
|
-
// Child may have already exited.
|
|
496
|
-
}
|
|
497
|
-
};
|
|
484
|
+
const killTree = () => terminateProcessTree(child);
|
|
498
485
|
|
|
499
486
|
const timer = setTimeout(() => {
|
|
500
487
|
timedOut = true;
|
|
@@ -666,31 +653,15 @@ export const hostShellTool = {
|
|
|
666
653
|
const stderrChunks: Buffer[] = [];
|
|
667
654
|
let timedOut = false;
|
|
668
655
|
|
|
669
|
-
const
|
|
656
|
+
const wrapped = buildShellInvocation(command);
|
|
657
|
+
const child = spawn(wrapped.command, wrapped.args, {
|
|
670
658
|
cwd: workingDir,
|
|
671
659
|
env: hostEnv,
|
|
672
660
|
stdio: ["ignore", "pipe", "pipe"],
|
|
673
661
|
detached: true,
|
|
674
662
|
});
|
|
675
663
|
|
|
676
|
-
|
|
677
|
-
// (negative PID), then falls back to killing the direct child if the
|
|
678
|
-
// PID is unavailable or the group kill fails.
|
|
679
|
-
const killTree = () => {
|
|
680
|
-
if (child.pid != null) {
|
|
681
|
-
try {
|
|
682
|
-
process.kill(-child.pid, "SIGKILL");
|
|
683
|
-
return;
|
|
684
|
-
} catch {
|
|
685
|
-
// Process group may have already exited — fall through.
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
try {
|
|
689
|
-
child.kill("SIGKILL");
|
|
690
|
-
} catch {
|
|
691
|
-
// Child may have already exited.
|
|
692
|
-
}
|
|
693
|
-
};
|
|
664
|
+
const killTree = () => terminateProcessTree(child);
|
|
694
665
|
|
|
695
666
|
const timer = setTimeout(() => {
|
|
696
667
|
timedOut = true;
|
|
@@ -27,24 +27,59 @@ export interface McpToolMetadata {
|
|
|
27
27
|
annotations?: McpToolAnnotations;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/** Risk levels ordered low to high, so a hint can step one place along it. */
|
|
31
|
+
const RISK_LADDER: readonly RiskLevel[] = [
|
|
32
|
+
RiskLevel.Low,
|
|
33
|
+
RiskLevel.Medium,
|
|
34
|
+
RiskLevel.High,
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
function stepRisk(risk: RiskLevel, direction: -1 | 1): RiskLevel {
|
|
38
|
+
const index = RISK_LADDER.indexOf(risk);
|
|
39
|
+
if (index === -1) {
|
|
40
|
+
return risk;
|
|
41
|
+
}
|
|
42
|
+
const next = Math.min(Math.max(index + direction, 0), RISK_LADDER.length - 1);
|
|
43
|
+
return RISK_LADDER[next] ?? risk;
|
|
44
|
+
}
|
|
45
|
+
|
|
30
46
|
/**
|
|
31
47
|
* Resolve the risk level a tool carries.
|
|
32
48
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
49
|
+
* The server's configured level is the anchor, and a tool's own MCP
|
|
50
|
+
* annotations move it at most one step along {@link RISK_LADDER}.
|
|
51
|
+
*
|
|
52
|
+
* `destructiveHint` steps up. Raising is the safe direction, so it applies at
|
|
53
|
+
* any server level.
|
|
54
|
+
*
|
|
55
|
+
* `readOnlyHint` steps down, and only from {@link RiskLevel.Medium}. Medium is
|
|
56
|
+
* the schema default, so a server sitting at High got there because the user
|
|
57
|
+
* put it there. A hint is self-reported by the server, and letting one lower a
|
|
58
|
+
* deliberately pinned server would hand a lying server the auto-approval the
|
|
59
|
+
* user withheld: under the Relaxed preset Medium carries no prompt at all. High
|
|
60
|
+
* is therefore the floor for downward hints.
|
|
61
|
+
*
|
|
62
|
+
* `destructiveHint` wins when a server sends both, since a tool that both reads
|
|
63
|
+
* and destroys is a tool that destroys.
|
|
64
|
+
*
|
|
65
|
+
* The MCP spec defaults `destructiveHint` to true when it is absent. This
|
|
66
|
+
* deliberately does not, because the server's configured level already answers
|
|
67
|
+
* that question: choosing a level for a server is the user's statement about
|
|
68
|
+
* what its unlabeled tools are worth. Honoring the spec default instead would
|
|
69
|
+
* put every tool of every unannotated server back at High, which is the
|
|
70
|
+
* behavior this replaces.
|
|
37
71
|
*/
|
|
38
72
|
function resolveRiskLevel(
|
|
39
73
|
metadata: McpToolMetadata,
|
|
40
74
|
serverConfig: McpServerConfig,
|
|
41
75
|
): RiskLevel {
|
|
42
76
|
const serverRisk = riskMap[serverConfig.defaultRiskLevel] ?? RiskLevel.High;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
serverRisk
|
|
46
|
-
|
|
47
|
-
|
|
77
|
+
const annotations = metadata.annotations;
|
|
78
|
+
if (annotations?.destructiveHint === true) {
|
|
79
|
+
return stepRisk(serverRisk, 1);
|
|
80
|
+
}
|
|
81
|
+
if (annotations?.readOnlyHint === true && serverRisk === RiskLevel.Medium) {
|
|
82
|
+
return stepRisk(serverRisk, -1);
|
|
48
83
|
}
|
|
49
84
|
return serverRisk;
|
|
50
85
|
}
|