@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
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Codex device-code authorization.
|
|
3
|
+
*
|
|
4
|
+
* OpenAI's Codex sign-in uses a bespoke device-authorization API rather than
|
|
5
|
+
* RFC 8628. The daemon asks for a short user code, the user types it at
|
|
6
|
+
* `https://auth.openai.com/codex/device`, and the poll endpoint answers with an
|
|
7
|
+
* OAuth authorization code plus the PKCE pair minted alongside it. The caller
|
|
8
|
+
* exchanges that pair at the normal token endpoint, so the resulting tokens are
|
|
9
|
+
* identical to the browser redirect flow's.
|
|
10
|
+
*
|
|
11
|
+
* The account must have "Enable device code authorization for Codex" turned on
|
|
12
|
+
* in ChatGPT security settings. When it is off the poll never succeeds: it
|
|
13
|
+
* either stays pending until the code expires or answers with a distinct error
|
|
14
|
+
* code, both of which surface as a `DeviceAuthError`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { getLogger } from "../util/logger.js";
|
|
18
|
+
import type { OAuth2Config, OAuth2FlowResult } from "./oauth2.js";
|
|
19
|
+
import { exchangeCodeForTokens } from "./oauth2.js";
|
|
20
|
+
|
|
21
|
+
const log = getLogger("openai-device-auth");
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Endpoints
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
export const OPENAI_DEVICE_AUTH_USERCODE_URL =
|
|
28
|
+
"https://auth.openai.com/api/accounts/deviceauth/usercode";
|
|
29
|
+
|
|
30
|
+
export const OPENAI_DEVICE_AUTH_TOKEN_URL =
|
|
31
|
+
"https://auth.openai.com/api/accounts/deviceauth/token";
|
|
32
|
+
|
|
33
|
+
/** Page where the user types the short code. */
|
|
34
|
+
export const OPENAI_DEVICE_VERIFICATION_URL =
|
|
35
|
+
"https://auth.openai.com/codex/device";
|
|
36
|
+
|
|
37
|
+
/** Redirect URI the device-issued authorization code is bound to. */
|
|
38
|
+
export const OPENAI_DEVICE_REDIRECT_URI =
|
|
39
|
+
"https://auth.openai.com/deviceauth/callback";
|
|
40
|
+
|
|
41
|
+
/** Fallback poll interval when the server omits or garbles `interval`. */
|
|
42
|
+
const DEFAULT_POLL_INTERVAL_SECONDS = 5;
|
|
43
|
+
|
|
44
|
+
/** Hard ceiling on a device authorization, regardless of `expires_at`. */
|
|
45
|
+
export const OPENAI_DEVICE_AUTH_MAX_LIFETIME_MS = 15 * 60 * 1000;
|
|
46
|
+
|
|
47
|
+
/** Error code the poll endpoint returns while the user has not yet approved. */
|
|
48
|
+
const AUTHORIZATION_PENDING_CODE = "deviceauth_authorization_pending";
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Types
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
export class DeviceAuthError extends Error {
|
|
55
|
+
/**
|
|
56
|
+
* Machine-readable failure code: the provider's `error.code` when it sends
|
|
57
|
+
* one, otherwise a locally assigned code (`expired_token`, `request_failed`,
|
|
58
|
+
* `aborted`).
|
|
59
|
+
*/
|
|
60
|
+
readonly code: string;
|
|
61
|
+
|
|
62
|
+
constructor(message: string, code: string) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.name = "DeviceAuthError";
|
|
65
|
+
this.code = code;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A minted device authorization, before the user has approved it. */
|
|
70
|
+
export interface DeviceAuthRequest {
|
|
71
|
+
deviceAuthId: string;
|
|
72
|
+
/** Short code the user types at `verificationUrl`. */
|
|
73
|
+
userCode: string;
|
|
74
|
+
verificationUrl: string;
|
|
75
|
+
/** ISO-8601 instant after which the code stops being accepted. */
|
|
76
|
+
expiresAt: string;
|
|
77
|
+
intervalSeconds: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The PKCE-bound authorization code the poll endpoint hands back. */
|
|
81
|
+
export interface DeviceAuthCode {
|
|
82
|
+
authorizationCode: string;
|
|
83
|
+
codeVerifier: string;
|
|
84
|
+
codeChallenge?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface DeviceAuthOptions {
|
|
88
|
+
/** Injectable `fetch`, for tests. */
|
|
89
|
+
fetchImpl?: typeof fetch;
|
|
90
|
+
/** Injectable delay between polls, for tests. */
|
|
91
|
+
sleep?: (ms: number) => Promise<void>;
|
|
92
|
+
/** Injectable clock, for tests. */
|
|
93
|
+
now?: () => number;
|
|
94
|
+
signal?: AbortSignal;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Step 1: mint a user code
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
export async function requestDeviceCode(
|
|
102
|
+
clientId: string,
|
|
103
|
+
options: DeviceAuthOptions = {},
|
|
104
|
+
): Promise<DeviceAuthRequest> {
|
|
105
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
106
|
+
|
|
107
|
+
let response: Response;
|
|
108
|
+
try {
|
|
109
|
+
response = await doFetch(OPENAI_DEVICE_AUTH_USERCODE_URL, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: {
|
|
112
|
+
"Content-Type": "application/json",
|
|
113
|
+
Accept: "application/json",
|
|
114
|
+
},
|
|
115
|
+
body: JSON.stringify({ client_id: clientId }),
|
|
116
|
+
signal: options.signal,
|
|
117
|
+
});
|
|
118
|
+
} catch (err) {
|
|
119
|
+
throw new DeviceAuthError(
|
|
120
|
+
`Could not reach the device authorization endpoint: ${errorMessage(err)}`,
|
|
121
|
+
"request_failed",
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
const { code, message } = await readErrorBody(response);
|
|
127
|
+
throw new DeviceAuthError(
|
|
128
|
+
message ??
|
|
129
|
+
`Device authorization request failed (HTTP ${response.status})`,
|
|
130
|
+
code ?? "request_failed",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const data = (await response.json()) as Record<string, unknown>;
|
|
135
|
+
const deviceAuthId = asString(data.device_auth_id);
|
|
136
|
+
const userCode = asString(data.user_code);
|
|
137
|
+
if (!deviceAuthId || !userCode) {
|
|
138
|
+
throw new DeviceAuthError(
|
|
139
|
+
"Device authorization response is missing a device id or user code.",
|
|
140
|
+
"request_failed",
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const expiresAt =
|
|
145
|
+
asString(data.expires_at) ??
|
|
146
|
+
new Date(
|
|
147
|
+
(options.now?.() ?? Date.now()) + OPENAI_DEVICE_AUTH_MAX_LIFETIME_MS,
|
|
148
|
+
).toISOString();
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
deviceAuthId,
|
|
152
|
+
userCode,
|
|
153
|
+
verificationUrl: OPENAI_DEVICE_VERIFICATION_URL,
|
|
154
|
+
expiresAt,
|
|
155
|
+
intervalSeconds: parseIntervalSeconds(data.interval),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Step 2: poll until the user approves
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Poll until the user approves the code, the code expires, or the provider
|
|
165
|
+
* reports a non-pending failure.
|
|
166
|
+
*
|
|
167
|
+
* The poll answers 403 (and occasionally 404) while approval is outstanding, so
|
|
168
|
+
* those statuses are only fatal when the body names an error code other than
|
|
169
|
+
* `deviceauth_authorization_pending`. Transport failures and 5xx/429 responses
|
|
170
|
+
* are retried until the deadline.
|
|
171
|
+
*/
|
|
172
|
+
export async function pollForAuthorizationCode(
|
|
173
|
+
request: DeviceAuthRequest,
|
|
174
|
+
options: DeviceAuthOptions = {},
|
|
175
|
+
): Promise<DeviceAuthCode> {
|
|
176
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
177
|
+
const doSleep = options.sleep ?? defaultSleep;
|
|
178
|
+
const now = options.now ?? (() => Date.now());
|
|
179
|
+
|
|
180
|
+
const intervalMs = request.intervalSeconds * 1000;
|
|
181
|
+
const deadline = resolveDeadline(request.expiresAt, now());
|
|
182
|
+
|
|
183
|
+
while (now() < deadline) {
|
|
184
|
+
assertNotAborted(options.signal);
|
|
185
|
+
|
|
186
|
+
let response: Response | undefined;
|
|
187
|
+
try {
|
|
188
|
+
response = await doFetch(OPENAI_DEVICE_AUTH_TOKEN_URL, {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: {
|
|
191
|
+
"Content-Type": "application/json",
|
|
192
|
+
Accept: "application/json",
|
|
193
|
+
},
|
|
194
|
+
body: JSON.stringify({
|
|
195
|
+
device_auth_id: request.deviceAuthId,
|
|
196
|
+
user_code: request.userCode,
|
|
197
|
+
}),
|
|
198
|
+
signal: options.signal,
|
|
199
|
+
});
|
|
200
|
+
} catch (err) {
|
|
201
|
+
assertNotAborted(options.signal);
|
|
202
|
+
log.warn({ err: errorMessage(err) }, "Device auth poll failed, retrying");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (response?.ok) {
|
|
206
|
+
const data = (await response.json()) as Record<string, unknown>;
|
|
207
|
+
const authorizationCode = asString(data.authorization_code);
|
|
208
|
+
const codeVerifier = asString(data.code_verifier);
|
|
209
|
+
if (!authorizationCode || !codeVerifier) {
|
|
210
|
+
throw new DeviceAuthError(
|
|
211
|
+
"Device authorization succeeded but returned no authorization code.",
|
|
212
|
+
"request_failed",
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
log.info("OpenAI device authorization approved");
|
|
216
|
+
const result: DeviceAuthCode = { authorizationCode, codeVerifier };
|
|
217
|
+
const codeChallenge = asString(data.code_challenge);
|
|
218
|
+
if (codeChallenge) {
|
|
219
|
+
result.codeChallenge = codeChallenge;
|
|
220
|
+
}
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (response && !isRetryableStatus(response.status)) {
|
|
225
|
+
const { code, message } = await readErrorBody(response);
|
|
226
|
+
// A pending approval answers 403/404 with the pending code; anything else
|
|
227
|
+
// is terminal, including the error raised when device-code authorization
|
|
228
|
+
// is disabled for the account.
|
|
229
|
+
if (code !== AUTHORIZATION_PENDING_CODE && (code || message)) {
|
|
230
|
+
throw new DeviceAuthError(
|
|
231
|
+
message ?? `Device authorization failed (HTTP ${response.status})`,
|
|
232
|
+
code ?? "request_failed",
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
await doSleep(intervalMs);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
assertNotAborted(options.signal);
|
|
241
|
+
throw new DeviceAuthError(
|
|
242
|
+
"The device code expired before the sign-in was approved.",
|
|
243
|
+
"expired_token",
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// Step 3: exchange the authorization code
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Run steps 2 and 3: wait for approval, then exchange the device-issued
|
|
253
|
+
* authorization code and its PKCE verifier for OAuth tokens.
|
|
254
|
+
*
|
|
255
|
+
* The signal reaches the exchange too, and is checked once more after it
|
|
256
|
+
* returns: a cancellation that lands while the tokens are in flight must not
|
|
257
|
+
* hand the caller a set of credentials to store.
|
|
258
|
+
*/
|
|
259
|
+
export async function completeDeviceAuth(
|
|
260
|
+
config: OAuth2Config,
|
|
261
|
+
request: DeviceAuthRequest,
|
|
262
|
+
options: DeviceAuthOptions = {},
|
|
263
|
+
): Promise<OAuth2FlowResult> {
|
|
264
|
+
const { authorizationCode, codeVerifier } = await pollForAuthorizationCode(
|
|
265
|
+
request,
|
|
266
|
+
options,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const result = await exchangeCodeForTokens(
|
|
270
|
+
config,
|
|
271
|
+
authorizationCode,
|
|
272
|
+
OPENAI_DEVICE_REDIRECT_URI,
|
|
273
|
+
codeVerifier,
|
|
274
|
+
undefined,
|
|
275
|
+
{ signal: options.signal },
|
|
276
|
+
);
|
|
277
|
+
assertNotAborted(options.signal);
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
// Helpers
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
/** `interval` arrives as a string of seconds; fall back when it is unusable. */
|
|
286
|
+
function parseIntervalSeconds(raw: unknown): number {
|
|
287
|
+
const parsed = Number(raw);
|
|
288
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
289
|
+
return DEFAULT_POLL_INTERVAL_SECONDS;
|
|
290
|
+
}
|
|
291
|
+
return parsed;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Stop at `expires_at`, never later than the hard lifetime ceiling. */
|
|
295
|
+
function resolveDeadline(expiresAt: string, startedAt: number): number {
|
|
296
|
+
const ceiling = startedAt + OPENAI_DEVICE_AUTH_MAX_LIFETIME_MS;
|
|
297
|
+
const parsed = Date.parse(expiresAt);
|
|
298
|
+
if (!Number.isFinite(parsed)) {
|
|
299
|
+
return ceiling;
|
|
300
|
+
}
|
|
301
|
+
return Math.min(parsed, ceiling);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function isRetryableStatus(status: number): boolean {
|
|
305
|
+
return status === 429 || status >= 500;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Pull `{ error: { code, message } }` off a failure body. Both fields are
|
|
310
|
+
* optional: an unparseable body reads as a transient failure so the caller
|
|
311
|
+
* keeps polling.
|
|
312
|
+
*/
|
|
313
|
+
async function readErrorBody(
|
|
314
|
+
response: Response,
|
|
315
|
+
): Promise<{ code?: string; message?: string }> {
|
|
316
|
+
const raw = await response.text().catch(() => "");
|
|
317
|
+
if (!raw) {
|
|
318
|
+
return {};
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
|
322
|
+
const error = parsed.error;
|
|
323
|
+
if (error && typeof error === "object") {
|
|
324
|
+
const nested = error as Record<string, unknown>;
|
|
325
|
+
const result: { code?: string; message?: string } = {};
|
|
326
|
+
const code = asString(nested.code);
|
|
327
|
+
if (code) {
|
|
328
|
+
result.code = code;
|
|
329
|
+
}
|
|
330
|
+
const message = asString(nested.message);
|
|
331
|
+
if (message) {
|
|
332
|
+
result.message = message;
|
|
333
|
+
}
|
|
334
|
+
return result;
|
|
335
|
+
}
|
|
336
|
+
const code = asString(error);
|
|
337
|
+
return code ? { code } : {};
|
|
338
|
+
} catch {
|
|
339
|
+
return {};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function asString(value: unknown): string | undefined {
|
|
344
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function errorMessage(err: unknown): string {
|
|
348
|
+
return err instanceof Error ? err.message : String(err);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function assertNotAborted(signal?: AbortSignal): void {
|
|
352
|
+
if (signal?.aborted) {
|
|
353
|
+
throw new DeviceAuthError("Device authorization was cancelled.", "aborted");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function defaultSleep(ms: number): Promise<void> {
|
|
358
|
+
return new Promise((resolve) => {
|
|
359
|
+
const timer = setTimeout(resolve, ms);
|
|
360
|
+
if (typeof timer === "object" && "unref" in timer) {
|
|
361
|
+
timer.unref();
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
}
|
|
@@ -156,6 +156,21 @@ export function setCesClient(client: CesClient | undefined): void {
|
|
|
156
156
|
"CES client updated; resetting resolved credential backend cache",
|
|
157
157
|
);
|
|
158
158
|
_cesClientListener?.(client);
|
|
159
|
+
void attachCredentialRecordBackend(client);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function attachCredentialRecordBackend(
|
|
163
|
+
client: CesClient | undefined,
|
|
164
|
+
): Promise<void> {
|
|
165
|
+
const { CesRpcRecordBackend } = await import("./ces-rpc-record-backend.js");
|
|
166
|
+
const { setCredentialRecordBackend } = await import(
|
|
167
|
+
"../tools/credentials/metadata-store.js"
|
|
168
|
+
);
|
|
169
|
+
if (!client) {
|
|
170
|
+
setCredentialRecordBackend(undefined);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
setCredentialRecordBackend(new CesRpcRecordBackend(client));
|
|
159
174
|
}
|
|
160
175
|
|
|
161
176
|
/**
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { getConfig } from "../config/loader.js";
|
|
2
2
|
import type { SkillSource } from "../config/skills.js";
|
|
3
3
|
import type { SkillInstallMeta } from "./install-meta.js";
|
|
4
|
+
import {
|
|
5
|
+
isSkillCompatibleWithPlatform,
|
|
6
|
+
type SkillPlatform,
|
|
7
|
+
} from "./platform-compatibility.js";
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* Plugin-facing read API over the skill surface: the locally installed
|
|
@@ -28,6 +32,8 @@ export interface ResolvedSkillEntry {
|
|
|
28
32
|
avoidWhen?: string[];
|
|
29
33
|
/** True when the skill is pinned into the memory selector pool every turn. */
|
|
30
34
|
alwaysCandidate?: boolean;
|
|
35
|
+
/** Host operating systems on which this skill may be used. */
|
|
36
|
+
platforms?: SkillPlatform[];
|
|
31
37
|
/** True for locally installed skills; false for remote catalog entries. */
|
|
32
38
|
installed: boolean;
|
|
33
39
|
/** Where the installed skill comes from. Unset for remote catalog entries. */
|
|
@@ -77,6 +83,7 @@ export async function listInstalledSkills(): Promise<ResolvedSkillEntry[]> {
|
|
|
77
83
|
activationHints: summary.activationHints,
|
|
78
84
|
avoidWhen: summary.avoidWhen,
|
|
79
85
|
alwaysCandidate: summary.alwaysCandidate,
|
|
86
|
+
platforms: summary.platforms,
|
|
80
87
|
installed: true,
|
|
81
88
|
source: summary.source,
|
|
82
89
|
state: stateById.get(summary.id) ?? "unavailable",
|
|
@@ -121,14 +128,16 @@ export async function listCatalogSkills(): Promise<ResolvedSkillEntry[]> {
|
|
|
121
128
|
typeof flagKey === "string" &&
|
|
122
129
|
flagKey.length > 0 &&
|
|
123
130
|
!isAssistantFeatureFlagEnabled(flagKey, config);
|
|
131
|
+
const incompatible = !isSkillCompatibleWithPlatform(entry);
|
|
124
132
|
return {
|
|
125
133
|
id: entry.id,
|
|
126
134
|
displayName: entry.metadata?.vellum?.["display-name"] ?? entry.name,
|
|
127
135
|
description: entry.description,
|
|
128
136
|
activationHints: entry.metadata?.vellum?.["activation-hints"],
|
|
129
137
|
avoidWhen: entry.metadata?.vellum?.["avoid-when"],
|
|
138
|
+
platforms: entry.platforms,
|
|
130
139
|
installed: false,
|
|
131
|
-
state: gated ? "unavailable" : "available",
|
|
140
|
+
state: gated || incompatible ? "unavailable" : "available",
|
|
132
141
|
};
|
|
133
142
|
});
|
|
134
143
|
}
|
|
@@ -19,6 +19,12 @@ import { loadSkillCatalog } from "../config/skills.js";
|
|
|
19
19
|
import { getLogger } from "../util/logger.js";
|
|
20
20
|
import { getWorkspaceSkillsDir } from "../util/platform.js";
|
|
21
21
|
import { computeSkillHash, writeInstallMeta } from "./install-meta.js";
|
|
22
|
+
import {
|
|
23
|
+
isSkillCompatibleWithPlatform,
|
|
24
|
+
normalizeSkillPlatforms,
|
|
25
|
+
type SkillPlatform,
|
|
26
|
+
skillPlatformUnavailableMessage,
|
|
27
|
+
} from "./platform-compatibility.js";
|
|
22
28
|
|
|
23
29
|
const log = getLogger("catalog-install");
|
|
24
30
|
|
|
@@ -33,6 +39,7 @@ export interface CatalogSkill {
|
|
|
33
39
|
includes?: string[];
|
|
34
40
|
version?: string;
|
|
35
41
|
updatedAt?: string;
|
|
42
|
+
platforms?: SkillPlatform[];
|
|
36
43
|
metadata?: {
|
|
37
44
|
icon?: string;
|
|
38
45
|
emoji?: string;
|
|
@@ -42,6 +49,7 @@ export interface CatalogSkill {
|
|
|
42
49
|
"avoid-when"?: string[];
|
|
43
50
|
"feature-flag"?: string;
|
|
44
51
|
category?: string;
|
|
52
|
+
platforms?: SkillPlatform[];
|
|
45
53
|
};
|
|
46
54
|
};
|
|
47
55
|
}
|
|
@@ -111,6 +119,7 @@ interface RawCatalogEntry {
|
|
|
111
119
|
updatedAt?: unknown;
|
|
112
120
|
display_name?: unknown;
|
|
113
121
|
category?: unknown;
|
|
122
|
+
platforms?: unknown;
|
|
114
123
|
updated_at?: unknown;
|
|
115
124
|
metadata?: CatalogSkill["metadata"];
|
|
116
125
|
}
|
|
@@ -144,6 +153,9 @@ function normalizeCatalogEntry(raw: unknown): CatalogSkill | null {
|
|
|
144
153
|
const displayName = nested?.["display-name"] ?? asStr(entry.display_name);
|
|
145
154
|
const icon = asStr(entry.icon) ?? asStr(entry.metadata?.icon);
|
|
146
155
|
const updatedAt = asStr(entry.updatedAt) ?? asStr(entry.updated_at);
|
|
156
|
+
const platforms = normalizeSkillPlatforms(
|
|
157
|
+
nested?.platforms ?? entry.platforms,
|
|
158
|
+
);
|
|
147
159
|
|
|
148
160
|
return {
|
|
149
161
|
id,
|
|
@@ -154,6 +166,7 @@ function normalizeCatalogEntry(raw: unknown): CatalogSkill | null {
|
|
|
154
166
|
...(entry.includes ? { includes: entry.includes } : {}),
|
|
155
167
|
...(entry.version ? { version: entry.version } : {}),
|
|
156
168
|
...(updatedAt ? { updatedAt } : {}),
|
|
169
|
+
...(platforms ? { platforms } : {}),
|
|
157
170
|
metadata: {
|
|
158
171
|
...entry.metadata,
|
|
159
172
|
...(icon ? { icon } : {}),
|
|
@@ -161,6 +174,7 @@ function normalizeCatalogEntry(raw: unknown): CatalogSkill | null {
|
|
|
161
174
|
...nested,
|
|
162
175
|
...(displayName ? { "display-name": displayName } : {}),
|
|
163
176
|
...(category ? { category } : {}),
|
|
177
|
+
...(platforms ? { platforms } : {}),
|
|
164
178
|
},
|
|
165
179
|
},
|
|
166
180
|
};
|
|
@@ -610,7 +624,8 @@ export async function resolveCatalog(
|
|
|
610
624
|
/**
|
|
611
625
|
* Attempt to find and install a skill from the first-party catalog.
|
|
612
626
|
* Returns true if the skill was installed, false if not found in catalog.
|
|
613
|
-
* Throws on
|
|
627
|
+
* Throws when the skill is unsupported on this host or on install failures
|
|
628
|
+
* (network, filesystem, etc).
|
|
614
629
|
*
|
|
615
630
|
* When `catalog` is provided it is used directly, avoiding a redundant
|
|
616
631
|
* network fetch — pass a pre-resolved catalog when calling in a loop.
|
|
@@ -639,6 +654,9 @@ export async function autoInstallFromCatalog(
|
|
|
639
654
|
if (!entry) {
|
|
640
655
|
return false;
|
|
641
656
|
}
|
|
657
|
+
if (!isSkillCompatibleWithPlatform(entry)) {
|
|
658
|
+
throw new Error(skillPlatformUnavailableMessage(skillId, entry));
|
|
659
|
+
}
|
|
642
660
|
|
|
643
661
|
// If the skill already exists on disk, reuse it instead of attempting a
|
|
644
662
|
// fresh install that would fail.
|
|
@@ -25,6 +25,10 @@ import { spawn } from "node:child_process";
|
|
|
25
25
|
|
|
26
26
|
import { buildSanitizedEnv } from "../tools/terminal/safe-env.js";
|
|
27
27
|
import { stripAnsiSequences } from "../util/ansi.js";
|
|
28
|
+
import {
|
|
29
|
+
buildShellInvocation,
|
|
30
|
+
terminateProcessTree,
|
|
31
|
+
} from "../util/host-process.js";
|
|
28
32
|
import { getLogger } from "../util/logger.js";
|
|
29
33
|
|
|
30
34
|
const log = getLogger("inline-command-runner");
|
|
@@ -99,7 +103,7 @@ export async function runInlineCommand(
|
|
|
99
103
|
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
100
104
|
const maxChars = options?.maxOutputChars ?? MAX_OUTPUT_CHARS;
|
|
101
105
|
|
|
102
|
-
const wrapped =
|
|
106
|
+
const wrapped = buildShellInvocation(command);
|
|
103
107
|
|
|
104
108
|
// Build a minimal, sanitized environment. Explicitly exclude gateway URL,
|
|
105
109
|
// workspace dir, and data dir since inline commands have no business calling
|
|
@@ -135,7 +139,7 @@ export async function runInlineCommand(
|
|
|
135
139
|
|
|
136
140
|
const timer = setTimeout(() => {
|
|
137
141
|
timedOut = true;
|
|
138
|
-
child
|
|
142
|
+
terminateProcessTree(child);
|
|
139
143
|
}, timeoutMs);
|
|
140
144
|
|
|
141
145
|
child.stdout!.on("data", (data: Buffer) => {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
filterSkillsByPlatform,
|
|
5
|
+
isSkillCompatibleWithPlatform,
|
|
6
|
+
normalizeSkillPlatforms,
|
|
7
|
+
skillPlatformForNodePlatform,
|
|
8
|
+
} from "./platform-compatibility.js";
|
|
9
|
+
|
|
10
|
+
describe("skill platform compatibility", () => {
|
|
11
|
+
test("maps Node platforms to skill platform names", () => {
|
|
12
|
+
expect(skillPlatformForNodePlatform("darwin")).toBe("macos");
|
|
13
|
+
expect(skillPlatformForNodePlatform("win32")).toBe("windows");
|
|
14
|
+
expect(skillPlatformForNodePlatform("linux")).toBe("linux");
|
|
15
|
+
expect(skillPlatformForNodePlatform("freebsd")).toBeNull();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("treats missing platform metadata as portable", () => {
|
|
19
|
+
expect(isSkillCompatibleWithPlatform({}, "win32")).toBe(true);
|
|
20
|
+
expect(isSkillCompatibleWithPlatform({}, "linux")).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("matches declared host platforms", () => {
|
|
24
|
+
const skill = { platforms: ["macos", "linux"] as const };
|
|
25
|
+
expect(isSkillCompatibleWithPlatform(skill, "darwin")).toBe(true);
|
|
26
|
+
expect(isSkillCompatibleWithPlatform(skill, "linux")).toBe(true);
|
|
27
|
+
expect(isSkillCompatibleWithPlatform(skill, "win32")).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("filters incompatible skills from offer surfaces", () => {
|
|
31
|
+
const skills = [
|
|
32
|
+
{ id: "portable" },
|
|
33
|
+
{ id: "mac-only", platforms: ["macos"] as const },
|
|
34
|
+
{ id: "windows-only", platforms: ["windows"] as const },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
expect(
|
|
38
|
+
filterSkillsByPlatform(skills, "win32").map((skill) => skill.id),
|
|
39
|
+
).toEqual(["portable", "windows-only"]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("normalizes valid unique metadata values", () => {
|
|
43
|
+
expect(normalizeSkillPlatforms(["windows", "windows", "linux"])).toEqual([
|
|
44
|
+
"windows",
|
|
45
|
+
"linux",
|
|
46
|
+
]);
|
|
47
|
+
expect(normalizeSkillPlatforms(["android", 42])).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export const SKILL_PLATFORM_VALUES = ["macos", "windows", "linux"] as const;
|
|
2
|
+
|
|
3
|
+
export type SkillPlatform = (typeof SKILL_PLATFORM_VALUES)[number];
|
|
4
|
+
|
|
5
|
+
export interface PlatformScopedSkill {
|
|
6
|
+
platforms?: readonly SkillPlatform[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function normalizeSkillPlatforms(
|
|
10
|
+
value: unknown,
|
|
11
|
+
): SkillPlatform[] | undefined {
|
|
12
|
+
if (!Array.isArray(value)) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const platforms = [
|
|
16
|
+
...new Set(
|
|
17
|
+
value.filter((platform): platform is SkillPlatform =>
|
|
18
|
+
(SKILL_PLATFORM_VALUES as readonly unknown[]).includes(platform),
|
|
19
|
+
),
|
|
20
|
+
),
|
|
21
|
+
];
|
|
22
|
+
return platforms.length > 0 ? platforms : undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function skillPlatformForNodePlatform(
|
|
26
|
+
platform: NodeJS.Platform,
|
|
27
|
+
): SkillPlatform | null {
|
|
28
|
+
if (platform === "darwin") {
|
|
29
|
+
return "macos";
|
|
30
|
+
}
|
|
31
|
+
if (platform === "win32") {
|
|
32
|
+
return "windows";
|
|
33
|
+
}
|
|
34
|
+
if (platform === "linux") {
|
|
35
|
+
return "linux";
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isSkillCompatibleWithPlatform(
|
|
41
|
+
skill: PlatformScopedSkill,
|
|
42
|
+
platform: NodeJS.Platform = process.platform,
|
|
43
|
+
): boolean {
|
|
44
|
+
if (!skill.platforms || skill.platforms.length === 0) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
const skillPlatform = skillPlatformForNodePlatform(platform);
|
|
48
|
+
return skillPlatform !== null && skill.platforms.includes(skillPlatform);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function filterSkillsByPlatform<T extends PlatformScopedSkill>(
|
|
52
|
+
skills: readonly T[],
|
|
53
|
+
platform: NodeJS.Platform = process.platform,
|
|
54
|
+
): T[] {
|
|
55
|
+
return skills.filter((skill) =>
|
|
56
|
+
isSkillCompatibleWithPlatform(skill, platform),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function skillPlatformUnavailableMessage(
|
|
61
|
+
skillId: string,
|
|
62
|
+
skill: PlatformScopedSkill,
|
|
63
|
+
): string {
|
|
64
|
+
return `Skill "${skillId}" is unavailable on this operating system. Supported platforms: ${(skill.platforms ?? []).join(", ")}.`;
|
|
65
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
|
|
3
|
+
import { CLIENT_OS_VALUES, type ClientOs } from "../channels/types.js";
|
|
3
4
|
import type { SkillToolEntry, SkillToolManifest } from "../config/skills.js";
|
|
4
5
|
|
|
5
6
|
const VALID_RISK_LEVELS = new Set(["low", "medium", "high"]);
|
|
6
7
|
const VALID_EXECUTION_TARGETS = new Set(["host", "sandbox"]);
|
|
8
|
+
const VALID_CLIENT_OS_VALUES = new Set<string>(CLIENT_OS_VALUES);
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Parse and validate a raw TOOLS.json payload into a typed SkillToolManifest.
|
|
@@ -137,6 +139,23 @@ function parseToolEntry(raw: unknown, prefix: string): SkillToolEntry {
|
|
|
137
139
|
const execution_target =
|
|
138
140
|
entry.execution_target as SkillToolEntry["execution_target"];
|
|
139
141
|
|
|
142
|
+
let supported_client_os: ClientOs[] | undefined;
|
|
143
|
+
if ("supported_client_os" in entry) {
|
|
144
|
+
if (
|
|
145
|
+
!Array.isArray(entry.supported_client_os) ||
|
|
146
|
+
entry.supported_client_os.length === 0 ||
|
|
147
|
+
entry.supported_client_os.some(
|
|
148
|
+
(value) =>
|
|
149
|
+
typeof value !== "string" || !VALID_CLIENT_OS_VALUES.has(value),
|
|
150
|
+
)
|
|
151
|
+
) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`${prefix}: "supported_client_os" must be a non-empty array of known client OS values`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
supported_client_os = [...new Set(entry.supported_client_os as ClientOs[])];
|
|
157
|
+
}
|
|
158
|
+
|
|
140
159
|
return {
|
|
141
160
|
name,
|
|
142
161
|
description,
|
|
@@ -145,6 +164,7 @@ function parseToolEntry(raw: unknown, prefix: string): SkillToolEntry {
|
|
|
145
164
|
input_schema,
|
|
146
165
|
executor,
|
|
147
166
|
execution_target,
|
|
167
|
+
supported_client_os,
|
|
148
168
|
};
|
|
149
169
|
}
|
|
150
170
|
|