@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
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { Database } from "bun:sqlite";
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
import { repointBackupProfileSelectionsMigration } from "../workspace/migrations/149-repoint-backup-profile-selections.js";
|
|
14
|
+
import { WORKSPACE_MIGRATIONS } from "../workspace/migrations/registry.js";
|
|
15
|
+
import { assertNotLiveDb } from "./assert-not-live-db.js";
|
|
16
|
+
|
|
17
|
+
let workspaceDir: string;
|
|
18
|
+
|
|
19
|
+
function dbPath(): string {
|
|
20
|
+
return join(workspaceDir, "data", "db", "assistant.db");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function writeConfig(data: Record<string, unknown>): void {
|
|
24
|
+
writeFileSync(
|
|
25
|
+
join(workspaceDir, "config.json"),
|
|
26
|
+
JSON.stringify(data, null, 2) + "\n",
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readConfig(): Record<string, any> {
|
|
31
|
+
return JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8"));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function seedProfilePinTables(): void {
|
|
35
|
+
mkdirSync(join(workspaceDir, "data", "db"), { recursive: true });
|
|
36
|
+
const db = new Database(dbPath());
|
|
37
|
+
db.run(
|
|
38
|
+
"CREATE TABLE conversations (id TEXT PRIMARY KEY, inference_profile TEXT)",
|
|
39
|
+
);
|
|
40
|
+
db.run(
|
|
41
|
+
"CREATE TABLE cron_jobs (id TEXT PRIMARY KEY, inference_profile TEXT)",
|
|
42
|
+
);
|
|
43
|
+
db.query(
|
|
44
|
+
"INSERT INTO conversations (id, inference_profile) VALUES (?, ?)",
|
|
45
|
+
).run("conv-1", "quality-optimized-backup");
|
|
46
|
+
db.query("INSERT INTO cron_jobs (id, inference_profile) VALUES (?, ?)").run(
|
|
47
|
+
"job-1",
|
|
48
|
+
"balanced-backup",
|
|
49
|
+
);
|
|
50
|
+
db.close();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readPins(table: "conversations" | "cron_jobs"): string[] {
|
|
54
|
+
const db = new Database(dbPath());
|
|
55
|
+
try {
|
|
56
|
+
return (
|
|
57
|
+
db
|
|
58
|
+
.query(`SELECT inference_profile AS profile FROM ${table} ORDER BY id`)
|
|
59
|
+
.all() as Array<{ profile: string }>
|
|
60
|
+
).map((row) => row.profile);
|
|
61
|
+
} finally {
|
|
62
|
+
db.close();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
workspaceDir = join(
|
|
68
|
+
tmpdir(),
|
|
69
|
+
`vellum-migration-149-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
70
|
+
);
|
|
71
|
+
mkdirSync(workspaceDir, { recursive: true });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
if (existsSync(workspaceDir)) {
|
|
76
|
+
assertNotLiveDb(workspaceDir);
|
|
77
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("149-repoint-backup-profile-selections migration", () => {
|
|
82
|
+
test("is registered", () => {
|
|
83
|
+
expect(repointBackupProfileSelectionsMigration.id).toBe(
|
|
84
|
+
"149-repoint-backup-profile-selections",
|
|
85
|
+
);
|
|
86
|
+
// The "newest migration" assertion lives with whichever migration is
|
|
87
|
+
// actually last; this one only has to stay in the ordered list.
|
|
88
|
+
expect(WORKSPACE_MIGRATIONS.map((m) => m.id)).toContain(
|
|
89
|
+
"149-repoint-backup-profile-selections",
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("repoints direct config selections and preserves fallback metadata", async () => {
|
|
94
|
+
writeConfig({
|
|
95
|
+
llm: {
|
|
96
|
+
activeProfile: "balanced-backup",
|
|
97
|
+
advisorProfile: "quality-optimized-backup",
|
|
98
|
+
profileOrder: ["balanced", "balanced-backup"],
|
|
99
|
+
callSites: {
|
|
100
|
+
mainAgent: { profile: "cost-optimized-backup" },
|
|
101
|
+
},
|
|
102
|
+
profiles: {
|
|
103
|
+
custom: {
|
|
104
|
+
mix: [{ profile: "latency-optimized-backup", weight: 1 }],
|
|
105
|
+
},
|
|
106
|
+
balanced: {
|
|
107
|
+
source: "managed",
|
|
108
|
+
fallbackProfile: "balanced-backup",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await repointBackupProfileSelectionsMigration.run(workspaceDir);
|
|
115
|
+
|
|
116
|
+
const llm = readConfig().llm;
|
|
117
|
+
expect(llm.activeProfile).toBe("balanced");
|
|
118
|
+
expect(llm.advisorProfile).toBe("quality-optimized");
|
|
119
|
+
expect(llm.callSites.mainAgent.profile).toBe("cost-optimized");
|
|
120
|
+
expect(llm.profiles.custom.mix[0].profile).toBe("latency-optimized");
|
|
121
|
+
expect(llm.profiles.balanced.fallbackProfile).toBe("balanced-backup");
|
|
122
|
+
expect(llm.profileOrder).toEqual(["balanced", "balanced-backup"]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("repoints conversation and schedule pins", async () => {
|
|
126
|
+
seedProfilePinTables();
|
|
127
|
+
|
|
128
|
+
await repointBackupProfileSelectionsMigration.run(workspaceDir);
|
|
129
|
+
|
|
130
|
+
expect(readPins("conversations")).toEqual(["quality-optimized"]);
|
|
131
|
+
expect(readPins("cron_jobs")).toEqual(["balanced"]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("is idempotent and tolerates a missing config or database", async () => {
|
|
135
|
+
await expect(
|
|
136
|
+
repointBackupProfileSelectionsMigration.run(workspaceDir),
|
|
137
|
+
).resolves.toBeUndefined();
|
|
138
|
+
|
|
139
|
+
writeConfig({ llm: { activeProfile: "balanced" } });
|
|
140
|
+
const before = readConfig();
|
|
141
|
+
await repointBackupProfileSelectionsMigration.run(workspaceDir);
|
|
142
|
+
expect(readConfig()).toEqual(before);
|
|
143
|
+
});
|
|
144
|
+
});
|
package/src/agent/loop.ts
CHANGED
|
@@ -43,7 +43,10 @@ import type {
|
|
|
43
43
|
ToolDefinition,
|
|
44
44
|
ToolResultContent,
|
|
45
45
|
} from "../providers/types.js";
|
|
46
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
isContextOverflowError,
|
|
48
|
+
NATIVE_WEB_SEARCH_TOOL_NAME,
|
|
49
|
+
} from "../providers/types.js";
|
|
47
50
|
import { getTool } from "../tools/registry.js";
|
|
48
51
|
import type { SensitiveOutputBinding } from "../tools/sensitive-output-placeholders.js";
|
|
49
52
|
import {
|
|
@@ -133,7 +136,7 @@ export interface AgentLoopConfig {
|
|
|
133
136
|
* `input_schema` is informational — the provider supplies the real schema.
|
|
134
137
|
*/
|
|
135
138
|
const NATIVE_WEB_SEARCH_TOOL: ToolDefinition = {
|
|
136
|
-
name:
|
|
139
|
+
name: NATIVE_WEB_SEARCH_TOOL_NAME,
|
|
137
140
|
description:
|
|
138
141
|
"Search the web for current information to ground your response.",
|
|
139
142
|
input_schema: {
|
|
@@ -369,6 +372,16 @@ export type AgentEvent =
|
|
|
369
372
|
cacheReadInputTokens?: number;
|
|
370
373
|
model: string;
|
|
371
374
|
actualProvider?: string;
|
|
375
|
+
/**
|
|
376
|
+
* Inference profile that actually governed the call, set only when a
|
|
377
|
+
* wrapper re-routed the request away from the caller's own resolution
|
|
378
|
+
* (`RetryProvider`'s fallback-profile escalation). Travels alongside
|
|
379
|
+
* `actualProvider` so the daemon's usage ledger attributes a degraded
|
|
380
|
+
* serve to the backup profile that answered rather than to the primary
|
|
381
|
+
* that failed. Absent on the normal (non-rerouted) path, where the
|
|
382
|
+
* caller's own resolution is already correct.
|
|
383
|
+
*/
|
|
384
|
+
actualInferenceProfile?: string;
|
|
372
385
|
providerDurationMs: number;
|
|
373
386
|
rawRequest?: unknown;
|
|
374
387
|
rawResponse?: unknown;
|
|
@@ -1468,6 +1481,14 @@ export class AgentLoop {
|
|
|
1468
1481
|
providerConfig.tool_choice = { type: "auto" };
|
|
1469
1482
|
}
|
|
1470
1483
|
|
|
1484
|
+
// Mark the sentinel so a route change downstream can tell it from an
|
|
1485
|
+
// app-executed `web_search` of the same name (see
|
|
1486
|
+
// `SendMessageConfig.nativeWebSearchSentinel`). Only set when true so
|
|
1487
|
+
// the wire config stays byte-identical otherwise.
|
|
1488
|
+
if (attachNativeWebSearch) {
|
|
1489
|
+
providerConfig.nativeWebSearchSentinel = true;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1471
1492
|
if (this.config.cacheTtl) {
|
|
1472
1493
|
providerConfig.cacheTtl = this.config.cacheTtl;
|
|
1473
1494
|
}
|
|
@@ -1809,6 +1830,14 @@ export class AgentLoop {
|
|
|
1809
1830
|
cacheReadInputTokens: response.usage.cacheReadInputTokens,
|
|
1810
1831
|
model: response.model,
|
|
1811
1832
|
actualProvider: response.actualProvider ?? this.provider.name,
|
|
1833
|
+
// Only present when a wrapper rerouted this call, so the normal
|
|
1834
|
+
// path's event shape stays byte-identical. There is no caller-side
|
|
1835
|
+
// fallback value to fill in: the loop's own profile resolution is
|
|
1836
|
+
// exactly what a reroute invalidates, and the daemon already reads
|
|
1837
|
+
// its own resolution when this is absent.
|
|
1838
|
+
...(response.actualInferenceProfile !== undefined
|
|
1839
|
+
? { actualInferenceProfile: response.actualInferenceProfile }
|
|
1840
|
+
: {}),
|
|
1812
1841
|
providerDurationMs,
|
|
1813
1842
|
rawRequest: response.rawRequest,
|
|
1814
1843
|
rawResponse: response.rawResponse,
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
completeSurfaceAndNotify,
|
|
32
32
|
markSurfaceCompleted,
|
|
33
33
|
} from "../daemon/conversation-surfaces.js";
|
|
34
|
+
import { withdrawDiscordApprovalCard } from "../messaging/providers/discord/withdraw.js";
|
|
34
35
|
import { withdrawSlackApprovalCard } from "../messaging/providers/slack/withdraw.js";
|
|
35
36
|
import { withdrawTelegramApprovalCard } from "../messaging/providers/telegram-bot/withdraw.js";
|
|
36
37
|
import { approvalCardSurfaceId } from "../notifications/approval-card-data.js";
|
|
@@ -131,6 +132,8 @@ export async function withdrawGuardianRequestCards(
|
|
|
131
132
|
decidedAction,
|
|
132
133
|
hasOriginGuardianReply ?? false,
|
|
133
134
|
);
|
|
135
|
+
} else if (delivery.destinationChannel === "discord") {
|
|
136
|
+
await withdrawDiscordCard(delivery, status, decidedAction);
|
|
134
137
|
}
|
|
135
138
|
// WhatsApp direct delivery can't edit a message in place (it would
|
|
136
139
|
// post a new one), so its stale clicks are left to the existing
|
|
@@ -231,6 +234,25 @@ async function withdrawSlackCard(
|
|
|
231
234
|
* outcome. No-ops when the channel-native message id was not captured at
|
|
232
235
|
* delivery time.
|
|
233
236
|
*/
|
|
237
|
+
async function withdrawDiscordCard(
|
|
238
|
+
delivery: GuardianRequestDeliveryWire,
|
|
239
|
+
status: GuardianRequestStatus,
|
|
240
|
+
decidedAction: ApprovalAction | undefined,
|
|
241
|
+
): Promise<void> {
|
|
242
|
+
if (!delivery.destinationChatId || !delivery.destinationMessageId) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
// The Discord edit rewrites the card's actionable tail to the outcome, so
|
|
246
|
+
// it serves both halves of withdrawal in one call and needs no separate
|
|
247
|
+
// status reply regardless of where the decision originated.
|
|
248
|
+
await withdrawDiscordApprovalCard({
|
|
249
|
+
guardianUserId: delivery.destinationChatId,
|
|
250
|
+
messageId: delivery.destinationMessageId,
|
|
251
|
+
status,
|
|
252
|
+
...(decidedAction ? { decidedAction } : {}),
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
234
256
|
async function withdrawTelegramCard(
|
|
235
257
|
delivery: GuardianRequestDeliveryWire,
|
|
236
258
|
status: GuardianRequestStatus,
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for ensureAvatarRaster and ensureAvatarRasterPath.
|
|
3
|
+
*
|
|
4
|
+
* Character re-render routes through the native @resvg/resvg-js binding. As in
|
|
5
|
+
* avatar-store.test.ts, the re-render case branches on `isResvgAvailable()` so
|
|
6
|
+
* the suite is deterministic whether or not the binding is installed.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
existsSync,
|
|
10
|
+
mkdirSync,
|
|
11
|
+
mkdtempSync,
|
|
12
|
+
rmSync,
|
|
13
|
+
symlinkSync,
|
|
14
|
+
writeFileSync,
|
|
15
|
+
} from "node:fs";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
ensureAvatarRaster,
|
|
22
|
+
ensureAvatarRasterPath,
|
|
23
|
+
} from "../ensure-raster.js";
|
|
24
|
+
import {
|
|
25
|
+
__resetResvgCacheForTests,
|
|
26
|
+
__setResvgCacheForTests,
|
|
27
|
+
isResvgAvailable,
|
|
28
|
+
} from "../resvg-lazy.js";
|
|
29
|
+
|
|
30
|
+
const VALID_TRAITS = { bodyShape: "blob", eyeStyle: "curious", color: "green" };
|
|
31
|
+
|
|
32
|
+
const IMAGE_FILENAME = "avatar-image.png";
|
|
33
|
+
const MANIFEST_FILENAME = "avatar.json";
|
|
34
|
+
const NATIVE_RENDER_TEST_TIMEOUT_MS = 15_000;
|
|
35
|
+
|
|
36
|
+
const FAKE_PNG = Buffer.from("not-really-a-png");
|
|
37
|
+
|
|
38
|
+
describe("ensureAvatarRaster", () => {
|
|
39
|
+
let workspaceDir: string;
|
|
40
|
+
let avatarDir: string;
|
|
41
|
+
let prevWorkspaceDir: string | undefined;
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
workspaceDir = mkdtempSync(join(tmpdir(), "ensure-raster-test-"));
|
|
45
|
+
avatarDir = join(workspaceDir, "data", "avatar");
|
|
46
|
+
mkdirSync(avatarDir, { recursive: true });
|
|
47
|
+
prevWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
|
|
48
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
__resetResvgCacheForTests();
|
|
53
|
+
if (prevWorkspaceDir === undefined) {
|
|
54
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
55
|
+
} else {
|
|
56
|
+
process.env.VELLUM_WORKSPACE_DIR = prevWorkspaceDir;
|
|
57
|
+
}
|
|
58
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const writeManifestFile = (manifest: Record<string, unknown>) => {
|
|
62
|
+
writeFileSync(join(avatarDir, MANIFEST_FILENAME), JSON.stringify(manifest));
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
test("returns null for kind none", async () => {
|
|
66
|
+
writeManifestFile({
|
|
67
|
+
kind: "none",
|
|
68
|
+
traits: null,
|
|
69
|
+
source: null,
|
|
70
|
+
image: null,
|
|
71
|
+
});
|
|
72
|
+
expect(await ensureAvatarRaster()).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("returns null with no manifest and no legacy files", async () => {
|
|
76
|
+
expect(await ensureAvatarRaster()).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("returns the existing PNG bytes for an image avatar", async () => {
|
|
80
|
+
writeFileSync(join(avatarDir, IMAGE_FILENAME), FAKE_PNG);
|
|
81
|
+
writeManifestFile({
|
|
82
|
+
kind: "image",
|
|
83
|
+
traits: null,
|
|
84
|
+
source: "upload",
|
|
85
|
+
image: { updatedAt: new Date().toISOString(), etag: "0123456789abcdef" },
|
|
86
|
+
});
|
|
87
|
+
const raster = await ensureAvatarRaster();
|
|
88
|
+
expect(raster).not.toBeNull();
|
|
89
|
+
expect(raster!.equals(FAKE_PNG)).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("returns the existing PNG for a character without re-rendering", async () => {
|
|
93
|
+
writeFileSync(join(avatarDir, IMAGE_FILENAME), FAKE_PNG);
|
|
94
|
+
writeManifestFile({
|
|
95
|
+
kind: "character",
|
|
96
|
+
traits: VALID_TRAITS,
|
|
97
|
+
source: "builder",
|
|
98
|
+
image: null,
|
|
99
|
+
});
|
|
100
|
+
// Force the unavailable path: a re-render attempt would return null.
|
|
101
|
+
__setResvgCacheForTests({ available: false, error: new Error("nope") });
|
|
102
|
+
const raster = await ensureAvatarRaster();
|
|
103
|
+
expect(raster).not.toBeNull();
|
|
104
|
+
expect(raster!.equals(FAKE_PNG)).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("returns null for a symlinked PNG but still reports its path", async () => {
|
|
108
|
+
const outside = join(workspaceDir, "outside.png");
|
|
109
|
+
writeFileSync(outside, FAKE_PNG);
|
|
110
|
+
symlinkSync(outside, join(avatarDir, IMAGE_FILENAME));
|
|
111
|
+
writeManifestFile({
|
|
112
|
+
kind: "image",
|
|
113
|
+
traits: null,
|
|
114
|
+
source: "upload",
|
|
115
|
+
image: { updatedAt: new Date().toISOString(), etag: "0123456789abcdef" },
|
|
116
|
+
});
|
|
117
|
+
expect(await ensureAvatarRasterPath()).toBe(
|
|
118
|
+
join(avatarDir, IMAGE_FILENAME),
|
|
119
|
+
);
|
|
120
|
+
expect(await ensureAvatarRaster()).toBeNull();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("returns null when the avatar dir is a symlink to another workspace", async () => {
|
|
124
|
+
const foreign = mkdtempSync(join(tmpdir(), "foreign-avatar-"));
|
|
125
|
+
writeFileSync(join(foreign, IMAGE_FILENAME), FAKE_PNG);
|
|
126
|
+
rmSync(avatarDir, { recursive: true, force: true });
|
|
127
|
+
symlinkSync(foreign, avatarDir);
|
|
128
|
+
writeManifestFile({
|
|
129
|
+
kind: "image",
|
|
130
|
+
traits: null,
|
|
131
|
+
source: "upload",
|
|
132
|
+
image: { updatedAt: new Date().toISOString(), etag: "0123456789abcdef" },
|
|
133
|
+
});
|
|
134
|
+
expect(await ensureAvatarRaster()).toBeNull();
|
|
135
|
+
rmSync(foreign, { recursive: true, force: true });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("returns null for an image avatar whose PNG is missing", async () => {
|
|
139
|
+
writeManifestFile({
|
|
140
|
+
kind: "image",
|
|
141
|
+
traits: null,
|
|
142
|
+
source: "upload",
|
|
143
|
+
image: { updatedAt: new Date().toISOString(), etag: "0123456789abcdef" },
|
|
144
|
+
});
|
|
145
|
+
expect(await ensureAvatarRaster()).toBeNull();
|
|
146
|
+
expect(existsSync(join(avatarDir, IMAGE_FILENAME))).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("returns null for a character when resvg is unavailable", async () => {
|
|
150
|
+
writeManifestFile({
|
|
151
|
+
kind: "character",
|
|
152
|
+
traits: VALID_TRAITS,
|
|
153
|
+
source: "builder",
|
|
154
|
+
image: null,
|
|
155
|
+
});
|
|
156
|
+
__setResvgCacheForTests({ available: false, error: new Error("nope") });
|
|
157
|
+
expect(await ensureAvatarRaster()).toBeNull();
|
|
158
|
+
expect(existsSync(join(avatarDir, IMAGE_FILENAME))).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test(
|
|
162
|
+
"re-renders a character whose PNG is missing",
|
|
163
|
+
async () => {
|
|
164
|
+
writeManifestFile({
|
|
165
|
+
kind: "character",
|
|
166
|
+
traits: VALID_TRAITS,
|
|
167
|
+
source: "builder",
|
|
168
|
+
image: null,
|
|
169
|
+
});
|
|
170
|
+
const raster = await ensureAvatarRaster();
|
|
171
|
+
if (!isResvgAvailable()) {
|
|
172
|
+
expect(raster).toBeNull();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
expect(raster).not.toBeNull();
|
|
176
|
+
expect(raster!.length).toBeGreaterThan(0);
|
|
177
|
+
// PNG magic bytes
|
|
178
|
+
expect(
|
|
179
|
+
raster!.subarray(0, 4).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47])),
|
|
180
|
+
).toBe(true);
|
|
181
|
+
expect(existsSync(join(avatarDir, IMAGE_FILENAME))).toBe(true);
|
|
182
|
+
},
|
|
183
|
+
NATIVE_RENDER_TEST_TIMEOUT_MS,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
test("accepts an explicit state instead of reading the manifest", async () => {
|
|
187
|
+
writeFileSync(join(avatarDir, IMAGE_FILENAME), FAKE_PNG);
|
|
188
|
+
// No manifest on disk; explicit `none` wins over the legacy PNG.
|
|
189
|
+
expect(
|
|
190
|
+
await ensureAvatarRaster({
|
|
191
|
+
kind: "none",
|
|
192
|
+
traits: null,
|
|
193
|
+
source: null,
|
|
194
|
+
image: null,
|
|
195
|
+
}),
|
|
196
|
+
).toBeNull();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("ensureAvatarRasterPath", () => {
|
|
200
|
+
test("returns null for kind none", async () => {
|
|
201
|
+
writeFileSync(join(avatarDir, IMAGE_FILENAME), FAKE_PNG);
|
|
202
|
+
writeManifestFile({
|
|
203
|
+
kind: "none",
|
|
204
|
+
traits: null,
|
|
205
|
+
source: null,
|
|
206
|
+
image: null,
|
|
207
|
+
});
|
|
208
|
+
expect(await ensureAvatarRasterPath()).toBeNull();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("returns the path of an existing PNG without reading it", async () => {
|
|
212
|
+
writeFileSync(join(avatarDir, IMAGE_FILENAME), FAKE_PNG);
|
|
213
|
+
writeManifestFile({
|
|
214
|
+
kind: "image",
|
|
215
|
+
traits: null,
|
|
216
|
+
source: "upload",
|
|
217
|
+
image: {
|
|
218
|
+
updatedAt: new Date().toISOString(),
|
|
219
|
+
etag: "0123456789abcdef",
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
expect(await ensureAvatarRasterPath()).toBe(
|
|
223
|
+
join(avatarDir, IMAGE_FILENAME),
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("returns null for a symlinked PNG but still reports its path", async () => {
|
|
228
|
+
const outside = join(workspaceDir, "outside.png");
|
|
229
|
+
writeFileSync(outside, FAKE_PNG);
|
|
230
|
+
symlinkSync(outside, join(avatarDir, IMAGE_FILENAME));
|
|
231
|
+
writeManifestFile({
|
|
232
|
+
kind: "image",
|
|
233
|
+
traits: null,
|
|
234
|
+
source: "upload",
|
|
235
|
+
image: {
|
|
236
|
+
updatedAt: new Date().toISOString(),
|
|
237
|
+
etag: "0123456789abcdef",
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
expect(await ensureAvatarRasterPath()).toBe(
|
|
241
|
+
join(avatarDir, IMAGE_FILENAME),
|
|
242
|
+
);
|
|
243
|
+
expect(await ensureAvatarRaster()).toBeNull();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("returns null for an image avatar whose PNG is missing", async () => {
|
|
247
|
+
writeManifestFile({
|
|
248
|
+
kind: "image",
|
|
249
|
+
traits: null,
|
|
250
|
+
source: "upload",
|
|
251
|
+
image: {
|
|
252
|
+
updatedAt: new Date().toISOString(),
|
|
253
|
+
etag: "0123456789abcdef",
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
expect(await ensureAvatarRasterPath()).toBeNull();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("returns null for a character when resvg is unavailable", async () => {
|
|
260
|
+
writeManifestFile({
|
|
261
|
+
kind: "character",
|
|
262
|
+
traits: VALID_TRAITS,
|
|
263
|
+
source: "builder",
|
|
264
|
+
image: null,
|
|
265
|
+
});
|
|
266
|
+
__setResvgCacheForTests({ available: false, error: new Error("nope") });
|
|
267
|
+
expect(await ensureAvatarRasterPath()).toBeNull();
|
|
268
|
+
expect(existsSync(join(avatarDir, IMAGE_FILENAME))).toBe(false);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test(
|
|
272
|
+
"re-renders a character whose PNG is missing and returns its path",
|
|
273
|
+
async () => {
|
|
274
|
+
writeManifestFile({
|
|
275
|
+
kind: "character",
|
|
276
|
+
traits: VALID_TRAITS,
|
|
277
|
+
source: "builder",
|
|
278
|
+
image: null,
|
|
279
|
+
});
|
|
280
|
+
const path = await ensureAvatarRasterPath();
|
|
281
|
+
if (!isResvgAvailable()) {
|
|
282
|
+
expect(path).toBeNull();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
expect(path).toBe(join(avatarDir, IMAGE_FILENAME));
|
|
286
|
+
expect(existsSync(path!)).toBe(true);
|
|
287
|
+
},
|
|
288
|
+
NATIVE_RENDER_TEST_TIMEOUT_MS,
|
|
289
|
+
);
|
|
290
|
+
});
|
|
291
|
+
});
|