@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
|
@@ -1,88 +1,259 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Route definitions for ChatGPT subscription
|
|
2
|
+
* Route definitions for ChatGPT subscription ("Sign in with ChatGPT") auth.
|
|
3
|
+
*
|
|
4
|
+
* Device code is the primary path: the daemon mints a short user code, the user
|
|
5
|
+
* types it at OpenAI's device page, and the daemon polls in the background and
|
|
6
|
+
* stores the tokens itself. The copy-paste PKCE path stays as the fallback for
|
|
7
|
+
* accounts that have not enabled device-code authorization for Codex.
|
|
8
|
+
*
|
|
9
|
+
* POST /v1/inference/chatgpt-subscription/device-auth: mint a user code and
|
|
10
|
+
* start the background poll. Returns `{ state, user_code, verification_url,
|
|
11
|
+
* expires_at, interval_seconds }`.
|
|
12
|
+
* GET /v1/inference/chatgpt-subscription/device-auth/status/:state: poll the
|
|
13
|
+
* device flow's status (`pending` | `connected` | `error`).
|
|
14
|
+
* DELETE /v1/inference/chatgpt-subscription/device-auth/:state: stop the
|
|
15
|
+
* background poll for an abandoned flow.
|
|
3
16
|
*
|
|
4
17
|
* POST /v1/inference/chatgpt-subscription/auth — generate a PKCE authorize
|
|
5
18
|
* URL for the user to visit. Returns `{ authorize_url, state }`.
|
|
6
|
-
*
|
|
7
19
|
* POST /v1/inference/chatgpt-subscription/auth/exchange — accept the
|
|
8
|
-
* authorization code + state from the redirect
|
|
9
|
-
* store in CES, and upsert the provider connection.
|
|
20
|
+
* authorization code + state from the redirect and exchange it for tokens.
|
|
10
21
|
*/
|
|
11
22
|
|
|
12
23
|
import { z } from "zod";
|
|
13
24
|
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
createConnection,
|
|
18
|
-
getConnection,
|
|
19
|
-
updateConnection,
|
|
20
|
-
} from "../../providers/inference/connections.js";
|
|
21
|
-
import type { OAuth2Config } from "../../security/oauth2.js";
|
|
25
|
+
import { storeChatgptSubscriptionTokens } from "../../providers/inference/chatgpt-subscription-auth.js";
|
|
26
|
+
import { OPENAI_OAUTH_CONFIG } from "../../providers/inference/chatgpt-subscription-credentials.js";
|
|
22
27
|
import {
|
|
23
28
|
exchangeCodeForTokens,
|
|
24
29
|
generateCodeChallenge,
|
|
25
30
|
generateCodeVerifier,
|
|
26
31
|
generateState,
|
|
27
32
|
} from "../../security/oauth2.js";
|
|
28
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
completeDeviceAuth,
|
|
35
|
+
DeviceAuthError,
|
|
36
|
+
OPENAI_DEVICE_AUTH_MAX_LIFETIME_MS,
|
|
37
|
+
requestDeviceCode,
|
|
38
|
+
} from "../../security/openai-device-auth.js";
|
|
29
39
|
import { getLogger } from "../../util/logger.js";
|
|
30
40
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
41
|
+
import { BadRequestError, NotFoundError } from "./errors.js";
|
|
42
|
+
import { createPendingFlowRegistry } from "./oauth-pending-flows.js";
|
|
31
43
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
32
44
|
|
|
33
|
-
const log = getLogger("chatgpt-subscription-auth");
|
|
45
|
+
const log = getLogger("chatgpt-subscription-auth-routes");
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
// OAuth config
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
47
|
+
const REDIRECT_URI = "http://localhost:1455/auth/callback";
|
|
38
48
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
scopeSeparator: " ",
|
|
45
|
-
authorizeParams: { id_token_add_organizations: "true" },
|
|
46
|
-
};
|
|
49
|
+
/**
|
|
50
|
+
* Copy-paste PKCE flows, keyed by the `state` in the authorize URL. The
|
|
51
|
+
* verifier is held until the user pastes the redirect's code back.
|
|
52
|
+
*/
|
|
53
|
+
const pendingPkceFlows = createPendingFlowRegistry<{ codeVerifier: string }>();
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Device-code flows, keyed by OpenAI's `device_auth_id`. Each entry carries the
|
|
57
|
+
* controller that stops its background poll. Entries outlive the device code
|
|
58
|
+
* itself so a client polling right after expiry still reads the terminal status
|
|
59
|
+
* instead of a 404.
|
|
60
|
+
*/
|
|
61
|
+
const pendingDeviceFlows = createPendingFlowRegistry<{
|
|
62
|
+
abort: AbortController;
|
|
63
|
+
}>({
|
|
64
|
+
ttlMs: OPENAI_DEVICE_AUTH_MAX_LIFETIME_MS + 5 * 60 * 1000,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The one device flow whose background poll is still running. A sign-in is a
|
|
69
|
+
* single-user act at a single moment, so starting one ends whichever came
|
|
70
|
+
* before it rather than leaving two polls hitting OpenAI every few seconds.
|
|
71
|
+
*/
|
|
72
|
+
let inFlightDeviceAuthId: string | null = null;
|
|
73
|
+
|
|
74
|
+
/** Stop a flow's poll and record why, when it has not already settled. */
|
|
75
|
+
function cancelDeviceFlow(state: string): boolean {
|
|
76
|
+
const flow = pendingDeviceFlows.get(state);
|
|
77
|
+
if (!flow || flow.status !== "pending") {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
flow.abort.abort();
|
|
81
|
+
pendingDeviceFlows.mark(state, "error", {
|
|
82
|
+
error: "The sign-in was cancelled.",
|
|
83
|
+
errorCode: "aborted",
|
|
84
|
+
});
|
|
85
|
+
if (inFlightDeviceAuthId === state) {
|
|
86
|
+
inFlightDeviceAuthId = null;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Release the in-flight slot once this flow's poll is done with it. */
|
|
92
|
+
function releaseDeviceFlow(state: string): void {
|
|
93
|
+
if (inFlightDeviceAuthId === state) {
|
|
94
|
+
inFlightDeviceAuthId = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Whether `state` is still the unsettled flow the daemon is running.
|
|
100
|
+
*
|
|
101
|
+
* A cancel or a newer sign-in can land after the poll has returned an
|
|
102
|
+
* authorization code but before the exchange and the credential writes finish.
|
|
103
|
+
* Checking this on either side of the store keeps an abandoned flow from
|
|
104
|
+
* writing tokens the user asked it to drop, or from overwriting the
|
|
105
|
+
* credentials of the sign-in that replaced it.
|
|
106
|
+
*/
|
|
107
|
+
function isDeviceFlowStillLive(state: string): boolean {
|
|
108
|
+
return (
|
|
109
|
+
inFlightDeviceAuthId === state &&
|
|
110
|
+
pendingDeviceFlows.get(state)?.status === "pending"
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Failures the user caused or waited out: expected ends, not defects. */
|
|
115
|
+
function isExpectedDeviceAuthEnd(code: string): boolean {
|
|
116
|
+
return code === "aborted" || code === "expired_token";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interface DeviceAuthStartResponse {
|
|
120
|
+
state: string;
|
|
121
|
+
user_code: string;
|
|
122
|
+
verification_url: string;
|
|
123
|
+
expires_at: string;
|
|
124
|
+
interval_seconds: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface FlowStatusResponse {
|
|
128
|
+
status: "pending" | "connected" | "error";
|
|
129
|
+
error?: string;
|
|
130
|
+
error_code?: string;
|
|
131
|
+
}
|
|
50
132
|
|
|
51
133
|
// ---------------------------------------------------------------------------
|
|
52
|
-
//
|
|
134
|
+
// Device-code handlers
|
|
53
135
|
// ---------------------------------------------------------------------------
|
|
54
136
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Mint a device code and return it immediately. The approval poll and the
|
|
139
|
+
* token exchange run in the background, one per `device_auth_id`, and flip the
|
|
140
|
+
* tracked flow's status when they settle.
|
|
141
|
+
*/
|
|
142
|
+
async function handleStartDeviceAuth(
|
|
143
|
+
_args: RouteHandlerArgs,
|
|
144
|
+
): Promise<DeviceAuthStartResponse> {
|
|
145
|
+
pendingDeviceFlows.cleanupExpired();
|
|
146
|
+
if (inFlightDeviceAuthId) {
|
|
147
|
+
cancelDeviceFlow(inFlightDeviceAuthId);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const request = await requestDeviceCode(OPENAI_OAUTH_CONFIG.clientId);
|
|
151
|
+
const abort = new AbortController();
|
|
152
|
+
pendingDeviceFlows.start(request.deviceAuthId, { abort });
|
|
153
|
+
inFlightDeviceAuthId = request.deviceAuthId;
|
|
154
|
+
|
|
155
|
+
void completeDeviceAuth(OPENAI_OAUTH_CONFIG, request, {
|
|
156
|
+
signal: abort.signal,
|
|
157
|
+
})
|
|
158
|
+
.then(async (result) => {
|
|
159
|
+
if (!isDeviceFlowStillLive(request.deviceAuthId)) {
|
|
160
|
+
log.warn(
|
|
161
|
+
"ChatGPT subscription device auth flow settled after it was dropped; discarding its tokens",
|
|
162
|
+
);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
await storeChatgptSubscriptionTokens(result.tokens);
|
|
166
|
+
if (!isDeviceFlowStillLive(request.deviceAuthId)) {
|
|
167
|
+
log.warn(
|
|
168
|
+
"ChatGPT subscription device auth flow was dropped while its tokens were stored",
|
|
169
|
+
);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
pendingDeviceFlows.mark(request.deviceAuthId, "connected");
|
|
173
|
+
log.info("ChatGPT subscription device auth flow connected");
|
|
174
|
+
})
|
|
175
|
+
.catch((err: unknown) => {
|
|
176
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
177
|
+
const errorCode =
|
|
178
|
+
err instanceof DeviceAuthError ? err.code : "device_auth_failed";
|
|
179
|
+
pendingDeviceFlows.mark(request.deviceAuthId, "error", {
|
|
180
|
+
error: message,
|
|
181
|
+
errorCode,
|
|
182
|
+
});
|
|
183
|
+
const detail = { err: message, code: errorCode };
|
|
184
|
+
if (isExpectedDeviceAuthEnd(errorCode)) {
|
|
185
|
+
log.warn(detail, "ChatGPT subscription device auth flow ended");
|
|
186
|
+
} else {
|
|
187
|
+
log.error(detail, "ChatGPT subscription device auth flow failed");
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
.finally(() => releaseDeviceFlow(request.deviceAuthId));
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
state: request.deviceAuthId,
|
|
194
|
+
user_code: request.userCode,
|
|
195
|
+
verification_url: request.verificationUrl,
|
|
196
|
+
expires_at: request.expiresAt,
|
|
197
|
+
interval_seconds: request.intervalSeconds,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function handleDeviceAuthStatus({
|
|
202
|
+
pathParams,
|
|
203
|
+
}: RouteHandlerArgs): FlowStatusResponse {
|
|
204
|
+
const { state } = pathParams as { state: string };
|
|
205
|
+
const report = pendingDeviceFlows.readStatus(state);
|
|
206
|
+
|
|
207
|
+
if (!report) {
|
|
208
|
+
throw new NotFoundError(
|
|
209
|
+
"No active ChatGPT device sign-in for the given state. Restart the sign-in flow.",
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const response: FlowStatusResponse = { status: report.status };
|
|
214
|
+
if (report.error !== undefined) {
|
|
215
|
+
response.error = report.error;
|
|
216
|
+
}
|
|
217
|
+
if (report.errorCode !== undefined) {
|
|
218
|
+
response.error_code = report.errorCode;
|
|
219
|
+
}
|
|
220
|
+
return response;
|
|
58
221
|
}
|
|
59
222
|
|
|
60
|
-
|
|
223
|
+
/**
|
|
224
|
+
* Stop an abandoned flow's background poll. Without this an abandoned sign-in
|
|
225
|
+
* keeps polling OpenAI every few seconds until the code expires.
|
|
226
|
+
*/
|
|
227
|
+
function handleCancelDeviceAuth({ pathParams }: RouteHandlerArgs): {
|
|
228
|
+
cancelled: boolean;
|
|
229
|
+
} {
|
|
230
|
+
const { state } = pathParams as { state: string };
|
|
61
231
|
|
|
62
|
-
|
|
232
|
+
if (!pendingDeviceFlows.get(state)) {
|
|
233
|
+
throw new NotFoundError(
|
|
234
|
+
"No active ChatGPT device sign-in for the given state.",
|
|
235
|
+
);
|
|
236
|
+
}
|
|
63
237
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
for (const [key, entry] of pendingAuths) {
|
|
68
|
-
if (entry.createdAt < cutoff) {
|
|
69
|
-
pendingAuths.delete(key);
|
|
70
|
-
}
|
|
238
|
+
const cancelled = cancelDeviceFlow(state);
|
|
239
|
+
if (cancelled) {
|
|
240
|
+
log.warn("ChatGPT subscription device auth flow cancelled by the client");
|
|
71
241
|
}
|
|
242
|
+
return { cancelled };
|
|
72
243
|
}
|
|
73
244
|
|
|
74
245
|
// ---------------------------------------------------------------------------
|
|
75
|
-
//
|
|
246
|
+
// Copy-paste PKCE handlers
|
|
76
247
|
// ---------------------------------------------------------------------------
|
|
77
248
|
|
|
78
249
|
async function handleStartAuth(_args: RouteHandlerArgs) {
|
|
79
|
-
|
|
250
|
+
pendingPkceFlows.cleanupExpired();
|
|
80
251
|
|
|
81
252
|
const codeVerifier = generateCodeVerifier();
|
|
82
253
|
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
83
254
|
const state = generateState();
|
|
84
255
|
|
|
85
|
-
|
|
256
|
+
pendingPkceFlows.start(state, { codeVerifier });
|
|
86
257
|
|
|
87
258
|
const params = new URLSearchParams({
|
|
88
259
|
response_type: "code",
|
|
@@ -103,18 +274,19 @@ async function handleStartAuth(_args: RouteHandlerArgs) {
|
|
|
103
274
|
async function handleExchange(args: RouteHandlerArgs) {
|
|
104
275
|
const { code, state } = args.body as { code: string; state: string };
|
|
105
276
|
|
|
106
|
-
const pending =
|
|
277
|
+
const pending = pendingPkceFlows.get(state);
|
|
107
278
|
if (!pending) {
|
|
108
|
-
throw new
|
|
279
|
+
throw new BadRequestError(
|
|
109
280
|
"Invalid or expired state parameter. Please restart the auth flow.",
|
|
110
281
|
);
|
|
111
282
|
}
|
|
112
283
|
|
|
113
|
-
|
|
284
|
+
pendingPkceFlows.delete(state);
|
|
114
285
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
286
|
+
if (pendingPkceFlows.isExpired(pending)) {
|
|
287
|
+
throw new BadRequestError(
|
|
288
|
+
"Auth flow expired. Please restart the auth flow.",
|
|
289
|
+
);
|
|
118
290
|
}
|
|
119
291
|
|
|
120
292
|
const { tokens } = await exchangeCodeForTokens(
|
|
@@ -124,70 +296,7 @@ async function handleExchange(args: RouteHandlerArgs) {
|
|
|
124
296
|
pending.codeVerifier,
|
|
125
297
|
);
|
|
126
298
|
|
|
127
|
-
|
|
128
|
-
const accessStored = await setSecureKeyAsync(
|
|
129
|
-
"credential/chatgpt/access_token",
|
|
130
|
-
tokens.accessToken,
|
|
131
|
-
);
|
|
132
|
-
if (!accessStored) {
|
|
133
|
-
log.error("Failed to store ChatGPT access token in CES");
|
|
134
|
-
throw new Error("Failed to store access token");
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (tokens.refreshToken) {
|
|
138
|
-
const refreshStored = await setSecureKeyAsync(
|
|
139
|
-
"credential/chatgpt/refresh_token",
|
|
140
|
-
tokens.refreshToken,
|
|
141
|
-
);
|
|
142
|
-
if (!refreshStored) {
|
|
143
|
-
log.error("Failed to store ChatGPT refresh token in CES");
|
|
144
|
-
throw new Error("Failed to store refresh token");
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (tokens.expiresIn) {
|
|
149
|
-
const expiresAt = Math.floor(Date.now() / 1000 + tokens.expiresIn);
|
|
150
|
-
await setSecureKeyAsync("credential/chatgpt/expires_at", String(expiresAt));
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Upsert provider connection
|
|
154
|
-
const db = getDb();
|
|
155
|
-
const authInput = {
|
|
156
|
-
type: "oauth_subscription" as const,
|
|
157
|
-
credential: "credential/chatgpt/access_token",
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const existing = getConnection(db, CONNECTION_NAME);
|
|
161
|
-
if (existing) {
|
|
162
|
-
// Stamp the provider with the auth: this row IS the subscription route
|
|
163
|
-
// (auth modality = provider identity), and dispatch derives the openai
|
|
164
|
-
// upstream from the identity per-request. Stamping also heals a
|
|
165
|
-
// claiming row whose provider would otherwise misroute the fresh token.
|
|
166
|
-
const updateResult = updateConnection(db, CONNECTION_NAME, {
|
|
167
|
-
auth: authInput,
|
|
168
|
-
provider: "chatgpt",
|
|
169
|
-
});
|
|
170
|
-
if (!updateResult.ok) {
|
|
171
|
-
log.error(
|
|
172
|
-
{ error: updateResult.error },
|
|
173
|
-
"Failed to update chatgpt-subscription connection",
|
|
174
|
-
);
|
|
175
|
-
throw new Error("Failed to update connection");
|
|
176
|
-
}
|
|
177
|
-
} else {
|
|
178
|
-
const createResult = createConnection(db, {
|
|
179
|
-
name: CONNECTION_NAME,
|
|
180
|
-
provider: "chatgpt",
|
|
181
|
-
auth: authInput,
|
|
182
|
-
});
|
|
183
|
-
if (!createResult.ok) {
|
|
184
|
-
log.error(
|
|
185
|
-
{ error: createResult.error },
|
|
186
|
-
"Failed to create chatgpt-subscription connection",
|
|
187
|
-
);
|
|
188
|
-
throw new Error("Failed to create connection");
|
|
189
|
-
}
|
|
190
|
-
}
|
|
299
|
+
await storeChatgptSubscriptionTokens(tokens);
|
|
191
300
|
|
|
192
301
|
log.info("ChatGPT subscription auth flow completed successfully");
|
|
193
302
|
return { ok: true };
|
|
@@ -198,6 +307,80 @@ async function handleExchange(args: RouteHandlerArgs) {
|
|
|
198
307
|
// ---------------------------------------------------------------------------
|
|
199
308
|
|
|
200
309
|
export const ROUTES: RouteDefinition[] = [
|
|
310
|
+
{
|
|
311
|
+
operationId: "inference_chatgpt_subscription_device_auth",
|
|
312
|
+
endpoint: "inference/chatgpt-subscription/device-auth",
|
|
313
|
+
method: "POST",
|
|
314
|
+
policy: {
|
|
315
|
+
requiredScopes: ["settings.write"],
|
|
316
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
317
|
+
},
|
|
318
|
+
summary: "Start ChatGPT subscription device-code sign-in",
|
|
319
|
+
description:
|
|
320
|
+
"Mint a short user code the user types at OpenAI's device page, and " +
|
|
321
|
+
"start a background poll that stores the tokens and upserts the " +
|
|
322
|
+
"provider connection once the sign-in is approved. Returns immediately; " +
|
|
323
|
+
"poll `.../device-auth/status/{state}` for the outcome. The account must " +
|
|
324
|
+
"have device-code authorization for Codex enabled in its ChatGPT " +
|
|
325
|
+
"security settings.",
|
|
326
|
+
tags: ["inference"],
|
|
327
|
+
responseBody: z.object({
|
|
328
|
+
state: z.string(),
|
|
329
|
+
user_code: z.string(),
|
|
330
|
+
verification_url: z.string(),
|
|
331
|
+
expires_at: z.string(),
|
|
332
|
+
interval_seconds: z.number(),
|
|
333
|
+
}),
|
|
334
|
+
handler: handleStartDeviceAuth,
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
operationId: "inference_chatgpt_subscription_device_auth_status",
|
|
338
|
+
endpoint: "inference/chatgpt-subscription/device-auth/status/:state",
|
|
339
|
+
method: "GET",
|
|
340
|
+
policy: {
|
|
341
|
+
requiredScopes: ["settings.read"],
|
|
342
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
343
|
+
},
|
|
344
|
+
summary: "Poll ChatGPT subscription device-code sign-in status",
|
|
345
|
+
description:
|
|
346
|
+
"Returns the current status of an in-flight device-code sign-in " +
|
|
347
|
+
"(pending/connected/error) so the client knows when the tokens have " +
|
|
348
|
+
"landed. A failure carries OpenAI's error code in `error_code`.",
|
|
349
|
+
tags: ["inference"],
|
|
350
|
+
pathParams: [{ name: "state" }],
|
|
351
|
+
additionalResponses: {
|
|
352
|
+
"404": { description: "No active device sign-in for the given state" },
|
|
353
|
+
},
|
|
354
|
+
responseBody: z.object({
|
|
355
|
+
status: z.enum(["pending", "connected", "error"]),
|
|
356
|
+
error: z.string().optional(),
|
|
357
|
+
error_code: z.string().optional(),
|
|
358
|
+
}),
|
|
359
|
+
handler: handleDeviceAuthStatus,
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
operationId: "inference_chatgpt_subscription_device_auth_cancel",
|
|
363
|
+
endpoint: "inference/chatgpt-subscription/device-auth/:state",
|
|
364
|
+
method: "DELETE",
|
|
365
|
+
policy: {
|
|
366
|
+
requiredScopes: ["settings.write"],
|
|
367
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
368
|
+
},
|
|
369
|
+
summary: "Cancel a ChatGPT subscription device-code sign-in",
|
|
370
|
+
description:
|
|
371
|
+
"Stop the background poll for a device-code sign-in the user walked " +
|
|
372
|
+
"away from, and mark the flow `error` with code `aborted`. A flow that " +
|
|
373
|
+
"already settled is left as it is. 404s an unknown state.",
|
|
374
|
+
tags: ["inference"],
|
|
375
|
+
pathParams: [{ name: "state" }],
|
|
376
|
+
additionalResponses: {
|
|
377
|
+
"404": { description: "No active device sign-in for the given state" },
|
|
378
|
+
},
|
|
379
|
+
responseBody: z.object({
|
|
380
|
+
cancelled: z.boolean(),
|
|
381
|
+
}),
|
|
382
|
+
handler: handleCancelDeviceAuth,
|
|
383
|
+
},
|
|
201
384
|
{
|
|
202
385
|
operationId: "inference_chatgpt_subscription_auth",
|
|
203
386
|
endpoint: "inference/chatgpt-subscription/auth",
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* 4. User enters an address; client POSTs to the gateway's
|
|
9
9
|
* `POST /v1/contacts/prompt` HTTP route.
|
|
10
10
|
* 5. Gateway upserts the contact + channel (gateway owns all contact writes).
|
|
11
|
-
* 6.
|
|
12
|
-
*
|
|
11
|
+
* 6. If the prompt was opened with --verify, the gateway reads
|
|
12
|
+
* `contact_prompt_flags` and attests the new channel.
|
|
13
|
+
* 7. Gateway calls daemon IPC `resolve_contact_prompt` with the new contact info.
|
|
14
|
+
* 8. Daemon resolves the pending promise; `contacts/prompt` IPC returns to CLI.
|
|
13
15
|
*
|
|
14
16
|
* The daemon only broadcasts the prompt and waits. It never writes contacts.
|
|
15
17
|
* All writes go through the gateway.
|
|
@@ -44,6 +46,8 @@ export interface ContactPromptResult {
|
|
|
44
46
|
interface PendingContactPrompt {
|
|
45
47
|
resolve: (result: ContactPromptResult) => void;
|
|
46
48
|
timer: ReturnType<typeof setTimeout>;
|
|
49
|
+
/** When true, the gateway marks the submitted channel verified (manual attest). */
|
|
50
|
+
verify: boolean;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
const pendingContactPrompts = new Map<string, PendingContactPrompt>();
|
|
@@ -122,6 +126,16 @@ const ContactPromptParams = z.object({
|
|
|
122
126
|
.enum(["guardian", "trusted-contact", "unknown"])
|
|
123
127
|
.default("unknown")
|
|
124
128
|
.describe("Intended role of the contact being registered."),
|
|
129
|
+
verify: z
|
|
130
|
+
.boolean()
|
|
131
|
+
.optional()
|
|
132
|
+
.describe(
|
|
133
|
+
"When true, submitting the address also marks the channel verified. Same attest as Contacts Verify me.",
|
|
134
|
+
),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const ContactPromptFlagsParams = z.object({
|
|
138
|
+
requestId: z.string().describe("The pending contact_request id."),
|
|
125
139
|
});
|
|
126
140
|
|
|
127
141
|
// ---------------------------------------------------------------------------
|
|
@@ -131,8 +145,15 @@ const ContactPromptParams = z.object({
|
|
|
131
145
|
async function handleContactPrompt({
|
|
132
146
|
body = {},
|
|
133
147
|
}: RouteHandlerArgs): Promise<ContactPromptResult> {
|
|
134
|
-
const {
|
|
135
|
-
|
|
148
|
+
const {
|
|
149
|
+
channel,
|
|
150
|
+
placeholder,
|
|
151
|
+
defaultValue,
|
|
152
|
+
label,
|
|
153
|
+
description,
|
|
154
|
+
role,
|
|
155
|
+
verify,
|
|
156
|
+
} = ContactPromptParams.parse(body);
|
|
136
157
|
|
|
137
158
|
const requestId = uuid();
|
|
138
159
|
|
|
@@ -143,7 +164,11 @@ async function handleContactPrompt({
|
|
|
143
164
|
resolve({ ok: false, error: "Prompt timed out" });
|
|
144
165
|
}, CONTACT_PROMPT_TIMEOUT_MS);
|
|
145
166
|
|
|
146
|
-
pendingContactPrompts.set(requestId, {
|
|
167
|
+
pendingContactPrompts.set(requestId, {
|
|
168
|
+
resolve,
|
|
169
|
+
timer,
|
|
170
|
+
verify: verify === true,
|
|
171
|
+
});
|
|
147
172
|
|
|
148
173
|
broadcastMessage({
|
|
149
174
|
type: "contact_request",
|
|
@@ -156,10 +181,26 @@ async function handleContactPrompt({
|
|
|
156
181
|
role,
|
|
157
182
|
});
|
|
158
183
|
|
|
159
|
-
log.info(
|
|
184
|
+
log.info(
|
|
185
|
+
{ requestId, channel, role, verify: verify === true },
|
|
186
|
+
"Contact prompt broadcast",
|
|
187
|
+
);
|
|
160
188
|
});
|
|
161
189
|
}
|
|
162
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Read-only flags for a pending prompt. The gateway asks this after it
|
|
193
|
+
* writes the channel so a `--verify` prompt can attest without the client
|
|
194
|
+
* having to echo the flag on submit.
|
|
195
|
+
*/
|
|
196
|
+
function readContactPromptFlags({ body = {} }: RouteHandlerArgs): {
|
|
197
|
+
verify: boolean;
|
|
198
|
+
} {
|
|
199
|
+
const { requestId } = ContactPromptFlagsParams.parse(body);
|
|
200
|
+
const pending = pendingContactPrompts.get(requestId);
|
|
201
|
+
return { verify: pending?.verify === true };
|
|
202
|
+
}
|
|
203
|
+
|
|
163
204
|
// ---------------------------------------------------------------------------
|
|
164
205
|
// Routes
|
|
165
206
|
// ---------------------------------------------------------------------------
|
|
@@ -202,4 +243,22 @@ export const CONTACT_PROMPT_ROUTES: RouteDefinition[] = [
|
|
|
202
243
|
"Called by the gateway after it writes the contact and channel. Unblocks the waiting contacts/prompt IPC call.",
|
|
203
244
|
tags: ["contacts"],
|
|
204
245
|
},
|
|
246
|
+
{
|
|
247
|
+
operationId: "contact_prompt_flags",
|
|
248
|
+
endpoint: "contact_prompt_flags",
|
|
249
|
+
method: "POST",
|
|
250
|
+
policy: {
|
|
251
|
+
requiredScopes: ["settings.write"],
|
|
252
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
253
|
+
},
|
|
254
|
+
handler: readContactPromptFlags,
|
|
255
|
+
summary: "Read flags for a pending contact prompt",
|
|
256
|
+
description:
|
|
257
|
+
"Returns whether the pending prompt asked the gateway to mark the submitted channel verified.",
|
|
258
|
+
tags: ["contacts"],
|
|
259
|
+
requestBody: ContactPromptFlagsParams,
|
|
260
|
+
responseBody: z.object({
|
|
261
|
+
verify: z.boolean(),
|
|
262
|
+
}),
|
|
263
|
+
},
|
|
205
264
|
];
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
INVITES_IPC_METHODS,
|
|
13
13
|
RedeemInviteByTokenRequestSchema,
|
|
14
14
|
RedeemVoiceInviteRequestSchema,
|
|
15
|
+
RiskThresholdSchema,
|
|
15
16
|
} from "@vellumai/gateway-client";
|
|
16
17
|
import type { ContactRead } from "@vellumai/gateway-client/gateway-ipc-contracts";
|
|
17
18
|
import {
|
|
@@ -173,11 +174,12 @@ function isContactType(value: string): value is ContactType {
|
|
|
173
174
|
// `role` is gateway-sourced: relayed reads trust the role on the `ContactRead`,
|
|
174
175
|
// while daemon-native reads derive it from the gateway guardian id set at the
|
|
175
176
|
// serve layer (see prepareContactResponse). Interaction telemetry
|
|
176
|
-
// (lastSeenAt/interactionCount/lastInteraction)
|
|
177
|
-
// carry
|
|
178
|
-
//
|
|
179
|
-
// `interactionCount` defaults to 0 (never
|
|
180
|
-
// real number); the
|
|
177
|
+
// (lastSeenAt/interactionCount/lastInteraction) and `autoApproveThreshold`
|
|
178
|
+
// are gateway-owned: relayed reads carry them directly, and daemon-native
|
|
179
|
+
// reads batch-hydrate them from the gateway (see hydrateTelemetryFromGateway).
|
|
180
|
+
// On a gateway fail-soft the count `interactionCount` defaults to 0 (never
|
|
181
|
+
// served as null, so callers render a real number); the
|
|
182
|
+
// `lastSeenAt`/`lastInteraction` timestamps and the ceiling degrade to null.
|
|
181
183
|
// The timestamp fields stay `.nullable()`; `interactionCount` is kept nullable
|
|
182
184
|
// defensively for the relay path, but is never emitted null.
|
|
183
185
|
const contactChannelSchema = z.object({
|
|
@@ -207,6 +209,7 @@ const contactSchema = z.object({
|
|
|
207
209
|
contactType: z.enum(["human", "assistant"]),
|
|
208
210
|
lastInteraction: z.number().nullable().optional(),
|
|
209
211
|
interactionCount: z.number().nullable(),
|
|
212
|
+
autoApproveThreshold: RiskThresholdSchema.nullable().optional(),
|
|
210
213
|
createdAt: z.number(),
|
|
211
214
|
updatedAt: z.number(),
|
|
212
215
|
channels: z.array(contactChannelSchema),
|
|
@@ -252,6 +255,7 @@ async function relayListContacts(limit: number, role: ContactRole | undefined) {
|
|
|
252
255
|
type ContactWithGatewayTelemetry = Omit<ContactWithChannels, "channels"> & {
|
|
253
256
|
interactionCount: number;
|
|
254
257
|
lastInteraction: number | null;
|
|
258
|
+
autoApproveThreshold: ContactRead["autoApproveThreshold"];
|
|
255
259
|
channels: Array<
|
|
256
260
|
ContactChannel & {
|
|
257
261
|
lastSeenAt: number | null;
|
|
@@ -271,16 +275,16 @@ function channelKey(type: string, address: string): string {
|
|
|
271
275
|
}
|
|
272
276
|
|
|
273
277
|
/**
|
|
274
|
-
* Overlay gateway-owned interaction telemetry
|
|
275
|
-
* (search / contactType-filtered),
|
|
276
|
-
* daemon still owns the FILTERING
|
|
277
|
-
* design-blocked), but telemetry
|
|
278
|
-
* interactionCount/lastInteraction, channel lastSeenAt)
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
278
|
+
* Overlay gateway-owned interaction telemetry and the contact auto-approve
|
|
279
|
+
* ceiling onto daemon-native contact reads (search / contactType-filtered),
|
|
280
|
+
* which bypass the gateway list relay. The daemon still owns the FILTERING
|
|
281
|
+
* (gateway-native search/contactType is design-blocked), but telemetry
|
|
282
|
+
* (contact + channel interactionCount/lastInteraction, channel lastSeenAt)
|
|
283
|
+
* and `autoApproveThreshold` are gateway-owned, so batch-fetch them via
|
|
284
|
+
* `contacts_list_rich` keyed by the filtered id set and overlay them. Fail-soft:
|
|
285
|
+
* if the gateway read fails or omits a contact, counts degrade to 0,
|
|
286
|
+
* timestamps to null, and the ceiling to null rather than falling back to
|
|
287
|
+
* the local assistant-DB aggregation.
|
|
284
288
|
*/
|
|
285
289
|
async function hydrateTelemetryFromGateway(
|
|
286
290
|
contacts: ContactWithChannels[],
|
|
@@ -321,6 +325,7 @@ async function hydrateTelemetryFromGateway(
|
|
|
321
325
|
...c,
|
|
322
326
|
interactionCount: gw?.interactionCount ?? 0,
|
|
323
327
|
lastInteraction: gw?.lastInteraction ?? null,
|
|
328
|
+
autoApproveThreshold: gw?.autoApproveThreshold ?? null,
|
|
324
329
|
channels: c.channels.map((ch) => {
|
|
325
330
|
const gwCh =
|
|
326
331
|
gwChannelById.get(ch.id) ??
|