@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
|
@@ -17,66 +17,26 @@ import {
|
|
|
17
17
|
} from "node:fs";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
|
|
20
|
-
import { getLogger } from "../util/logger.js";
|
|
21
20
|
import {
|
|
22
21
|
AVATAR_IMAGE_FILENAME,
|
|
23
22
|
AVATAR_MANIFEST_FILENAME,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
export interface AvatarImageMeta {
|
|
34
|
-
updatedAt: string;
|
|
35
|
-
etag: string;
|
|
36
|
-
}
|
|
23
|
+
AVATAR_TRAITS_FILENAME,
|
|
24
|
+
type AvatarImageMeta,
|
|
25
|
+
type AvatarKind,
|
|
26
|
+
type AvatarSource,
|
|
27
|
+
type AvatarState,
|
|
28
|
+
type CharacterTraits,
|
|
29
|
+
deriveAvatarFromLegacyFiles,
|
|
30
|
+
parseAvatarManifest,
|
|
31
|
+
} from "@vellumai/avatar-manifest";
|
|
37
32
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
traits: CharacterTraits | null;
|
|
41
|
-
source: AvatarSource | null;
|
|
42
|
-
image: AvatarImageMeta | null;
|
|
43
|
-
}
|
|
33
|
+
import { getLogger } from "../util/logger.js";
|
|
34
|
+
import { getAvatarDir } from "../util/platform.js";
|
|
44
35
|
|
|
45
|
-
const
|
|
46
|
-
"character",
|
|
47
|
-
"image",
|
|
48
|
-
"none",
|
|
49
|
-
]);
|
|
36
|
+
const log = getLogger("avatar-manifest");
|
|
50
37
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!value || typeof value !== "object") {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
const t = value as Record<string, unknown>;
|
|
57
|
-
return (
|
|
58
|
-
typeof t.bodyShape === "string" &&
|
|
59
|
-
!!t.bodyShape &&
|
|
60
|
-
typeof t.eyeStyle === "string" &&
|
|
61
|
-
!!t.eyeStyle &&
|
|
62
|
-
typeof t.color === "string" &&
|
|
63
|
-
!!t.color
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Narrows an unknown value to valid AvatarImageMeta (presence check only). */
|
|
68
|
-
function isValidImageMeta(value: unknown): value is AvatarImageMeta {
|
|
69
|
-
if (!value || typeof value !== "object") {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
const m = value as Record<string, unknown>;
|
|
73
|
-
return (
|
|
74
|
-
typeof m.updatedAt === "string" &&
|
|
75
|
-
!!m.updatedAt &&
|
|
76
|
-
typeof m.etag === "string" &&
|
|
77
|
-
!!m.etag
|
|
78
|
-
);
|
|
79
|
-
}
|
|
38
|
+
export type { AvatarImageMeta, AvatarKind, AvatarSource, AvatarState };
|
|
39
|
+
export type { CharacterTraits };
|
|
80
40
|
|
|
81
41
|
/**
|
|
82
42
|
* Reads and validates the avatar manifest. Returns `null` when the manifest is
|
|
@@ -93,32 +53,9 @@ export function readManifest(
|
|
|
93
53
|
}
|
|
94
54
|
|
|
95
55
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
const obj = parsed as Record<string, unknown>;
|
|
101
|
-
if (typeof obj.kind !== "string" || !AVATAR_KINDS.has(obj.kind)) {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
const kind = obj.kind as AvatarKind;
|
|
105
|
-
|
|
106
|
-
// Reject partial manifests: a valid `kind` with a missing/malformed payload
|
|
107
|
-
// would otherwise short-circuit the legacy fallback and surface an avatar
|
|
108
|
-
// with null traits/image.
|
|
109
|
-
if (kind === "character" && !isValidTraits(obj.traits)) {
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
if (kind === "image" && !isValidImageMeta(obj.image)) {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
kind,
|
|
118
|
-
traits: (obj.traits as CharacterTraits | null) ?? null,
|
|
119
|
-
source: (obj.source as AvatarSource | null) ?? null,
|
|
120
|
-
image: (obj.image as AvatarImageMeta | null) ?? null,
|
|
121
|
-
};
|
|
56
|
+
return parseAvatarManifest(
|
|
57
|
+
JSON.parse(readFileSync(manifestPath, "utf-8")) as unknown,
|
|
58
|
+
);
|
|
122
59
|
} catch (err) {
|
|
123
60
|
log.warn({ err }, "Failed to read avatar manifest — treating as absent");
|
|
124
61
|
return null;
|
|
@@ -167,24 +104,16 @@ export function computeImageMeta(imagePath: string): AvatarImageMeta {
|
|
|
167
104
|
* Used by the read handlers to self-heal once on a manifest-miss: the derived
|
|
168
105
|
* state is persisted via `writeManifest` so subsequent reads are manifest-only.
|
|
169
106
|
*
|
|
170
|
-
* Inference is
|
|
171
|
-
* exists, the result is `character` regardless of whether a PNG is also
|
|
172
|
-
* present. We deliberately do NOT compare mtimes to break the both-present
|
|
173
|
-
* tie, because the builder writes the rendered PNG *after* the traits file
|
|
174
|
-
* (see traits-png-sync.ts), so the PNG is always newer even when the
|
|
175
|
-
* character is the source of truth. Comparing mtimes would misclassify a
|
|
176
|
-
* builder-generated character as an uploaded image.
|
|
107
|
+
* Inference is traits-first (see `deriveAvatarFromLegacyFiles`).
|
|
177
108
|
*/
|
|
178
109
|
export function deriveStateFromLegacyFiles(
|
|
179
110
|
avatarDir: string = getAvatarDir(),
|
|
180
111
|
): AvatarState {
|
|
181
|
-
const traitsPath = join(avatarDir,
|
|
112
|
+
const traitsPath = join(avatarDir, AVATAR_TRAITS_FILENAME);
|
|
113
|
+
let traitsJson: unknown;
|
|
182
114
|
if (existsSync(traitsPath)) {
|
|
183
115
|
try {
|
|
184
|
-
|
|
185
|
-
if (isValidTraits(traits)) {
|
|
186
|
-
return { kind: "character", traits, source: null, image: null };
|
|
187
|
-
}
|
|
116
|
+
traitsJson = JSON.parse(readFileSync(traitsPath, "utf-8")) as unknown;
|
|
188
117
|
} catch (err) {
|
|
189
118
|
log.warn(
|
|
190
119
|
{ err },
|
|
@@ -194,14 +123,33 @@ export function deriveStateFromLegacyFiles(
|
|
|
194
123
|
}
|
|
195
124
|
|
|
196
125
|
const imagePath = join(avatarDir, AVATAR_IMAGE_FILENAME);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
126
|
+
const derived = deriveAvatarFromLegacyFiles({
|
|
127
|
+
traitsJson,
|
|
128
|
+
hasImage: existsSync(imagePath),
|
|
129
|
+
});
|
|
130
|
+
switch (derived.kind) {
|
|
131
|
+
case "character":
|
|
132
|
+
return {
|
|
133
|
+
kind: "character",
|
|
134
|
+
traits: derived.traits,
|
|
135
|
+
source: null,
|
|
136
|
+
image: null,
|
|
137
|
+
};
|
|
138
|
+
case "image":
|
|
139
|
+
return {
|
|
140
|
+
kind: "image",
|
|
141
|
+
traits: null,
|
|
142
|
+
source: null,
|
|
143
|
+
image: computeImageMeta(imagePath),
|
|
144
|
+
};
|
|
145
|
+
case "none":
|
|
146
|
+
return { kind: "none", traits: null, source: null, image: null };
|
|
204
147
|
}
|
|
148
|
+
}
|
|
205
149
|
|
|
206
|
-
|
|
150
|
+
/** Manifest first, legacy sidecars as fallback. Never persists. */
|
|
151
|
+
export function readAvatarState(
|
|
152
|
+
avatarDir: string = getAvatarDir(),
|
|
153
|
+
): AvatarState {
|
|
154
|
+
return readManifest(avatarDir) ?? deriveStateFromLegacyFiles(avatarDir);
|
|
207
155
|
}
|
|
@@ -15,13 +15,14 @@ import { randomUUID } from "node:crypto";
|
|
|
15
15
|
import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
|
|
18
|
-
import { getLogger } from "../util/logger.js";
|
|
19
18
|
import {
|
|
20
19
|
AVATAR_IMAGE_FILENAME,
|
|
21
20
|
AVATAR_MANIFEST_FILENAME,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
AVATAR_TRAITS_FILENAME,
|
|
22
|
+
} from "@vellumai/avatar-manifest";
|
|
23
|
+
|
|
24
|
+
import { getLogger } from "../util/logger.js";
|
|
25
|
+
import { getAvatarDir, getAvatarImagePath } from "../util/platform.js";
|
|
25
26
|
import {
|
|
26
27
|
type AvatarSource,
|
|
27
28
|
computeImageMeta,
|
|
@@ -30,7 +31,6 @@ import {
|
|
|
30
31
|
import {
|
|
31
32
|
ASCII_FILENAME,
|
|
32
33
|
type CharacterTraits,
|
|
33
|
-
TRAITS_FILENAME,
|
|
34
34
|
type TraitsSyncResult,
|
|
35
35
|
writeTraitsAndRenderAvatar,
|
|
36
36
|
} from "./traits-png-sync.js";
|
|
@@ -77,7 +77,7 @@ export function setImage(pngBuffer: Buffer, source: AvatarSource): void {
|
|
|
77
77
|
writeFileSync(pngTmp, pngBuffer);
|
|
78
78
|
renameSync(pngTmp, pngPath);
|
|
79
79
|
|
|
80
|
-
rmSync(join(avatarDir,
|
|
80
|
+
rmSync(join(avatarDir, AVATAR_TRAITS_FILENAME), { force: true });
|
|
81
81
|
rmSync(join(avatarDir, ASCII_FILENAME), { force: true });
|
|
82
82
|
|
|
83
83
|
writeManifest({
|
|
@@ -105,7 +105,7 @@ export function clearAvatar(): void {
|
|
|
105
105
|
mkdirSync(avatarDir, { recursive: true });
|
|
106
106
|
|
|
107
107
|
rmSync(join(avatarDir, AVATAR_IMAGE_FILENAME), { force: true });
|
|
108
|
-
rmSync(join(avatarDir,
|
|
108
|
+
rmSync(join(avatarDir, AVATAR_TRAITS_FILENAME), { force: true });
|
|
109
109
|
rmSync(join(avatarDir, ASCII_FILENAME), { force: true });
|
|
110
110
|
rmSync(join(avatarDir, AVATAR_MANIFEST_FILENAME), { force: true });
|
|
111
111
|
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import {
|
|
2
|
+
closeSync,
|
|
3
|
+
constants,
|
|
4
|
+
existsSync,
|
|
5
|
+
fstatSync,
|
|
6
|
+
lstatSync,
|
|
7
|
+
openSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
realpathSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { basename, dirname, isAbsolute, join, relative } from "node:path";
|
|
12
|
+
|
|
13
|
+
import { getLogger } from "../util/logger.js";
|
|
14
|
+
import { getAvatarImagePath, getWorkspaceDir } from "../util/platform.js";
|
|
15
|
+
import { type AvatarState, readAvatarState } from "./avatar-manifest.js";
|
|
16
|
+
import { writeTraitsAndRenderAvatar } from "./traits-png-sync.js";
|
|
17
|
+
|
|
18
|
+
const log = getLogger("ensure-raster");
|
|
19
|
+
|
|
20
|
+
/** Largest raster any reader serves; a bigger file is treated as unreadable. */
|
|
21
|
+
export const AVATAR_RASTER_MAX_BYTES = 5 * 1024 * 1024;
|
|
22
|
+
|
|
23
|
+
function isWithin(dir: string, filePath: string): boolean {
|
|
24
|
+
const rel = relative(dir, filePath);
|
|
25
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Reads the raster only when it is a regular file that truly lives under the
|
|
30
|
+
* avatar dir: a symlinked PNG (or a symlinked ancestor) would let the
|
|
31
|
+
* workspace hand a reader an arbitrary host file. Validation and the read
|
|
32
|
+
* share one descriptor so a concurrent swap of the path cannot slip a
|
|
33
|
+
* different file past the checks, and the descriptor's location is
|
|
34
|
+
* revalidated after the open. Returns null when the file is missing,
|
|
35
|
+
* symlinked, outside the avatar dir, not a regular file, or over the cap.
|
|
36
|
+
*/
|
|
37
|
+
export function readContainedAvatarRaster(rasterPath: string): Buffer | null {
|
|
38
|
+
let fd: number | undefined;
|
|
39
|
+
try {
|
|
40
|
+
// Anchored on the real workspace root: a data/avatar symlink pointing at
|
|
41
|
+
// another workspace resolves outside it and is rejected.
|
|
42
|
+
// The root is resolved once and reused after the open so a workspace
|
|
43
|
+
// pathname swapped for a symlink mid-read cannot re-anchor the check.
|
|
44
|
+
const realRoot = realpathSync(getWorkspaceDir());
|
|
45
|
+
const realDir = realpathSync(dirname(rasterPath));
|
|
46
|
+
if (!isWithin(realRoot, realDir)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const realPath = join(realDir, basename(rasterPath));
|
|
50
|
+
const linkStats = lstatSync(realPath);
|
|
51
|
+
if (!linkStats.isFile()) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
fd = openSync(realPath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
55
|
+
const stats = fstatSync(fd);
|
|
56
|
+
if (
|
|
57
|
+
!stats.isFile() ||
|
|
58
|
+
stats.dev !== linkStats.dev ||
|
|
59
|
+
stats.ino !== linkStats.ino ||
|
|
60
|
+
stats.size > AVATAR_RASTER_MAX_BYTES
|
|
61
|
+
) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
// O_NOFOLLOW guards only the last component; an ancestor swapped for an
|
|
65
|
+
// outside symlink between the check and the open either stays swapped
|
|
66
|
+
// (fresh realpath resolves outside) or is swapped back (the inside file
|
|
67
|
+
// is a different inode than the opened fd). Node has no openat.
|
|
68
|
+
const reopenedPath = realpathSync(rasterPath);
|
|
69
|
+
if (!isWithin(realRoot, reopenedPath)) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const reopenedStats = lstatSync(reopenedPath);
|
|
73
|
+
if (
|
|
74
|
+
!reopenedStats.isFile() ||
|
|
75
|
+
reopenedStats.dev !== stats.dev ||
|
|
76
|
+
reopenedStats.ino !== stats.ino
|
|
77
|
+
) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const bytes = readFileSync(fd);
|
|
81
|
+
return bytes.length > AVATAR_RASTER_MAX_BYTES ? null : bytes;
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
} finally {
|
|
85
|
+
if (fd !== undefined) {
|
|
86
|
+
closeSync(fd);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns the on-disk path of the current avatar PNG, or null when there is
|
|
93
|
+
* none. Never reads the raster bytes.
|
|
94
|
+
*
|
|
95
|
+
* Side-effect-free when the PNG already exists. A character whose PNG is
|
|
96
|
+
* missing is re-rendered from its persisted traits (best-effort; null when
|
|
97
|
+
* the native rasterizer is unavailable or the render fails). An image-kind
|
|
98
|
+
* avatar with a missing PNG cannot be recovered and yields null.
|
|
99
|
+
*
|
|
100
|
+
* Callers that already hold the avatar state can pass it to skip the re-read.
|
|
101
|
+
*/
|
|
102
|
+
export async function ensureAvatarRasterPath(
|
|
103
|
+
state: AvatarState = readAvatarState(),
|
|
104
|
+
): Promise<string | null> {
|
|
105
|
+
if (state.kind === "none") {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const avatarPath = getAvatarImagePath();
|
|
110
|
+
if (existsSync(avatarPath)) {
|
|
111
|
+
return avatarPath;
|
|
112
|
+
}
|
|
113
|
+
if (state.kind !== "character" || !state.traits) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const result = writeTraitsAndRenderAvatar(state.traits);
|
|
119
|
+
if (result.ok) {
|
|
120
|
+
return avatarPath;
|
|
121
|
+
}
|
|
122
|
+
log.warn({ reason: result.reason }, "Avatar raster re-render failed");
|
|
123
|
+
} catch (err) {
|
|
124
|
+
log.warn({ err }, "Avatar raster re-render threw");
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Returns the PNG raster for the current avatar, or null when there is none
|
|
131
|
+
* or it cannot be read (see `readContainedAvatarRaster`). Same regeneration
|
|
132
|
+
* semantics as `ensureAvatarRasterPath`.
|
|
133
|
+
*/
|
|
134
|
+
export async function ensureAvatarRaster(
|
|
135
|
+
state: AvatarState = readAvatarState(),
|
|
136
|
+
): Promise<Buffer | null> {
|
|
137
|
+
const avatarPath = await ensureAvatarRasterPath(state);
|
|
138
|
+
if (!avatarPath) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const bytes = readContainedAvatarRaster(avatarPath);
|
|
142
|
+
if (!bytes) {
|
|
143
|
+
log.warn({ path: avatarPath }, "Avatar raster unreadable or not contained");
|
|
144
|
+
}
|
|
145
|
+
return bytes;
|
|
146
|
+
}
|
|
@@ -2,8 +2,14 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
|
+
import {
|
|
6
|
+
AVATAR_IMAGE_FILENAME,
|
|
7
|
+
AVATAR_TRAITS_FILENAME,
|
|
8
|
+
type CharacterTraits,
|
|
9
|
+
} from "@vellumai/avatar-manifest";
|
|
10
|
+
|
|
5
11
|
import { getLogger } from "../util/logger.js";
|
|
6
|
-
import {
|
|
12
|
+
import { getAvatarDir } from "../util/platform.js";
|
|
7
13
|
import { renderCharacterAscii } from "./ascii-renderer.js";
|
|
8
14
|
import { getCharacterComponents } from "./character-components.js";
|
|
9
15
|
import { renderCharacterPng } from "./png-renderer.js";
|
|
@@ -11,17 +17,10 @@ import { isResvgAvailable } from "./resvg-lazy.js";
|
|
|
11
17
|
|
|
12
18
|
const log = getLogger("traits-png-sync");
|
|
13
19
|
|
|
14
|
-
/** Sidecar filename for the persisted character traits JSON. */
|
|
15
|
-
export const TRAITS_FILENAME = "character-traits.json";
|
|
16
|
-
|
|
17
20
|
/** Sidecar filename for the rendered ASCII art. */
|
|
18
21
|
export const ASCII_FILENAME = "character-ascii.txt";
|
|
19
22
|
|
|
20
|
-
export
|
|
21
|
-
bodyShape: string;
|
|
22
|
-
eyeStyle: string;
|
|
23
|
-
color: string;
|
|
24
|
-
}
|
|
23
|
+
export type { CharacterTraits } from "@vellumai/avatar-manifest";
|
|
25
24
|
|
|
26
25
|
export type TraitsSyncResult =
|
|
27
26
|
| { ok: true; asciiWritten: boolean }
|
|
@@ -171,7 +170,7 @@ export function writeTraitsAndRenderAvatar(
|
|
|
171
170
|
}
|
|
172
171
|
|
|
173
172
|
const avatarDir = getAvatarDir();
|
|
174
|
-
const traitsPath = join(avatarDir,
|
|
173
|
+
const traitsPath = join(avatarDir, AVATAR_TRAITS_FILENAME);
|
|
175
174
|
|
|
176
175
|
try {
|
|
177
176
|
mkdirSync(avatarDir, { recursive: true });
|
|
@@ -82,7 +82,6 @@ mock.module("../util/platform.js", () => ({
|
|
|
82
82
|
getSandboxWorkingDir: () => testWorkspaceDir,
|
|
83
83
|
getSoundsDir: () => workspacePath("data", "sounds"),
|
|
84
84
|
getAvatarDir: () => workspacePath("data", "avatar"),
|
|
85
|
-
AVATAR_IMAGE_FILENAME: "avatar-image.png",
|
|
86
85
|
getAvatarImagePath: () => workspacePath("data", "avatar", "avatar-image.png"),
|
|
87
86
|
getXdgVellumConfigDirName: () => ".vellum",
|
|
88
87
|
getProfilerRootDir: () => workspacePath("data", "profiler"),
|
|
@@ -134,7 +134,7 @@ describe("getDefaultOffsiteBackupsDir", () => {
|
|
|
134
134
|
|
|
135
135
|
describe("resolveOffsiteDestinations", () => {
|
|
136
136
|
test("returns iCloud default with encrypt=true when override is null", () => {
|
|
137
|
-
const result = resolveOffsiteDestinations(null);
|
|
137
|
+
const result = resolveOffsiteDestinations(null, "darwin");
|
|
138
138
|
expect(result).toHaveLength(1);
|
|
139
139
|
expect(result[0].encrypt).toBe(true);
|
|
140
140
|
expect(result[0].path).toContain(
|
|
@@ -143,11 +143,15 @@ describe("resolveOffsiteDestinations", () => {
|
|
|
143
143
|
});
|
|
144
144
|
|
|
145
145
|
test("returns iCloud default when override is undefined", () => {
|
|
146
|
-
const result = resolveOffsiteDestinations(undefined);
|
|
146
|
+
const result = resolveOffsiteDestinations(undefined, "darwin");
|
|
147
147
|
expect(result).toHaveLength(1);
|
|
148
148
|
expect(result[0].encrypt).toBe(true);
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
test("returns no implicit destination on Windows", () => {
|
|
152
|
+
expect(resolveOffsiteDestinations(null, "win32")).toEqual([]);
|
|
153
|
+
});
|
|
154
|
+
|
|
151
155
|
test("returns an empty array unchanged", () => {
|
|
152
156
|
expect(resolveOffsiteDestinations([])).toEqual([]);
|
|
153
157
|
});
|
package/src/backup/paths.ts
CHANGED
|
@@ -115,15 +115,17 @@ export function deriveSafeAncestor(destinationPath: string): string {
|
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
117
|
* Resolves the list of offsite backup destinations from an optional config
|
|
118
|
-
* override.
|
|
119
|
-
*
|
|
120
|
-
* enabled. When `override` is an array (including the empty array), returns it
|
|
121
|
-
* unchanged so callers never need to null-check.
|
|
118
|
+
* override. A null override uses encrypted iCloud Drive on macOS and no
|
|
119
|
+
* implicit destination on other platforms. Explicit arrays are unchanged.
|
|
122
120
|
*/
|
|
123
121
|
export function resolveOffsiteDestinations(
|
|
124
122
|
override?: BackupDestination[] | null,
|
|
123
|
+
platform: NodeJS.Platform = process.platform,
|
|
125
124
|
): BackupDestination[] {
|
|
126
125
|
if (override == null) {
|
|
126
|
+
if (platform !== "darwin") {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
127
129
|
return [{ path: getDefaultOffsiteBackupsDir(), encrypt: true }];
|
|
128
130
|
}
|
|
129
131
|
return override;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { existsSync, rmSync } from "node:fs";
|
|
13
13
|
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
14
|
-
import { dirname, join, resolve } from "node:path";
|
|
14
|
+
import { delimiter, dirname, join, resolve } from "node:path";
|
|
15
15
|
|
|
16
16
|
import { getLogger } from "../util/logger.js";
|
|
17
17
|
import { ensureCompilerTools } from "./compiler-tools.js";
|
|
@@ -469,7 +469,7 @@ export async function runCompile(
|
|
|
469
469
|
const cacheNodeModules = join(getCacheDir(), "node_modules");
|
|
470
470
|
const nodePath = [preactParent, cacheNodeModules]
|
|
471
471
|
.filter((p) => existsSync(p))
|
|
472
|
-
.join(
|
|
472
|
+
.join(delimiter);
|
|
473
473
|
|
|
474
474
|
// Shell out to esbuild CLI
|
|
475
475
|
const args = [
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { resolveEsbuildPlatform } from "./compiler-tools.js";
|
|
4
|
+
|
|
5
|
+
describe("resolveEsbuildPlatform", () => {
|
|
6
|
+
test("selects Windows executables", () => {
|
|
7
|
+
expect(resolveEsbuildPlatform("win32", "x64")).toEqual({
|
|
8
|
+
packageName: "win32-x64",
|
|
9
|
+
binaryPathParts: ["esbuild.exe"],
|
|
10
|
+
});
|
|
11
|
+
expect(resolveEsbuildPlatform("win32", "arm64")).toEqual({
|
|
12
|
+
packageName: "win32-arm64",
|
|
13
|
+
binaryPathParts: ["esbuild.exe"],
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("keeps POSIX binaries under bin", () => {
|
|
18
|
+
expect(resolveEsbuildPlatform("darwin", "arm64")).toEqual({
|
|
19
|
+
packageName: "darwin-arm64",
|
|
20
|
+
binaryPathParts: ["bin", "esbuild"],
|
|
21
|
+
});
|
|
22
|
+
expect(resolveEsbuildPlatform("linux", "x64")).toEqual({
|
|
23
|
+
packageName: "linux-x64",
|
|
24
|
+
binaryPathParts: ["bin", "esbuild"],
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("rejects unsupported targets", () => {
|
|
29
|
+
expect(() => resolveEsbuildPlatform("freebsd", "x64")).toThrow(
|
|
30
|
+
"Unsupported esbuild platform: freebsd",
|
|
31
|
+
);
|
|
32
|
+
expect(() => resolveEsbuildPlatform("win32", "ia32")).toThrow(
|
|
33
|
+
"Unsupported esbuild architecture: ia32",
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
writeFileSync,
|
|
17
17
|
} from "node:fs";
|
|
18
18
|
import { arch, platform } from "node:os";
|
|
19
|
-
import { join } from "node:path";
|
|
19
|
+
import { basename, join } from "node:path";
|
|
20
20
|
|
|
21
21
|
import { getLogger } from "../util/logger.js";
|
|
22
22
|
import { getWorkspaceDir } from "../util/platform.js";
|
|
@@ -44,6 +44,37 @@ interface VersionManifest {
|
|
|
44
44
|
installedAt: string;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
interface EsbuildPlatform {
|
|
48
|
+
packageName: string;
|
|
49
|
+
binaryPathParts: string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resolveEsbuildPlatform(
|
|
53
|
+
os: NodeJS.Platform,
|
|
54
|
+
cpu: string,
|
|
55
|
+
): EsbuildPlatform {
|
|
56
|
+
if (cpu !== "arm64" && cpu !== "x64") {
|
|
57
|
+
throw new Error(`Unsupported esbuild architecture: ${cpu}`);
|
|
58
|
+
}
|
|
59
|
+
if (os === "darwin" || os === "linux") {
|
|
60
|
+
return {
|
|
61
|
+
packageName: `${os}-${cpu}`,
|
|
62
|
+
binaryPathParts: ["bin", "esbuild"],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (os === "win32") {
|
|
66
|
+
return {
|
|
67
|
+
packageName: `win32-${cpu}`,
|
|
68
|
+
binaryPathParts: ["esbuild.exe"],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
throw new Error(`Unsupported esbuild platform: ${os}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function installedEsbuildName(os: NodeJS.Platform): string {
|
|
75
|
+
return os === "win32" ? "esbuild.exe" : "esbuild";
|
|
76
|
+
}
|
|
77
|
+
|
|
47
78
|
function getToolsDir(): string {
|
|
48
79
|
return join(getWorkspaceDir(), "compiler-tools");
|
|
49
80
|
}
|
|
@@ -171,8 +202,11 @@ function isReady(baseDir: string): boolean {
|
|
|
171
202
|
if (!manifest || manifest.toolsVersion !== TOOLS_VERSION) {
|
|
172
203
|
return false;
|
|
173
204
|
}
|
|
205
|
+
if (manifest.platform !== platform() || manifest.arch !== arch()) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
174
208
|
return (
|
|
175
|
-
existsSync(join(baseDir, "bin",
|
|
209
|
+
existsSync(join(baseDir, "bin", installedEsbuildName(platform()))) &&
|
|
176
210
|
existsSync(join(baseDir, "node_modules", "preact"))
|
|
177
211
|
);
|
|
178
212
|
}
|
|
@@ -193,7 +227,7 @@ export async function ensureCompilerTools(): Promise<CompilerTools> {
|
|
|
193
227
|
}
|
|
194
228
|
|
|
195
229
|
return {
|
|
196
|
-
esbuildBin: join(baseDir, "bin",
|
|
230
|
+
esbuildBin: join(baseDir, "bin", installedEsbuildName(platform())),
|
|
197
231
|
preactDir: join(baseDir, "node_modules", "preact"),
|
|
198
232
|
};
|
|
199
233
|
}
|
|
@@ -247,22 +281,17 @@ async function install(baseDir: string): Promise<void> {
|
|
|
247
281
|
mkdirSync(tmpDir, { recursive: true });
|
|
248
282
|
|
|
249
283
|
try {
|
|
250
|
-
|
|
251
|
-
const esbuildPlatform =
|
|
252
|
-
os === "darwin"
|
|
253
|
-
? cpu === "arm64"
|
|
254
|
-
? "darwin-arm64"
|
|
255
|
-
: "darwin-x64"
|
|
256
|
-
: cpu === "arm64"
|
|
257
|
-
? "linux-arm64"
|
|
258
|
-
: "linux-x64";
|
|
284
|
+
const esbuildPlatform = resolveEsbuildPlatform(os, cpu);
|
|
259
285
|
|
|
260
286
|
// Download esbuild binary + preact in parallel
|
|
261
287
|
await Promise.all([
|
|
262
288
|
downloadAndExtract(
|
|
263
|
-
`@esbuild/${esbuildPlatform}`,
|
|
289
|
+
`@esbuild/${esbuildPlatform.packageName}`,
|
|
264
290
|
ESBUILD_VERSION,
|
|
265
|
-
npmTarballUrl(
|
|
291
|
+
npmTarballUrl(
|
|
292
|
+
`@esbuild/${esbuildPlatform.packageName}`,
|
|
293
|
+
ESBUILD_VERSION,
|
|
294
|
+
),
|
|
266
295
|
join(tmpDir, "esbuild-pkg"),
|
|
267
296
|
),
|
|
268
297
|
downloadAndExtract(
|
|
@@ -274,12 +303,19 @@ async function install(baseDir: string): Promise<void> {
|
|
|
274
303
|
]);
|
|
275
304
|
|
|
276
305
|
// Move esbuild binary to bin/
|
|
277
|
-
const esbuildBinSrc = join(
|
|
306
|
+
const esbuildBinSrc = join(
|
|
307
|
+
tmpDir,
|
|
308
|
+
"esbuild-pkg",
|
|
309
|
+
...esbuildPlatform.binaryPathParts,
|
|
310
|
+
);
|
|
278
311
|
const binDir = join(tmpDir, "bin");
|
|
279
312
|
mkdirSync(binDir, { recursive: true });
|
|
280
313
|
const { renameSync } = await import("node:fs");
|
|
281
|
-
|
|
282
|
-
|
|
314
|
+
const esbuildBin = join(binDir, installedEsbuildName(os));
|
|
315
|
+
renameSync(esbuildBinSrc, esbuildBin);
|
|
316
|
+
if (os !== "win32") {
|
|
317
|
+
chmodSync(esbuildBin, 0o755);
|
|
318
|
+
}
|
|
283
319
|
rmSync(join(tmpDir, "esbuild-pkg"), { recursive: true, force: true });
|
|
284
320
|
|
|
285
321
|
// Write version manifest
|
|
@@ -299,7 +335,7 @@ async function install(baseDir: string): Promise<void> {
|
|
|
299
335
|
// Atomic swap: clear old install, move new files in
|
|
300
336
|
const { readdirSync } = await import("node:fs");
|
|
301
337
|
for (const entry of readdirSync(baseDir)) {
|
|
302
|
-
if (entry.startsWith(".") || entry === tmpDir
|
|
338
|
+
if (entry.startsWith(".") || entry === basename(tmpDir)) {
|
|
303
339
|
continue;
|
|
304
340
|
}
|
|
305
341
|
rmSync(join(baseDir, entry), { recursive: true, force: true });
|