@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
|
@@ -267,24 +267,106 @@ describe("runBackgroundJob", () => {
|
|
|
267
267
|
// `turnFailure`. The runner must surface this as a failure, not ok=true.
|
|
268
268
|
processMessageImpl = async () => ({
|
|
269
269
|
messageId: "msg-failed-turn",
|
|
270
|
-
turnFailure: { failureCode: "
|
|
270
|
+
turnFailure: { failureCode: "PROVIDER_BILLING" },
|
|
271
271
|
});
|
|
272
272
|
|
|
273
273
|
const result = await runBackgroundJob(baseOpts());
|
|
274
274
|
|
|
275
275
|
expect(result.ok).toBe(false);
|
|
276
276
|
expect(result.errorKind).toBe("model_provider");
|
|
277
|
-
expect(result.error?.message).toContain("
|
|
277
|
+
expect(result.error?.message).toContain("PROVIDER_BILLING");
|
|
278
278
|
expect(result.conversationId).toBe(STUB_CONVERSATION_ID);
|
|
279
279
|
// The stable classified code rides the result so callers can branch on
|
|
280
280
|
// the failure class without parsing the error message.
|
|
281
|
-
expect(result.failureCode).toBe("
|
|
281
|
+
expect(result.turnFailure?.failureCode).toBe("PROVIDER_BILLING");
|
|
282
282
|
|
|
283
283
|
expect(emitCalls).toHaveLength(1);
|
|
284
284
|
expect(emitCalls[0].sourceEventName).toBe("activity.failed");
|
|
285
285
|
expect(
|
|
286
286
|
(emitCalls[0].contextPayload as { errorKind: string }).errorKind,
|
|
287
287
|
).toBe("model_provider");
|
|
288
|
+
// A provider-scoped failure code keys the notification on the cause and
|
|
289
|
+
// the resolved provider scope, not the job. `heartbeatAgent` resolves the
|
|
290
|
+
// cost-optimized default profile, so every job failing on this code
|
|
291
|
+
// through that route collapses into one notification per UTC day.
|
|
292
|
+
expect(emitCalls[0].dedupeKey as string).toMatch(
|
|
293
|
+
/^activity-failed:cause:PROVIDER_BILLING:cost-optimized:\d{4}-\d{2}-\d{2}$/,
|
|
294
|
+
);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("a usable overrideProfile scopes the cause-keyed dedupe", async () => {
|
|
298
|
+
processMessageImpl = async () => ({
|
|
299
|
+
messageId: "msg-failed-turn",
|
|
300
|
+
turnFailure: { failureCode: "PROVIDER_BILLING" },
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
await runBackgroundJob(baseOpts({ overrideProfile: "balanced" }));
|
|
304
|
+
|
|
305
|
+
expect(emitCalls).toHaveLength(1);
|
|
306
|
+
expect(emitCalls[0].dedupeKey as string).toMatch(
|
|
307
|
+
/^activity-failed:cause:PROVIDER_BILLING:balanced:\d{4}-\d{2}-\d{2}$/,
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("carried turn attribution scopes the dedupe and rides the payload", async () => {
|
|
312
|
+
processMessageImpl = async () => ({
|
|
313
|
+
messageId: "msg-failed-turn",
|
|
314
|
+
turnFailure: {
|
|
315
|
+
failureCode: "PROVIDER_BILLING",
|
|
316
|
+
userMessage: "You're out of credits.",
|
|
317
|
+
errorCategory: "credits_exhausted",
|
|
318
|
+
connectionName: "conn-a",
|
|
319
|
+
profileName: "balanced",
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const result = await runBackgroundJob(baseOpts());
|
|
324
|
+
|
|
325
|
+
expect(result.ok).toBe(false);
|
|
326
|
+
expect(result.turnFailure?.userMessage).toBe("You're out of credits.");
|
|
327
|
+
expect(emitCalls).toHaveLength(1);
|
|
328
|
+
// The carried connection outranks the call site's recomputed profile.
|
|
329
|
+
expect(emitCalls[0].dedupeKey as string).toMatch(
|
|
330
|
+
/^activity-failed:cause:PROVIDER_BILLING:conn-a:\d{4}-\d{2}-\d{2}$/,
|
|
331
|
+
);
|
|
332
|
+
expect(emitCalls[0].contextPayload).toMatchObject({
|
|
333
|
+
failureSummary: "You're out of credits.",
|
|
334
|
+
errorCategory: "credits_exhausted",
|
|
335
|
+
connectionName: "conn-a",
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("job-specific failure code keeps the per-job dedupe key", async () => {
|
|
340
|
+
// CONTEXT_TOO_LARGE names a defect in this job's own prompt; collapsing
|
|
341
|
+
// it across jobs would hide a second job's unrelated failure.
|
|
342
|
+
processMessageImpl = async () => ({
|
|
343
|
+
messageId: "msg-failed-turn",
|
|
344
|
+
turnFailure: { failureCode: "CONTEXT_TOO_LARGE" },
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const result = await runBackgroundJob(baseOpts());
|
|
348
|
+
|
|
349
|
+
expect(result.ok).toBe(false);
|
|
350
|
+
expect(result.turnFailure?.failureCode).toBe("CONTEXT_TOO_LARGE");
|
|
351
|
+
expect(emitCalls).toHaveLength(1);
|
|
352
|
+
expect(emitCalls[0].dedupeKey as string).toMatch(
|
|
353
|
+
/^activity-failed:test-job:\d{4}-\d{2}-\d{2}$/,
|
|
354
|
+
);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("code-less provider failure dedupes on the shared model_provider cause", async () => {
|
|
358
|
+
processMessageImpl = async () => {
|
|
359
|
+
throw new Error("provider returned 401");
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const result = await runBackgroundJob(baseOpts());
|
|
363
|
+
|
|
364
|
+
expect(result.ok).toBe(false);
|
|
365
|
+
expect(result.errorKind).toBe("model_provider");
|
|
366
|
+
expect(emitCalls).toHaveLength(1);
|
|
367
|
+
expect(emitCalls[0].dedupeKey as string).toMatch(
|
|
368
|
+
/^activity-failed:cause:model_provider:cost-optimized:\d{4}-\d{2}-\d{2}$/,
|
|
369
|
+
);
|
|
288
370
|
});
|
|
289
371
|
|
|
290
372
|
test("timeout: returns ok=false with errorKind=timeout and emits activity.failed", async () => {
|
|
@@ -301,6 +383,10 @@ describe("runBackgroundJob", () => {
|
|
|
301
383
|
expect(
|
|
302
384
|
(emitCalls[0].contextPayload as { errorKind: string }).errorKind,
|
|
303
385
|
).toBe("timeout");
|
|
386
|
+
// A timeout is job-specific, so it keeps the per-job dedupe key.
|
|
387
|
+
expect(emitCalls[0].dedupeKey as string).toMatch(
|
|
388
|
+
/^activity-failed:test-job:\d{4}-\d{2}-\d{2}$/,
|
|
389
|
+
);
|
|
304
390
|
});
|
|
305
391
|
|
|
306
392
|
test("suppressFailureNotifications: failure returns ok=false but emits nothing", async () => {
|
|
@@ -459,6 +459,20 @@ export interface WakeResult {
|
|
|
459
459
|
producedToolCalls: boolean;
|
|
460
460
|
/** Present only when `invoked: false`; identifies why the wake was skipped. */
|
|
461
461
|
reason?: WakeSkipReason;
|
|
462
|
+
/**
|
|
463
|
+
* How the agent loop terminated, from its `agent_loop_exit` event. Set on
|
|
464
|
+
* `invoked: true` results whose loop reported a terminal exit, and absent
|
|
465
|
+
* when it reported none (a checkpoint handoff, whose run resumes
|
|
466
|
+
* separately).
|
|
467
|
+
*
|
|
468
|
+
* `"no_tool_calls"` is the model-driven stop: the run finished because the
|
|
469
|
+
* assistant answered without asking for another tool. Every other reason
|
|
470
|
+
* means something ended the run for it (cancellation, an output-token
|
|
471
|
+
* ceiling, an unhandled error), so a caller reading the run's output as a
|
|
472
|
+
* complete conclusion has to tell them apart: the memory retrospective
|
|
473
|
+
* accepts a reviewed-nothing-to-save pass only on a model-driven stop.
|
|
474
|
+
*/
|
|
475
|
+
exitReason?: AgentLoopExitReason;
|
|
462
476
|
}
|
|
463
477
|
|
|
464
478
|
/**
|
|
@@ -1051,6 +1065,10 @@ export async function wakeAgentForOpportunity(
|
|
|
1051
1065
|
// catch turns provider rejections and unhandled throws into a graceful
|
|
1052
1066
|
// no-output stop instead of rethrowing).
|
|
1053
1067
|
let terminalExitReason: AgentLoopExitReason | null = null;
|
|
1068
|
+
// The `exitReason` field for an `invoked: true` result: present exactly
|
|
1069
|
+
// when the loop reported a terminal exit.
|
|
1070
|
+
const exitReasonField = (): { exitReason?: AgentLoopExitReason } =>
|
|
1071
|
+
terminalExitReason !== null ? { exitReason: terminalExitReason } : {};
|
|
1054
1072
|
const persistLog = (record: PendingLog): void => {
|
|
1055
1073
|
try {
|
|
1056
1074
|
recordRequestLog(
|
|
@@ -1125,10 +1143,23 @@ export async function wakeAgentForOpportunity(
|
|
|
1125
1143
|
// the conversation-id seed resolves the same mix arm the dispatch
|
|
1126
1144
|
// path chose. Without these, attribution credits the call-site
|
|
1127
1145
|
// profile/arm instead of the one that ran.
|
|
1146
|
+
//
|
|
1147
|
+
// A rerouted call (`RetryProvider`'s fallback-profile escalation)
|
|
1148
|
+
// outranks both: the wake resolved `overrideProfile` before the
|
|
1149
|
+
// run and cannot know the request was escalated, while
|
|
1150
|
+
// `providerName` and `model` on this same row already follow the
|
|
1151
|
+
// backup off the event. Attributing the profile from the wake's
|
|
1152
|
+
// own resolution would write a row that contradicts itself. One
|
|
1153
|
+
// row is written per `usage` event, each from that event alone,
|
|
1154
|
+
// so this needs no cross-call state: a later non-rerouted call
|
|
1155
|
+
// writes its own row under the wake's resolution.
|
|
1128
1156
|
{
|
|
1129
1157
|
callSite,
|
|
1130
|
-
overrideProfile:
|
|
1131
|
-
|
|
1158
|
+
overrideProfile:
|
|
1159
|
+
event.actualInferenceProfile ?? overrideProfile ?? null,
|
|
1160
|
+
forceOverrideProfile:
|
|
1161
|
+
event.actualInferenceProfile !== undefined ||
|
|
1162
|
+
forceOverrideProfile,
|
|
1132
1163
|
selectionSeed: conversationId,
|
|
1133
1164
|
},
|
|
1134
1165
|
opts.cronRunId ?? null,
|
|
@@ -1545,7 +1576,11 @@ export async function wakeAgentForOpportunity(
|
|
|
1545
1576
|
reason: "run_error" as const,
|
|
1546
1577
|
};
|
|
1547
1578
|
}
|
|
1548
|
-
return {
|
|
1579
|
+
return {
|
|
1580
|
+
invoked: true,
|
|
1581
|
+
producedToolCalls: false,
|
|
1582
|
+
...exitReasonField(),
|
|
1583
|
+
};
|
|
1549
1584
|
} finally {
|
|
1550
1585
|
// Restore the pre-wake values so a queued user turn or background read
|
|
1551
1586
|
// never observes the wake's stamps. (`runAgentLoopImpl` re-stamps both
|
|
@@ -1642,7 +1677,11 @@ export async function wakeAgentForOpportunity(
|
|
|
1642
1677
|
// since checkpoints only fire after tool turns and there were
|
|
1643
1678
|
// none.) The finally still kicks the queue drain so a racy queued
|
|
1644
1679
|
// message isn't stranded.
|
|
1645
|
-
return {
|
|
1680
|
+
return {
|
|
1681
|
+
invoked: true,
|
|
1682
|
+
producedToolCalls: false,
|
|
1683
|
+
...exitReasonField(),
|
|
1684
|
+
};
|
|
1646
1685
|
}
|
|
1647
1686
|
|
|
1648
1687
|
tailMessageCount = tailMessages.length;
|
|
@@ -1677,7 +1716,7 @@ export async function wakeAgentForOpportunity(
|
|
|
1677
1716
|
});
|
|
1678
1717
|
drainedInTry = true;
|
|
1679
1718
|
|
|
1680
|
-
return { invoked: true, producedToolCalls };
|
|
1719
|
+
return { invoked: true, producedToolCalls, ...exitReasonField() };
|
|
1681
1720
|
} finally {
|
|
1682
1721
|
// Put the conversation's resting trust back on every exit path.
|
|
1683
1722
|
restorePersistentWakeTrust();
|
|
@@ -18,15 +18,18 @@
|
|
|
18
18
|
* `suppressFailureNotifications`.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import { resolveSingleRouteProfileKey } from "../config/llm-resolver.js";
|
|
22
|
+
import { getConfig } from "../config/loader.js";
|
|
21
23
|
import type { LLMCallSite } from "../config/schemas/llm.js";
|
|
22
24
|
import { processMessage } from "../daemon/process-message.js";
|
|
23
25
|
import type { SubagentToolGateMode } from "../daemon/tool-setup-types.js";
|
|
24
26
|
import type { TrustContext } from "../daemon/trust-context-types.js";
|
|
25
|
-
import {
|
|
26
|
-
import type { AttentionHints } from "../notifications/signal.js";
|
|
27
|
+
import { emitBackgroundFailureSignal } from "../notifications/background-failure-signal.js";
|
|
27
28
|
import { bootstrapConversation } from "../persistence/conversation-bootstrap.js";
|
|
28
29
|
import { addMessage } from "../persistence/conversation-crud.js";
|
|
29
30
|
import type { TitleOrigin } from "../persistence/conversation-title-service.js";
|
|
31
|
+
import { dispatchProviderResolvable } from "../providers/provider-resolvability.js";
|
|
32
|
+
import type { TurnFailure } from "../telemetry/turn-outcome.js";
|
|
30
33
|
import { getLogger } from "../util/logger.js";
|
|
31
34
|
import { hasReceivedUserMessage } from "./pre-first-message-gate.js";
|
|
32
35
|
|
|
@@ -53,12 +56,14 @@ class BackgroundJobTimeoutError extends Error {
|
|
|
53
56
|
*/
|
|
54
57
|
class BackgroundJobTurnFailureError extends Error {
|
|
55
58
|
override name = "BackgroundJobTurnFailureError";
|
|
56
|
-
readonly
|
|
57
|
-
constructor(
|
|
59
|
+
readonly turnFailure: TurnFailure;
|
|
60
|
+
constructor(turnFailure: TurnFailure) {
|
|
58
61
|
super(
|
|
59
|
-
failureCode
|
|
62
|
+
turnFailure.failureCode
|
|
63
|
+
? `Agent turn failed (${turnFailure.failureCode})`
|
|
64
|
+
: "Agent turn failed",
|
|
60
65
|
);
|
|
61
|
-
this.
|
|
66
|
+
this.turnFailure = turnFailure;
|
|
62
67
|
}
|
|
63
68
|
}
|
|
64
69
|
|
|
@@ -99,9 +104,9 @@ export interface RunBackgroundJobOptions {
|
|
|
99
104
|
timeoutMs: number;
|
|
100
105
|
/**
|
|
101
106
|
* When true, failures do NOT emit an `activity.failed` notification.
|
|
102
|
-
* Use for jobs
|
|
103
|
-
*
|
|
104
|
-
*
|
|
107
|
+
* Use for jobs whose failure alerting is owned elsewhere: heartbeat's
|
|
108
|
+
* alerter banner, and the scheduler's retry policy (which alerts once,
|
|
109
|
+
* when retries are exhausted, instead of once per attempt).
|
|
105
110
|
*/
|
|
106
111
|
suppressFailureNotifications?: boolean;
|
|
107
112
|
/** Conversation grouping id. Defaults to `"system:background"`. */
|
|
@@ -199,13 +204,14 @@ export interface RunBackgroundJobResult {
|
|
|
199
204
|
error?: Error;
|
|
200
205
|
errorKind?: BackgroundJobErrorKind;
|
|
201
206
|
/**
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
+
* The failing turn's full carried classification (stable failureCode,
|
|
208
|
+
* authored user message, category, connection/profile attribution), when
|
|
209
|
+
* the failure was a non-throwing turn failure. Absent for timeouts and
|
|
210
|
+
* thrown exceptions. The single classification field on the result:
|
|
211
|
+
* callers branch on `turnFailure?.failureCode` and forward the object
|
|
212
|
+
* wholesale when reporting the failure onward.
|
|
207
213
|
*/
|
|
208
|
-
|
|
214
|
+
turnFailure?: TurnFailure;
|
|
209
215
|
/**
|
|
210
216
|
* Set when the runner declined to execute. Callers can distinguish a
|
|
211
217
|
* skipped job from a successful one even though both report `ok: true`.
|
|
@@ -377,18 +383,17 @@ export async function runBackgroundJob(
|
|
|
377
383
|
// Rethrow so it flows through the shared failure path below (logging +
|
|
378
384
|
// `activity.failed` emission) rather than returning `ok: true`.
|
|
379
385
|
if (runResult.turnFailure) {
|
|
380
|
-
throw new BackgroundJobTurnFailureError(
|
|
381
|
-
runResult.turnFailure.failureCode,
|
|
382
|
-
);
|
|
386
|
+
throw new BackgroundJobTurnFailureError(runResult.turnFailure);
|
|
383
387
|
}
|
|
384
388
|
return { conversationId: conversation.id, ok: true };
|
|
385
389
|
} catch (err) {
|
|
386
390
|
const errorKind = classifyError(err);
|
|
387
391
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
388
|
-
const
|
|
392
|
+
const turnFailure =
|
|
389
393
|
err instanceof BackgroundJobTurnFailureError
|
|
390
|
-
? err.
|
|
394
|
+
? err.turnFailure
|
|
391
395
|
: undefined;
|
|
396
|
+
const failureCode = turnFailure?.failureCode;
|
|
392
397
|
// Bootstrap can fail before `conversation` is assigned; fall back to ""
|
|
393
398
|
// so the structured failure result still flows to the caller.
|
|
394
399
|
const conversationId = conversation?.id ?? "";
|
|
@@ -404,38 +409,51 @@ export async function runBackgroundJob(
|
|
|
404
409
|
);
|
|
405
410
|
|
|
406
411
|
if (!opts.suppressFailureNotifications) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
sourceChannel: "assistant_tool",
|
|
421
|
-
sourceContextId: conversationId,
|
|
422
|
-
sourceEventName: "activity.failed",
|
|
423
|
-
dedupeKey,
|
|
424
|
-
contextPayload: {
|
|
425
|
-
jobName: opts.jobName,
|
|
426
|
-
errorMessage: error.message,
|
|
427
|
-
errorKind,
|
|
428
|
-
},
|
|
429
|
-
attentionHints: hints,
|
|
430
|
-
}).catch((emitErr) => {
|
|
431
|
-
log.warn(
|
|
412
|
+
// Fallback scope for failures that carry no attribution: the profile
|
|
413
|
+
// key the resolver would use for this call site, computed with the
|
|
414
|
+
// same provider-resolvability predicate dispatch uses so the
|
|
415
|
+
// recomputation cannot accept a profile dispatch rejected (e.g. a pin
|
|
416
|
+
// whose connection was force-deleted). A mix winner, no named winner,
|
|
417
|
+
// or a config read failing on this path all yield no scope, and the
|
|
418
|
+
// key falls back to per-job. A carried `turnFailure.profileName` is
|
|
419
|
+
// dispatch's own attribution and takes precedence over all of this.
|
|
420
|
+
let fallbackProviderScope: string | undefined;
|
|
421
|
+
try {
|
|
422
|
+
fallbackProviderScope = resolveSingleRouteProfileKey(
|
|
423
|
+
opts.callSite,
|
|
424
|
+
getConfig().llm,
|
|
432
425
|
{
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
426
|
+
...(opts.overrideProfile
|
|
427
|
+
? { overrideProfile: opts.overrideProfile }
|
|
428
|
+
: {}),
|
|
429
|
+
isResolvableProvider: dispatchProviderResolvable,
|
|
436
430
|
},
|
|
437
|
-
"Failed to emit activity.failed notification for background job",
|
|
438
431
|
);
|
|
432
|
+
} catch {
|
|
433
|
+
fallbackProviderScope = undefined;
|
|
434
|
+
}
|
|
435
|
+
emitBackgroundFailureSignal({
|
|
436
|
+
jobName: opts.jobName,
|
|
437
|
+
sourceChannel: "assistant_tool",
|
|
438
|
+
sourceContextId: conversationId,
|
|
439
|
+
errorKind,
|
|
440
|
+
errorMessage: error.message,
|
|
441
|
+
...(failureCode !== undefined ? { failureCode } : {}),
|
|
442
|
+
...(turnFailure?.userMessage !== undefined
|
|
443
|
+
? { failureSummary: turnFailure.userMessage }
|
|
444
|
+
: {}),
|
|
445
|
+
...(turnFailure?.errorCategory !== undefined
|
|
446
|
+
? { errorCategory: turnFailure.errorCategory }
|
|
447
|
+
: {}),
|
|
448
|
+
...(turnFailure?.connectionName !== undefined
|
|
449
|
+
? { connectionName: turnFailure.connectionName }
|
|
450
|
+
: {}),
|
|
451
|
+
...(turnFailure?.profileName !== undefined
|
|
452
|
+
? { profileName: turnFailure.profileName }
|
|
453
|
+
: {}),
|
|
454
|
+
...(fallbackProviderScope !== undefined
|
|
455
|
+
? { fallbackProviderScope }
|
|
456
|
+
: {}),
|
|
439
457
|
});
|
|
440
458
|
}
|
|
441
459
|
|
|
@@ -444,7 +462,7 @@ export async function runBackgroundJob(
|
|
|
444
462
|
ok: false,
|
|
445
463
|
error,
|
|
446
464
|
errorKind,
|
|
447
|
-
...(
|
|
465
|
+
...(turnFailure !== undefined ? { turnFailure } : {}),
|
|
448
466
|
};
|
|
449
467
|
} finally {
|
|
450
468
|
if (timer) {
|
|
@@ -548,44 +548,6 @@ const slackProbe: ChannelProbe = {
|
|
|
548
548
|
},
|
|
549
549
|
};
|
|
550
550
|
|
|
551
|
-
/**
|
|
552
|
-
* Whether Discord admits anything in the guilds it has joined.
|
|
553
|
-
*
|
|
554
|
-
* A configuration check rather than an operational one, and the distinction
|
|
555
|
-
* carries the meaning. An empty allow-list is a setup step nobody finished,
|
|
556
|
-
* not a channel that broke: direct messages reach the assistant either way,
|
|
557
|
-
* because a message with no guild never meets this gate. Reporting it as
|
|
558
|
-
* failing health would send an operator to look for an outage that is not
|
|
559
|
-
* there, and reporting it as ready would claim delivery while every guild
|
|
560
|
-
* message is dropped.
|
|
561
|
-
*
|
|
562
|
-
* An unreachable gateway is indeterminate, matching the socket check beside
|
|
563
|
-
* it: nobody looked, so nothing is claimed either way.
|
|
564
|
-
*/
|
|
565
|
-
async function checkDiscordGuildAdmission(): Promise<ReadinessCheckResult> {
|
|
566
|
-
const { readDiscordAdmission } =
|
|
567
|
-
await import("../channels/gateway-discord-admission.js");
|
|
568
|
-
|
|
569
|
-
let admitted;
|
|
570
|
-
try {
|
|
571
|
-
admitted = (await readDiscordAdmission()).admittedChannelCount;
|
|
572
|
-
} catch {
|
|
573
|
-
return {
|
|
574
|
-
name: "guild_admission",
|
|
575
|
-
passed: true,
|
|
576
|
-
message: "Could not reach the gateway to read Discord's allow-list",
|
|
577
|
-
indeterminate: true,
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
return check(
|
|
582
|
-
"guild_admission",
|
|
583
|
-
admitted > 0,
|
|
584
|
-
`Discord may act in ${admitted} channel${admitted === 1 ? "" : "s"}`,
|
|
585
|
-
"Discord is in no channel's allow-list, so every guild message is dropped. Add channel ids to `discord.allowedChannelIds`",
|
|
586
|
-
);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
551
|
// ── Discord Probe ───────────────────────────────────────────────────────────
|
|
590
552
|
|
|
591
553
|
/**
|
|
@@ -614,7 +576,6 @@ const discordProbe: ChannelProbe = {
|
|
|
614
576
|
"Discord bot token",
|
|
615
577
|
),
|
|
616
578
|
await checkSocketInboundDelivery("discord"),
|
|
617
|
-
await checkDiscordGuildAdmission(),
|
|
618
579
|
];
|
|
619
580
|
},
|
|
620
581
|
};
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
startPluginScheduleReconcileSweep,
|
|
43
43
|
stopPluginScheduleReconcileSweep,
|
|
44
44
|
} from "../schedule/plugin-schedule-reconciler.js";
|
|
45
|
+
import { sttCatalogKeyForRole } from "../stt/roles.js";
|
|
45
46
|
import {
|
|
46
47
|
activeSttStreamSessions,
|
|
47
48
|
SttStreamSession,
|
|
@@ -265,16 +266,22 @@ export class RuntimeHttpServer {
|
|
|
265
266
|
const sttData = data;
|
|
266
267
|
|
|
267
268
|
// The runtime is config-authoritative: always resolve the
|
|
268
|
-
// provider from
|
|
269
|
-
// the
|
|
269
|
+
// provider from config regardless of what the client/gateway
|
|
270
|
+
// requested. Dictation's own role selects it, matching the dial
|
|
271
|
+
// below, so the session's label, its failure copy and the
|
|
272
|
+
// mismatch telemetry all name the provider this socket actually
|
|
273
|
+
// reaches.
|
|
270
274
|
//
|
|
271
275
|
// getConfig() can throw (e.g. after invalidateConfigCache()
|
|
272
276
|
// when config.json is temporarily invalid). Wrap in try/catch
|
|
273
|
-
// so the session still starts normally
|
|
277
|
+
// so the session still starts normally: resolveStreamingTranscriber
|
|
274
278
|
// reads config inside SttStreamSession.start()'s own guarded path.
|
|
275
279
|
let configuredProvider: string | undefined;
|
|
276
280
|
try {
|
|
277
|
-
configuredProvider =
|
|
281
|
+
configuredProvider = sttCatalogKeyForRole(
|
|
282
|
+
getConfig().services.stt,
|
|
283
|
+
"dictation",
|
|
284
|
+
);
|
|
278
285
|
|
|
279
286
|
// Mismatch telemetry: when the optional requested provider
|
|
280
287
|
// disagrees with the configured provider, log a warning so
|
|
@@ -327,6 +334,7 @@ export class RuntimeHttpServer {
|
|
|
327
334
|
// transcriber and sends a `ready` event on success.
|
|
328
335
|
void session.start(() =>
|
|
329
336
|
resolveStreamingTranscriber({
|
|
337
|
+
role: "dictation",
|
|
330
338
|
sampleRate: sttData.sampleRate,
|
|
331
339
|
}),
|
|
332
340
|
);
|
|
@@ -464,7 +464,7 @@ describe("GET /avatar/get (manifest-driven precedence)", () => {
|
|
|
464
464
|
base64?: string;
|
|
465
465
|
}
|
|
466
466
|
|
|
467
|
-
test("returns the PNG for an image manifest", () => {
|
|
467
|
+
test("returns the PNG for an image manifest", async () => {
|
|
468
468
|
writeFileSync(path(IMAGE_FILENAME), Buffer.from("png bytes"));
|
|
469
469
|
const state: AvatarState = {
|
|
470
470
|
kind: "image",
|
|
@@ -474,12 +474,12 @@ describe("GET /avatar/get (manifest-driven precedence)", () => {
|
|
|
474
474
|
};
|
|
475
475
|
writeManifest(state, avatarDir);
|
|
476
476
|
|
|
477
|
-
const result = getHandler("avatar_get")({}) as GetResult;
|
|
477
|
+
const result = (await getHandler("avatar_get")({})) as GetResult;
|
|
478
478
|
expect(result.exists).toBe(true);
|
|
479
479
|
expect(result.path).toBe(path(IMAGE_FILENAME));
|
|
480
480
|
});
|
|
481
481
|
|
|
482
|
-
test("returns base64 for an image manifest when format=base64", () => {
|
|
482
|
+
test("returns base64 for an image manifest when format=base64", async () => {
|
|
483
483
|
writeFileSync(path(IMAGE_FILENAME), Buffer.from("png bytes"));
|
|
484
484
|
const state: AvatarState = {
|
|
485
485
|
kind: "image",
|
|
@@ -489,15 +489,15 @@ describe("GET /avatar/get (manifest-driven precedence)", () => {
|
|
|
489
489
|
};
|
|
490
490
|
writeManifest(state, avatarDir);
|
|
491
491
|
|
|
492
|
-
const result = getHandler("avatar_get")({
|
|
492
|
+
const result = (await getHandler("avatar_get")({
|
|
493
493
|
queryParams: { format: "base64" },
|
|
494
|
-
}) as GetResult;
|
|
494
|
+
})) as GetResult;
|
|
495
495
|
expect(result.exists).toBe(true);
|
|
496
496
|
expect(result.base64).toBe(Buffer.from("png bytes").toString("base64"));
|
|
497
497
|
expect(result.path).toBeUndefined();
|
|
498
498
|
});
|
|
499
499
|
|
|
500
|
-
test("returns the existing raster for a character manifest", () => {
|
|
500
|
+
test("returns the existing raster for a character manifest", async () => {
|
|
501
501
|
// The builder writes the rendered PNG; assert the accessor returns it
|
|
502
502
|
// without needing to re-render (the manifest says character).
|
|
503
503
|
writeFileSync(path(IMAGE_FILENAME), Buffer.from("rendered character png"));
|
|
@@ -509,12 +509,12 @@ describe("GET /avatar/get (manifest-driven precedence)", () => {
|
|
|
509
509
|
};
|
|
510
510
|
writeManifest(state, avatarDir);
|
|
511
511
|
|
|
512
|
-
const result = getHandler("avatar_get")({}) as GetResult;
|
|
512
|
+
const result = (await getHandler("avatar_get")({})) as GetResult;
|
|
513
513
|
expect(result.exists).toBe(true);
|
|
514
514
|
expect(result.path).toBe(path(IMAGE_FILENAME));
|
|
515
515
|
});
|
|
516
516
|
|
|
517
|
-
test("manifest precedence wins over file order: a character manifest is honored even when a PNG is present", () => {
|
|
517
|
+
test("manifest precedence wins over file order: a character manifest is honored even when a PNG is present", async () => {
|
|
518
518
|
// Both a PNG and traits are on disk, but the manifest declares character.
|
|
519
519
|
// The pre-manifest accessor was image-first; precedence is now the manifest.
|
|
520
520
|
writeFileSync(path(IMAGE_FILENAME), Buffer.from("rendered character png"));
|
|
@@ -527,27 +527,27 @@ describe("GET /avatar/get (manifest-driven precedence)", () => {
|
|
|
527
527
|
};
|
|
528
528
|
writeManifest(state, avatarDir);
|
|
529
529
|
|
|
530
|
-
const result = getHandler("avatar_get")({}) as GetResult;
|
|
530
|
+
const result = (await getHandler("avatar_get")({})) as GetResult;
|
|
531
531
|
expect(result.exists).toBe(true);
|
|
532
532
|
expect(result.path).toBe(path(IMAGE_FILENAME));
|
|
533
533
|
});
|
|
534
534
|
|
|
535
|
-
test("returns exists:false for a none manifest", () => {
|
|
535
|
+
test("returns exists:false for a none manifest", async () => {
|
|
536
536
|
writeManifest(
|
|
537
537
|
{ kind: "none", traits: null, source: null, image: null },
|
|
538
538
|
avatarDir,
|
|
539
539
|
);
|
|
540
540
|
|
|
541
|
-
const result = getHandler("avatar_get")({}) as GetResult;
|
|
541
|
+
const result = (await getHandler("avatar_get")({})) as GetResult;
|
|
542
542
|
expect(result.exists).toBe(false);
|
|
543
543
|
expect(result.path).toBeUndefined();
|
|
544
544
|
});
|
|
545
545
|
|
|
546
|
-
test("self-heals from legacy files when no manifest exists (image file) and persists the manifest", () => {
|
|
546
|
+
test("self-heals from legacy files when no manifest exists (image file) and persists the manifest", async () => {
|
|
547
547
|
writeFileSync(path(IMAGE_FILENAME), Buffer.from("png bytes"));
|
|
548
548
|
expect(existsSync(path(MANIFEST_FILENAME))).toBe(false);
|
|
549
549
|
|
|
550
|
-
const result = getHandler("avatar_get")({}) as GetResult;
|
|
550
|
+
const result = (await getHandler("avatar_get")({})) as GetResult;
|
|
551
551
|
expect(result.exists).toBe(true);
|
|
552
552
|
expect(result.path).toBe(path(IMAGE_FILENAME));
|
|
553
553
|
|
|
@@ -558,14 +558,14 @@ describe("GET /avatar/get (manifest-driven precedence)", () => {
|
|
|
558
558
|
expect(persisted.kind).toBe("image");
|
|
559
559
|
});
|
|
560
560
|
|
|
561
|
-
test("returns exists:false for an empty workspace (no manifest, no files)", () => {
|
|
562
|
-
const result = getHandler("avatar_get")({}) as GetResult;
|
|
561
|
+
test("returns exists:false for an empty workspace (no manifest, no files)", async () => {
|
|
562
|
+
const result = (await getHandler("avatar_get")({})) as GetResult;
|
|
563
563
|
expect(result.exists).toBe(false);
|
|
564
564
|
});
|
|
565
565
|
|
|
566
|
-
test("rejects an invalid format", () => {
|
|
567
|
-
expect(
|
|
566
|
+
test("rejects an invalid format", async () => {
|
|
567
|
+
await expect(
|
|
568
568
|
getHandler("avatar_get")({ queryParams: { format: "bmp" } }),
|
|
569
|
-
).toThrow(/Invalid format/);
|
|
569
|
+
).rejects.toThrow(/Invalid format/);
|
|
570
570
|
});
|
|
571
571
|
});
|
|
@@ -270,7 +270,7 @@ afterEach(() => {
|
|
|
270
270
|
// ---------------------------------------------------------------------------
|
|
271
271
|
|
|
272
272
|
describe("handleBackupList", () => {
|
|
273
|
-
test("empty workspace
|
|
273
|
+
test("empty workspace uses the platform offsite default", async () => {
|
|
274
274
|
const origHome = process.env.HOME;
|
|
275
275
|
process.env.HOME = ROOT;
|
|
276
276
|
try {
|
|
@@ -287,10 +287,14 @@ describe("handleBackupList", () => {
|
|
|
287
287
|
|
|
288
288
|
const result = await handleBackupList();
|
|
289
289
|
expect(result.local).toEqual([]);
|
|
290
|
-
expect(result.offsite).toHaveLength(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
290
|
+
expect(result.offsite).toHaveLength(
|
|
291
|
+
process.platform === "darwin" ? 1 : 0,
|
|
292
|
+
);
|
|
293
|
+
if (process.platform === "darwin") {
|
|
294
|
+
expect(result.offsite[0].destination.encrypt).toBe(true);
|
|
295
|
+
expect(result.offsite[0].snapshots).toEqual([]);
|
|
296
|
+
expect(result.offsite[0].reachable).toBe(false);
|
|
297
|
+
}
|
|
294
298
|
expect(result.nextRunAt).toBeNull();
|
|
295
299
|
} finally {
|
|
296
300
|
if (origHome === undefined) {
|