@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vellumai/assistant",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.7-staging.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"@qdrant/js-client-rest": "1.17.0",
|
|
44
44
|
"@resvg/resvg-js": "2.6.2",
|
|
45
45
|
"@slack/types": "2.22.0",
|
|
46
|
+
"@vellumai/avatar-manifest": "file:../packages/avatar-manifest",
|
|
46
47
|
"@vellumai/ces-client": "file:../packages/ces-client",
|
|
47
48
|
"@vellumai/credential-storage": "file:../packages/credential-storage",
|
|
48
49
|
"@vellumai/egress-proxy": "file:../packages/egress-proxy",
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
"remark-parse": "11.0.0",
|
|
73
74
|
"rrule": "2.8.1",
|
|
74
75
|
"semver": "7.8.0",
|
|
76
|
+
"sharp": "0.34.5",
|
|
75
77
|
"stemmer": "2.0.1",
|
|
76
78
|
"tar-stream": "3.1.7",
|
|
77
79
|
"tldts": "7.0.25",
|
|
@@ -85,6 +87,7 @@
|
|
|
85
87
|
"node": ">=20.12.0"
|
|
86
88
|
},
|
|
87
89
|
"bundledDependencies": [
|
|
90
|
+
"@vellumai/avatar-manifest",
|
|
88
91
|
"@vellumai/ces-client",
|
|
89
92
|
"@vellumai/credential-storage",
|
|
90
93
|
"@vellumai/service-contracts",
|
|
@@ -86,6 +86,9 @@ function projectModel(model: CatalogModel): Record<string, unknown> {
|
|
|
86
86
|
if (model.pricing !== undefined) {
|
|
87
87
|
projected.pricing = model.pricing;
|
|
88
88
|
}
|
|
89
|
+
if (model.featureFlag !== undefined) {
|
|
90
|
+
projected.featureFlag = model.featureFlag;
|
|
91
|
+
}
|
|
89
92
|
return projected;
|
|
90
93
|
}
|
|
91
94
|
|
|
@@ -115,6 +118,9 @@ function projectProvider(entry: ProviderCatalogEntry): Record<string, unknown> {
|
|
|
115
118
|
if (entry.supportsPlatformAuth !== undefined) {
|
|
116
119
|
projected.supportsPlatformAuth = entry.supportsPlatformAuth;
|
|
117
120
|
}
|
|
121
|
+
if (entry.featureFlag !== undefined) {
|
|
122
|
+
projected.featureFlag = entry.featureFlag;
|
|
123
|
+
}
|
|
118
124
|
projected.defaultModel = entry.defaultModel;
|
|
119
125
|
projected.models = entry.models.map(projectModel);
|
|
120
126
|
// NOTE: `apiKeyUrl` intentionally omitted — clients use
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The inbound handoff of the approval-card message id, driven through the
|
|
3
|
+
* real handler.
|
|
4
|
+
*
|
|
5
|
+
* The companion suite (approval-card-edit.test.ts) pins the interception
|
|
6
|
+
* seam but injects `approvalMessageId` directly, so it stays green if the
|
|
7
|
+
* inbound handler stops reading `sourceMetadata.messageId` off the wire.
|
|
8
|
+
* This suite closes that gap: a Telegram button decision enters through
|
|
9
|
+
* `handleChannelInbound` exactly as the gateway forwards it, and the edit
|
|
10
|
+
* must address the message id the wire carried.
|
|
11
|
+
*/
|
|
12
|
+
import { beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
mock.module("../config/env.js", () => ({ isHttpAuthDisabled: () => true }));
|
|
15
|
+
|
|
16
|
+
const _conversationMocks = new Map<string, unknown>();
|
|
17
|
+
mock.module("../daemon/conversation-registry.js", () => ({
|
|
18
|
+
findConversation: (id: string) => _conversationMocks.get(id),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
mock.module("../runtime/local-actor-identity.js", () => ({
|
|
22
|
+
findLocalGuardianPrincipalId: async () => "vellum-principal-1",
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
import type { TrustVerdict } from "@vellumai/gateway-client";
|
|
26
|
+
|
|
27
|
+
import type { Conversation } from "../daemon/conversation.js";
|
|
28
|
+
import * as providers from "../messaging/providers/index.js";
|
|
29
|
+
import { getDb } from "../persistence/db-connection.js";
|
|
30
|
+
import { initializeDb } from "../persistence/db-init.js";
|
|
31
|
+
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
32
|
+
import {
|
|
33
|
+
handleChannelInbound,
|
|
34
|
+
setAdapterProcessMessage,
|
|
35
|
+
} from "./helpers/channel-test-adapter.js";
|
|
36
|
+
|
|
37
|
+
await initializeDb();
|
|
38
|
+
|
|
39
|
+
const CHAT_ID = "chat-123";
|
|
40
|
+
const GUARDIAN = "user-1";
|
|
41
|
+
const BUTTON_MESSAGE_ID = "4242";
|
|
42
|
+
const CALLBACK_URL = "https://gateway.test/deliver/telegram";
|
|
43
|
+
|
|
44
|
+
function resetTables(): void {
|
|
45
|
+
const db = getDb();
|
|
46
|
+
db.run("DELETE FROM channel_inbound_events");
|
|
47
|
+
db.run("DELETE FROM conversation_keys");
|
|
48
|
+
db.run("DELETE FROM messages");
|
|
49
|
+
db.run("DELETE FROM conversations");
|
|
50
|
+
db.run("DELETE FROM external_conversation_bindings");
|
|
51
|
+
db.run("DELETE FROM contact_channels");
|
|
52
|
+
db.run("DELETE FROM contacts");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function guardianVerdict(): TrustVerdict {
|
|
56
|
+
return {
|
|
57
|
+
trustClass: "guardian",
|
|
58
|
+
canonicalSenderId: GUARDIAN,
|
|
59
|
+
contactId: "contact-1",
|
|
60
|
+
channelId: "channel-1",
|
|
61
|
+
type: "telegram",
|
|
62
|
+
address: GUARDIAN,
|
|
63
|
+
externalChatId: CHAT_ID,
|
|
64
|
+
status: "active",
|
|
65
|
+
policy: "allow",
|
|
66
|
+
guardianExternalUserId: GUARDIAN,
|
|
67
|
+
guardianDeliveryChatId: CHAT_ID,
|
|
68
|
+
guardianPrincipalId: "vellum-principal-1",
|
|
69
|
+
} satisfies TrustVerdict;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function makeInboundRequest(overrides: Record<string, unknown> = {}): Request {
|
|
73
|
+
return new Request("http://localhost/channels/inbound", {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: {
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
"X-Gateway-Origin": "test-token",
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
sourceChannel: "telegram",
|
|
81
|
+
interface: "telegram",
|
|
82
|
+
conversationExternalId: CHAT_ID,
|
|
83
|
+
externalMessageId: `msg-${Date.now()}-${Math.random()
|
|
84
|
+
.toString(36)
|
|
85
|
+
.slice(2, 8)}`,
|
|
86
|
+
content: "hello",
|
|
87
|
+
actorExternalId: GUARDIAN,
|
|
88
|
+
replyCallbackUrl: CALLBACK_URL,
|
|
89
|
+
sourceMetadata: { trustVerdict: guardianVerdict() },
|
|
90
|
+
...overrides,
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function conversationIdFromInboundEvents(): string {
|
|
96
|
+
const rows = getDb()
|
|
97
|
+
.$client.prepare("SELECT conversation_id FROM channel_inbound_events")
|
|
98
|
+
.all() as Array<{ conversation_id: string }>;
|
|
99
|
+
expect(rows.length).toBeGreaterThan(0);
|
|
100
|
+
return rows[0].conversation_id;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function registerPendingInteraction(
|
|
104
|
+
requestId: string,
|
|
105
|
+
conversationId: string,
|
|
106
|
+
): void {
|
|
107
|
+
const _mockSession = {
|
|
108
|
+
handleConfirmationResponse: mock(() => {}),
|
|
109
|
+
ensureActorScopedHistory: async () => {},
|
|
110
|
+
} as unknown as Conversation;
|
|
111
|
+
_conversationMocks.set(conversationId, _mockSession);
|
|
112
|
+
|
|
113
|
+
pendingInteractions.register(requestId, {
|
|
114
|
+
conversationId,
|
|
115
|
+
kind: "confirmation",
|
|
116
|
+
confirmationDetails: {
|
|
117
|
+
toolName: "execute_shell",
|
|
118
|
+
input: { command: "ls" },
|
|
119
|
+
riskLevel: "high",
|
|
120
|
+
allowlistOptions: [
|
|
121
|
+
{ label: "test", description: "test", pattern: "test" },
|
|
122
|
+
],
|
|
123
|
+
scopeOptions: [{ label: "everywhere", scope: "everywhere" }],
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
describe("approval-card edit: inbound message-id handoff", () => {
|
|
129
|
+
let editSpy: ReturnType<typeof spyOn>;
|
|
130
|
+
|
|
131
|
+
beforeEach(() => {
|
|
132
|
+
resetTables();
|
|
133
|
+
pendingInteractions.clear();
|
|
134
|
+
_conversationMocks.clear();
|
|
135
|
+
setAdapterProcessMessage(undefined);
|
|
136
|
+
editSpy = spyOn(providers, "editChannelMessage").mockResolvedValue({
|
|
137
|
+
ok: true,
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("a Telegram button press edits the message id the wire carried", async () => {
|
|
142
|
+
// First inbound materializes the conversation the pending approval
|
|
143
|
+
// will hang off, the way a real approval prompt's conversation exists
|
|
144
|
+
// before its buttons are pressed.
|
|
145
|
+
const first = await handleChannelInbound(makeInboundRequest());
|
|
146
|
+
expect(((await first.json()) as { accepted?: boolean }).accepted).toBe(
|
|
147
|
+
true,
|
|
148
|
+
);
|
|
149
|
+
const conversationId = conversationIdFromInboundEvents();
|
|
150
|
+
registerPendingInteraction("req-handoff-1", conversationId);
|
|
151
|
+
|
|
152
|
+
// The gateway forwards a button press with the keyboard message's id on
|
|
153
|
+
// sourceMetadata.messageId; nothing in this request names the id any
|
|
154
|
+
// other way, so the edit below proves the handler read it off the wire.
|
|
155
|
+
const res = await handleChannelInbound(
|
|
156
|
+
makeInboundRequest({
|
|
157
|
+
content: "",
|
|
158
|
+
callbackData: "apr:req-handoff-1:approve_once",
|
|
159
|
+
sourceMetadata: {
|
|
160
|
+
trustVerdict: guardianVerdict(),
|
|
161
|
+
messageId: BUTTON_MESSAGE_ID,
|
|
162
|
+
},
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
165
|
+
expect(res.status).toBe(200);
|
|
166
|
+
|
|
167
|
+
expect(editSpy).toHaveBeenCalledTimes(1);
|
|
168
|
+
const [callbackUrl, payload] = editSpy.mock.calls[0];
|
|
169
|
+
expect(callbackUrl).toBe(CALLBACK_URL);
|
|
170
|
+
expect(payload.chatId).toBe(CHAT_ID);
|
|
171
|
+
expect(payload.messageId).toBe(BUTTON_MESSAGE_ID);
|
|
172
|
+
expect(payload.text).toContain("Approved");
|
|
173
|
+
|
|
174
|
+
editSpy.mockRestore();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("a button press whose wire carries no message id edits nothing", async () => {
|
|
178
|
+
// The inverse pin: the edit is driven by the wire's id and only by it.
|
|
179
|
+
const first = await handleChannelInbound(makeInboundRequest());
|
|
180
|
+
expect(((await first.json()) as { accepted?: boolean }).accepted).toBe(
|
|
181
|
+
true,
|
|
182
|
+
);
|
|
183
|
+
const conversationId = conversationIdFromInboundEvents();
|
|
184
|
+
registerPendingInteraction("req-handoff-2", conversationId);
|
|
185
|
+
|
|
186
|
+
const res = await handleChannelInbound(
|
|
187
|
+
makeInboundRequest({
|
|
188
|
+
content: "",
|
|
189
|
+
callbackData: "apr:req-handoff-2:approve_once",
|
|
190
|
+
sourceMetadata: { trustVerdict: guardianVerdict() },
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
expect(res.status).toBe(200);
|
|
194
|
+
|
|
195
|
+
expect(editSpy).not.toHaveBeenCalled();
|
|
196
|
+
|
|
197
|
+
editSpy.mockRestore();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval-card editing after a button decision, across channels.
|
|
3
|
+
*
|
|
4
|
+
* The card edit is routed by the reply callback URL through the channel edit
|
|
5
|
+
* capability, so the same interception path serves every channel whose
|
|
6
|
+
* transport implements `edit`. These tests drive the Telegram shape end to
|
|
7
|
+
* end at this seam: the gateway forwards the id of the message holding the
|
|
8
|
+
* inline keyboard, and a decision or a stale press must edit exactly that
|
|
9
|
+
* message.
|
|
10
|
+
*/
|
|
11
|
+
import { beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
const _conversationMocks = new Map<string, unknown>();
|
|
14
|
+
mock.module("../daemon/conversation-registry.js", () => ({
|
|
15
|
+
findConversation: (id: string) => _conversationMocks.get(id),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
let _anchorPrincipalId: string | undefined;
|
|
19
|
+
mock.module("../runtime/local-actor-identity.js", () => ({
|
|
20
|
+
findLocalGuardianPrincipalId: async () => _anchorPrincipalId,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
import type { Conversation } from "../daemon/conversation.js";
|
|
24
|
+
import * as providers from "../messaging/providers/index.js";
|
|
25
|
+
import { initializeDb } from "../persistence/db-init.js";
|
|
26
|
+
import * as gatewayClient from "../runtime/gateway-client.js";
|
|
27
|
+
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
28
|
+
import { handleApprovalInterception } from "../runtime/routes/guardian-approval-interception.js";
|
|
29
|
+
|
|
30
|
+
await initializeDb();
|
|
31
|
+
|
|
32
|
+
const ASSISTANT_ID = "self";
|
|
33
|
+
const CONVERSATION_ID = "conv-card-edit-1";
|
|
34
|
+
const REQUESTER_CHAT = "5550100";
|
|
35
|
+
const TELEGRAM_CALLBACK_URL = "https://gateway.test/deliver/telegram?chat=1";
|
|
36
|
+
const BUTTON_MESSAGE_ID = "4242";
|
|
37
|
+
|
|
38
|
+
function registerPendingInteraction(requestId: string): void {
|
|
39
|
+
const _mockSession = {
|
|
40
|
+
handleConfirmationResponse: mock(() => {}),
|
|
41
|
+
ensureActorScopedHistory: async () => {},
|
|
42
|
+
} as unknown as Conversation;
|
|
43
|
+
_conversationMocks.set(CONVERSATION_ID, _mockSession);
|
|
44
|
+
|
|
45
|
+
pendingInteractions.register(requestId, {
|
|
46
|
+
conversationId: CONVERSATION_ID,
|
|
47
|
+
kind: "confirmation",
|
|
48
|
+
confirmationDetails: {
|
|
49
|
+
toolName: "execute_shell",
|
|
50
|
+
input: { command: "ls" },
|
|
51
|
+
riskLevel: "high",
|
|
52
|
+
allowlistOptions: [
|
|
53
|
+
{ label: "test", description: "test", pattern: "test" },
|
|
54
|
+
],
|
|
55
|
+
scopeOptions: [{ label: "everywhere", scope: "everywhere" }],
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function guardianParams(callbackData: string) {
|
|
61
|
+
return {
|
|
62
|
+
conversationId: CONVERSATION_ID,
|
|
63
|
+
callbackData,
|
|
64
|
+
content: "",
|
|
65
|
+
conversationExternalId: REQUESTER_CHAT,
|
|
66
|
+
sourceChannel: "telegram" as const,
|
|
67
|
+
actorExternalId: "guardian-user-1",
|
|
68
|
+
replyCallbackUrl: TELEGRAM_CALLBACK_URL,
|
|
69
|
+
trustCtx: {
|
|
70
|
+
sourceChannel: "telegram" as const,
|
|
71
|
+
trustClass: "guardian" as const,
|
|
72
|
+
requesterExternalUserId: "guardian-user-1",
|
|
73
|
+
guardianExternalUserId: "guardian-user-1",
|
|
74
|
+
guardianPrincipalId: "guardian-principal-1",
|
|
75
|
+
},
|
|
76
|
+
assistantId: ASSISTANT_ID,
|
|
77
|
+
approvalMessageId: BUTTON_MESSAGE_ID,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
describe("approval card edit after a button decision", () => {
|
|
82
|
+
let editSpy: ReturnType<typeof spyOn>;
|
|
83
|
+
let deliverSpy: ReturnType<typeof spyOn>;
|
|
84
|
+
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
pendingInteractions.clear();
|
|
87
|
+
_anchorPrincipalId = "guardian-principal-1";
|
|
88
|
+
editSpy = spyOn(providers, "editChannelMessage").mockResolvedValue({
|
|
89
|
+
ok: true,
|
|
90
|
+
});
|
|
91
|
+
deliverSpy = spyOn(gatewayClient, "deliverChannelReply").mockResolvedValue({
|
|
92
|
+
ok: true,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("a Telegram approve button edits exactly the card message", async () => {
|
|
97
|
+
registerPendingInteraction("req-card-edit-approve");
|
|
98
|
+
|
|
99
|
+
const result = await handleApprovalInterception(
|
|
100
|
+
guardianParams("apr:req-card-edit-approve:approve_once"),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(result).toEqual({ handled: true, type: "decision_applied" });
|
|
104
|
+
expect(editSpy).toHaveBeenCalledTimes(1);
|
|
105
|
+
const [callbackUrl, payload] = editSpy.mock.calls[0];
|
|
106
|
+
expect(callbackUrl).toBe(TELEGRAM_CALLBACK_URL);
|
|
107
|
+
expect(payload.chatId).toBe(REQUESTER_CHAT);
|
|
108
|
+
expect(payload.messageId).toBe(BUTTON_MESSAGE_ID);
|
|
109
|
+
expect(payload.text).toContain("Approved");
|
|
110
|
+
|
|
111
|
+
editSpy.mockRestore();
|
|
112
|
+
deliverSpy.mockRestore();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("a Telegram reject button edits the card to Denied", async () => {
|
|
116
|
+
registerPendingInteraction("req-card-edit-reject");
|
|
117
|
+
|
|
118
|
+
const result = await handleApprovalInterception(
|
|
119
|
+
guardianParams("apr:req-card-edit-reject:reject"),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
expect(result).toEqual({ handled: true, type: "decision_applied" });
|
|
123
|
+
expect(editSpy).toHaveBeenCalledTimes(1);
|
|
124
|
+
const [, payload] = editSpy.mock.calls[0];
|
|
125
|
+
expect(payload.messageId).toBe(BUTTON_MESSAGE_ID);
|
|
126
|
+
expect(payload.text).toContain("Denied");
|
|
127
|
+
|
|
128
|
+
editSpy.mockRestore();
|
|
129
|
+
deliverSpy.mockRestore();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("a stale Telegram button press marks the card resolved", async () => {
|
|
133
|
+
// Pending interaction exists, but the button belongs to a different,
|
|
134
|
+
// already-settled request: the card must lose its buttons rather than
|
|
135
|
+
// apply to the wrong interaction.
|
|
136
|
+
registerPendingInteraction("req-card-edit-live");
|
|
137
|
+
|
|
138
|
+
const result = await handleApprovalInterception(
|
|
139
|
+
guardianParams("apr:req-card-edit-settled:approve_once"),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(result).toEqual({ handled: true, type: "stale_ignored" });
|
|
143
|
+
expect(editSpy).toHaveBeenCalledTimes(1);
|
|
144
|
+
const [callbackUrl, payload] = editSpy.mock.calls[0];
|
|
145
|
+
expect(callbackUrl).toBe(TELEGRAM_CALLBACK_URL);
|
|
146
|
+
expect(payload.messageId).toBe(BUTTON_MESSAGE_ID);
|
|
147
|
+
expect(payload.text).toBe("This approval request has been resolved.");
|
|
148
|
+
expect(payload.emphasis).toBe("muted");
|
|
149
|
+
|
|
150
|
+
editSpy.mockRestore();
|
|
151
|
+
deliverSpy.mockRestore();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* register and assistant-outbound paths keep content verbatim.
|
|
5
5
|
*
|
|
6
6
|
* The converter module is mocked (keyed on the declared mime type) so these
|
|
7
|
-
* tests run
|
|
7
|
+
* tests run independently of the installed image encoder; real conversion is
|
|
8
8
|
* covered in image-conversion.test.ts. mock.module is process-global — keep
|
|
9
9
|
* these cases in this file.
|
|
10
10
|
*/
|
|
@@ -8,6 +8,10 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
8
8
|
|
|
9
9
|
import type { SkillSummary } from "../config/skills.js";
|
|
10
10
|
import type { SkillInstallMeta } from "../skills/install-meta.js";
|
|
11
|
+
import {
|
|
12
|
+
SKILL_PLATFORM_VALUES,
|
|
13
|
+
skillPlatformForNodePlatform,
|
|
14
|
+
} from "../skills/platform-compatibility.js";
|
|
11
15
|
import { setConfig } from "./helpers/set-config.js";
|
|
12
16
|
|
|
13
17
|
function makeSummary(overrides: Partial<SkillSummary> = {}): SkillSummary {
|
|
@@ -120,6 +124,26 @@ describe("listInstalledSkills", () => {
|
|
|
120
124
|
]);
|
|
121
125
|
});
|
|
122
126
|
|
|
127
|
+
test("reports host-incompatible skills as unavailable", async () => {
|
|
128
|
+
const currentPlatform = skillPlatformForNodePlatform(process.platform)!;
|
|
129
|
+
const incompatiblePlatform = SKILL_PLATFORM_VALUES.find(
|
|
130
|
+
(platform) => platform !== currentPlatform,
|
|
131
|
+
)!;
|
|
132
|
+
catalogFixture = [
|
|
133
|
+
makeSummary({ id: "compatible", platforms: [currentPlatform] }),
|
|
134
|
+
makeSummary({ id: "incompatible", platforms: [incompatiblePlatform] }),
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
const { listInstalledSkills } =
|
|
138
|
+
await import("../skills/available-skills.js");
|
|
139
|
+
const entries = await listInstalledSkills();
|
|
140
|
+
|
|
141
|
+
expect(entries.map((entry) => [entry.id, entry.state])).toEqual([
|
|
142
|
+
["compatible", "enabled"],
|
|
143
|
+
["incompatible", "unavailable"],
|
|
144
|
+
]);
|
|
145
|
+
});
|
|
146
|
+
|
|
123
147
|
test("reports bundled skills excluded by allowBundled as unavailable", async () => {
|
|
124
148
|
catalogFixture = [
|
|
125
149
|
makeSummary({ id: "allowed-bundled" }),
|
|
@@ -250,6 +274,36 @@ describe("listCatalogSkills", () => {
|
|
|
250
274
|
expect(await listCatalogSkills()).toEqual([]);
|
|
251
275
|
});
|
|
252
276
|
|
|
277
|
+
test("reports host-incompatible catalog skills as unavailable", async () => {
|
|
278
|
+
const currentPlatform = skillPlatformForNodePlatform(process.platform)!;
|
|
279
|
+
const incompatiblePlatform = SKILL_PLATFORM_VALUES.find(
|
|
280
|
+
(platform) => platform !== currentPlatform,
|
|
281
|
+
)!;
|
|
282
|
+
remoteFixture = [
|
|
283
|
+
{
|
|
284
|
+
id: "incompatible",
|
|
285
|
+
name: "incompatible",
|
|
286
|
+
description: "Only works elsewhere",
|
|
287
|
+
platforms: [incompatiblePlatform],
|
|
288
|
+
metadata: { vellum: { platforms: [incompatiblePlatform] } },
|
|
289
|
+
},
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
const { listCatalogSkills } = await import("../skills/available-skills.js");
|
|
293
|
+
expect(await listCatalogSkills()).toEqual([
|
|
294
|
+
{
|
|
295
|
+
id: "incompatible",
|
|
296
|
+
displayName: "incompatible",
|
|
297
|
+
description: "Only works elsewhere",
|
|
298
|
+
activationHints: undefined,
|
|
299
|
+
avoidWhen: undefined,
|
|
300
|
+
platforms: [incompatiblePlatform],
|
|
301
|
+
installed: false,
|
|
302
|
+
state: "unavailable",
|
|
303
|
+
},
|
|
304
|
+
]);
|
|
305
|
+
});
|
|
306
|
+
|
|
253
307
|
test("adds no error handling of its own — unexpected catalog-read errors propagate", async () => {
|
|
254
308
|
remoteError = new Error("catalog read exploded");
|
|
255
309
|
const { listCatalogSkills } = await import("../skills/available-skills.js");
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
mock.module("../platform/sync-avatar.js", () => ({
|
|
4
|
+
syncAvatarToPlatform: () => {},
|
|
5
|
+
}));
|
|
2
6
|
|
|
3
7
|
import type { AssistantEventEnvelope } from "../api/index.js";
|
|
4
8
|
import { SYNC_TAGS } from "../daemon/message-types/sync.js";
|
|
@@ -52,6 +52,7 @@ describe("fetchCatalog normalization", () => {
|
|
|
52
52
|
description: "Shop on Amazon",
|
|
53
53
|
icon: "🛒",
|
|
54
54
|
category: "commerce",
|
|
55
|
+
platforms: ["windows"],
|
|
55
56
|
updated_at: "2026-04-19T19:26:17Z",
|
|
56
57
|
},
|
|
57
58
|
],
|
|
@@ -67,6 +68,8 @@ describe("fetchCatalog normalization", () => {
|
|
|
67
68
|
expect(skill.icon).toBe("🛒");
|
|
68
69
|
expect(skill.metadata?.icon).toBe("🛒");
|
|
69
70
|
expect(skill.updatedAt).toBe("2026-04-19T19:26:17Z");
|
|
71
|
+
expect(skill.platforms).toEqual(["windows"]);
|
|
72
|
+
expect(skill.metadata?.vellum?.platforms).toEqual(["windows"]);
|
|
70
73
|
});
|
|
71
74
|
|
|
72
75
|
test("leaves category undefined when the API omits it", async () => {
|
|
@@ -115,7 +118,11 @@ describe("readLocalCatalog normalization", () => {
|
|
|
115
118
|
description: "Task management",
|
|
116
119
|
metadata: {
|
|
117
120
|
icon: "✅",
|
|
118
|
-
vellum: {
|
|
121
|
+
vellum: {
|
|
122
|
+
"display-name": "Tasks",
|
|
123
|
+
category: "productivity",
|
|
124
|
+
platforms: ["macos", "linux"],
|
|
125
|
+
},
|
|
119
126
|
},
|
|
120
127
|
},
|
|
121
128
|
],
|
|
@@ -127,6 +134,7 @@ describe("readLocalCatalog normalization", () => {
|
|
|
127
134
|
expect(catalog).toHaveLength(1);
|
|
128
135
|
expect(catalog[0].metadata?.vellum?.category).toBe("productivity");
|
|
129
136
|
expect(catalog[0].metadata?.vellum?.["display-name"]).toBe("Tasks");
|
|
137
|
+
expect(catalog[0].platforms).toEqual(["macos", "linux"]);
|
|
130
138
|
} finally {
|
|
131
139
|
rmSync(dir, { recursive: true, force: true });
|
|
132
140
|
}
|
|
@@ -38,9 +38,7 @@ describe("channel policy registry", () => {
|
|
|
38
38
|
|
|
39
39
|
test("getDeliverableChannels returns exactly the channels with deliveryEnabled: true", () => {
|
|
40
40
|
const deliverable = getDeliverableChannels();
|
|
41
|
-
const expected = CHANNEL_IDS.filter(
|
|
42
|
-
(id) => getChannelPolicy(id).notification.deliveryEnabled,
|
|
43
|
-
);
|
|
41
|
+
const expected = CHANNEL_IDS.filter(isNotificationDeliverable);
|
|
44
42
|
|
|
45
43
|
expect(deliverable).toHaveLength(expected.length);
|
|
46
44
|
for (const id of expected) {
|
|
@@ -44,18 +44,6 @@ type SocketHealth = {
|
|
|
44
44
|
let askedChannels: string[];
|
|
45
45
|
let socketHealth: SocketHealth | Error;
|
|
46
46
|
|
|
47
|
-
/** How many channels the gateway reports on Discord's allow-list. */
|
|
48
|
-
let admittedChannelCount: number | Error;
|
|
49
|
-
|
|
50
|
-
mock.module("../channels/gateway-discord-admission.js", () => ({
|
|
51
|
-
readDiscordAdmission: async () => {
|
|
52
|
-
if (admittedChannelCount instanceof Error) {
|
|
53
|
-
throw admittedChannelCount;
|
|
54
|
-
}
|
|
55
|
-
return { admittedChannelCount };
|
|
56
|
-
},
|
|
57
|
-
}));
|
|
58
|
-
|
|
59
47
|
mock.module("../channels/gateway-channel-socket-health.js", () => ({
|
|
60
48
|
readChannelSocketHealth: async (channel: string) => {
|
|
61
49
|
askedChannels.push(channel);
|
|
@@ -72,7 +60,6 @@ beforeEach(() => {
|
|
|
72
60
|
socketHealth = { channel: "discord", status: "connected" };
|
|
73
61
|
// The default install state: fail-closed and empty, admitting nothing until
|
|
74
62
|
// someone lists channel ids. Tests that need admission say so.
|
|
75
|
-
admittedChannelCount = 0;
|
|
76
63
|
});
|
|
77
64
|
|
|
78
65
|
async function runDiscordProbe() {
|
|
@@ -95,28 +82,30 @@ describe("discord readiness", () => {
|
|
|
95
82
|
expect(askedChannels).toEqual(["discord"]);
|
|
96
83
|
});
|
|
97
84
|
|
|
98
|
-
test("a fresh install has
|
|
85
|
+
test("a fresh install has no token", async () => {
|
|
99
86
|
const [snapshot] = await runDiscordProbe();
|
|
100
87
|
|
|
101
88
|
expect(findCheck(snapshot, "bot_token").passed).toBe(false);
|
|
102
|
-
expect(findCheck(snapshot, "guild_admission").passed).toBe(false);
|
|
103
89
|
// Nothing configured at all, which is a different report from a channel
|
|
104
90
|
// half set up: no step has been taken rather than one left undone.
|
|
105
91
|
expect(snapshot.setupStatus).toBe("not_configured");
|
|
106
92
|
});
|
|
107
93
|
|
|
108
|
-
test("
|
|
109
|
-
|
|
94
|
+
test("Discord has one setup step, so it is never half configured", async () => {
|
|
95
|
+
// A live socket without a token still reads as not configured rather than
|
|
96
|
+
// incomplete. `incomplete` needs one configuration check passing while
|
|
97
|
+
// another fails, and the token is now the only one: with the room
|
|
98
|
+
// allow-list gone there is no second step to be part-way through.
|
|
99
|
+
socketHealth = { channel: "discord", status: "connected" };
|
|
110
100
|
|
|
111
101
|
const [snapshot] = await runDiscordProbe();
|
|
112
102
|
|
|
113
103
|
expect(findCheck(snapshot, "bot_token").passed).toBe(false);
|
|
114
|
-
expect(snapshot.setupStatus).toBe("
|
|
104
|
+
expect(snapshot.setupStatus).toBe("not_configured");
|
|
115
105
|
});
|
|
116
106
|
|
|
117
|
-
test("a token
|
|
107
|
+
test("a token and a live connection read as ready", async () => {
|
|
118
108
|
mockSecureKeys[credentialKey("discord_channel", "bot_token")] = "bot-fake";
|
|
119
|
-
admittedChannelCount = 2;
|
|
120
109
|
socketHealth = {
|
|
121
110
|
channel: "discord",
|
|
122
111
|
status: "connected",
|
|
@@ -130,61 +119,11 @@ describe("discord readiness", () => {
|
|
|
130
119
|
expect(delivery.passed).toBe(true);
|
|
131
120
|
expect(delivery.message).toContain("Discord");
|
|
132
121
|
expect(delivery.message).toContain("2026-08-21T17:00:00.000Z");
|
|
133
|
-
expect(findCheck(snapshot, "guild_admission").message).toContain(
|
|
134
|
-
"2 channels",
|
|
135
|
-
);
|
|
136
122
|
expect(snapshot.ready).toBe(true);
|
|
137
123
|
});
|
|
138
124
|
|
|
139
|
-
test("an empty allow-list is unfinished setup, not a live channel", async () => {
|
|
140
|
-
mockSecureKeys[credentialKey("discord_channel", "bot_token")] = "bot-fake";
|
|
141
|
-
admittedChannelCount = 0;
|
|
142
|
-
|
|
143
|
-
const [snapshot] = await runDiscordProbe();
|
|
144
|
-
|
|
145
|
-
// A connected socket that admits nothing still drops every guild message,
|
|
146
|
-
// so claiming ready here would promise delivery the gate is refusing.
|
|
147
|
-
const admission = findCheck(snapshot, "guild_admission");
|
|
148
|
-
expect(admission.passed).toBe(false);
|
|
149
|
-
expect(snapshot.setupStatus).toBe("incomplete");
|
|
150
|
-
expect(snapshot.ready).toBe(false);
|
|
151
|
-
|
|
152
|
-
// And it is a setup step rather than an outage: direct messages carry no
|
|
153
|
-
// guild and never meet this gate, so the connection itself is fine.
|
|
154
|
-
expect(snapshot.health).not.toBe("failing");
|
|
155
|
-
expect(findCheck(snapshot, "inbound_delivery").passed).toBe(true);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test("one admitted channel reads in the singular", async () => {
|
|
159
|
-
mockSecureKeys[credentialKey("discord_channel", "bot_token")] = "bot-fake";
|
|
160
|
-
admittedChannelCount = 1;
|
|
161
|
-
|
|
162
|
-
const [snapshot] = await runDiscordProbe();
|
|
163
|
-
|
|
164
|
-
expect(findCheck(snapshot, "guild_admission").message).toContain(
|
|
165
|
-
"1 channel",
|
|
166
|
-
);
|
|
167
|
-
expect(findCheck(snapshot, "guild_admission").message).not.toContain(
|
|
168
|
-
"1 channels",
|
|
169
|
-
);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test("an unreachable gateway does not claim the allow-list is empty", async () => {
|
|
173
|
-
mockSecureKeys[credentialKey("discord_channel", "bot_token")] = "bot-fake";
|
|
174
|
-
admittedChannelCount = new Error("gateway down");
|
|
175
|
-
|
|
176
|
-
const [snapshot] = await runDiscordProbe();
|
|
177
|
-
|
|
178
|
-
const admission = findCheck(snapshot, "guild_admission");
|
|
179
|
-
expect(admission.passed).toBe(true);
|
|
180
|
-
expect(admission.indeterminate).toBe(true);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
125
|
test("a dead socket reads as configured and failing, not unconfigured", async () => {
|
|
184
126
|
mockSecureKeys[credentialKey("discord_channel", "bot_token")] = "bot-fake";
|
|
185
|
-
// Setup has to be finished for this to isolate the socket: an empty
|
|
186
|
-
// allow-list would leave it incomplete for a reason that is not the socket.
|
|
187
|
-
admittedChannelCount = 1;
|
|
188
127
|
socketHealth = { channel: "discord", status: "disconnected" };
|
|
189
128
|
|
|
190
129
|
const [snapshot] = await runDiscordProbe();
|