@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
|
@@ -8,6 +8,7 @@ const LIVE_VOICE_CLIENT_FRAME_TYPES = [
|
|
|
8
8
|
"end",
|
|
9
9
|
"update_config",
|
|
10
10
|
"attach_image",
|
|
11
|
+
"text",
|
|
11
12
|
] as const;
|
|
12
13
|
|
|
13
14
|
type LiveVoiceClientFrameType = (typeof LIVE_VOICE_CLIENT_FRAME_TYPES)[number];
|
|
@@ -101,6 +102,22 @@ export interface LiveVoiceClientStartFrame {
|
|
|
101
102
|
* [{@link MIN_BARGE_IN_MIN_SPEECH_MS}, {@link MAX_BARGE_IN_MIN_SPEECH_MS}].
|
|
102
103
|
*/
|
|
103
104
|
readonly bargeInMinSpeechMs?: number;
|
|
105
|
+
/**
|
|
106
|
+
* The client has a text input affordance, so it can take a turn without the
|
|
107
|
+
* microphone (see {@link LiveVoiceClientTextTurnFrame}).
|
|
108
|
+
*
|
|
109
|
+
* Load-bearing at startup, not just a feature announcement: a session whose
|
|
110
|
+
* speech-to-text leg has no working credential is normally rejected outright
|
|
111
|
+
* (`credentials_unavailable`), because a session that cannot hear is a
|
|
112
|
+
* session that cannot be used. A client that can type is the exception: for
|
|
113
|
+
* it, a missing STT leg is degradation rather than failure, so the session
|
|
114
|
+
* starts text-only and says so on `ready` via `audioInput: false`.
|
|
115
|
+
*
|
|
116
|
+
* Absent means false: a client that predates the field has no way to take a
|
|
117
|
+
* turn without the microphone, so a broken STT leg must still fail its
|
|
118
|
+
* session rather than open one it cannot speak into.
|
|
119
|
+
*/
|
|
120
|
+
readonly textInput?: boolean;
|
|
104
121
|
/**
|
|
105
122
|
* Which client opened the session. Absent from clients that predate the
|
|
106
123
|
* field, in which case the originating client is simply unknown.
|
|
@@ -129,6 +146,15 @@ export const MAX_SILENCE_THRESHOLD_MS = 5_000;
|
|
|
129
146
|
export const MIN_BARGE_IN_MIN_SPEECH_MS = 0;
|
|
130
147
|
export const MAX_BARGE_IN_MIN_SPEECH_MS = 3_000;
|
|
131
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Longest typed turn accepted on a `text` frame.
|
|
151
|
+
*
|
|
152
|
+
* Generous next to anything a person says in one breath, and far below what
|
|
153
|
+
* would make the reply unspeakable. The cap exists so a socket tuned for 50 ms
|
|
154
|
+
* audio frames cannot be handed a whole document to synthesize.
|
|
155
|
+
*/
|
|
156
|
+
export const MAX_TEXT_TURN_CHARS = 4_000;
|
|
157
|
+
|
|
132
158
|
export interface LiveVoiceClientAudioFrame {
|
|
133
159
|
readonly type: "audio";
|
|
134
160
|
readonly dataBase64: string;
|
|
@@ -180,6 +206,45 @@ export interface LiveVoiceClientAttachImageFrame {
|
|
|
180
206
|
readonly attachmentId: string;
|
|
181
207
|
}
|
|
182
208
|
|
|
209
|
+
/**
|
|
210
|
+
* A user turn the client already has as text, taken without the microphone.
|
|
211
|
+
*
|
|
212
|
+
* The session runs it through the same pipeline a spoken turn takes, joining
|
|
213
|
+
* at the point STT would have handed over a finished transcript: same turn
|
|
214
|
+
* runner, same streaming TTS, same barge-in and progress narration. Only the
|
|
215
|
+
* capture half is skipped, so the reply is spoken exactly as it would be for
|
|
216
|
+
* speech.
|
|
217
|
+
*
|
|
218
|
+
* Deliberately not routed through `processMessage` into the conversation the
|
|
219
|
+
* session owns. That path diverges from the voice one in ways that have
|
|
220
|
+
* already produced their own bug class (missing user_message_echo, an
|
|
221
|
+
* unpersisted conversation row, missing trustContext), and a typed turn that
|
|
222
|
+
* behaved differently from a spoken one would reintroduce all of it.
|
|
223
|
+
*
|
|
224
|
+
* Accepted at any point in a session, not only as its first turn. A user
|
|
225
|
+
* whose microphone stops working mid-session types the rest of the
|
|
226
|
+
* conversation; a client with no microphone at all (see the start frame's
|
|
227
|
+
* `textInput`) types every turn.
|
|
228
|
+
*/
|
|
229
|
+
export interface LiveVoiceClientTextTurnFrame {
|
|
230
|
+
readonly type: "text";
|
|
231
|
+
readonly text: string;
|
|
232
|
+
/**
|
|
233
|
+
* Marks the turn as an internal instruction rather than something the user
|
|
234
|
+
* typed. The row still persists and still drives the turn, so the model
|
|
235
|
+
* sees it, but it is suppressed from the transcript: no live echo, and
|
|
236
|
+
* `/messages` filters it after a reload.
|
|
237
|
+
*
|
|
238
|
+
* For machine signals the user never wrote, such as the greeting that opens
|
|
239
|
+
* a voice session. A turn the user actually typed leaves it unset.
|
|
240
|
+
*
|
|
241
|
+
* Optional, and a daemon that does not understand it simply persists the
|
|
242
|
+
* turn visibly, so a client cannot tell from the frame alone whether it was
|
|
243
|
+
* honored.
|
|
244
|
+
*/
|
|
245
|
+
readonly hidden?: boolean;
|
|
246
|
+
}
|
|
247
|
+
|
|
183
248
|
export type LiveVoiceClientFrame =
|
|
184
249
|
| LiveVoiceClientStartFrame
|
|
185
250
|
| LiveVoiceClientAudioFrame
|
|
@@ -187,7 +252,8 @@ export type LiveVoiceClientFrame =
|
|
|
187
252
|
| LiveVoiceClientInterruptFrame
|
|
188
253
|
| LiveVoiceClientEndFrame
|
|
189
254
|
| LiveVoiceClientUpdateConfigFrame
|
|
190
|
-
| LiveVoiceClientAttachImageFrame
|
|
255
|
+
| LiveVoiceClientAttachImageFrame
|
|
256
|
+
| LiveVoiceClientTextTurnFrame;
|
|
191
257
|
|
|
192
258
|
interface LiveVoiceBinaryAudioFrame {
|
|
193
259
|
readonly type: "binary_audio";
|
|
@@ -209,11 +275,55 @@ export interface LiveVoiceReadyServerFrame extends LiveVoiceServerFrameBase {
|
|
|
209
275
|
* (older daemons) means "manual".
|
|
210
276
|
*/
|
|
211
277
|
readonly turnDetection?: LiveVoiceTurnDetectionMode;
|
|
278
|
+
/**
|
|
279
|
+
* Whether this daemon will accept `text` frames on the session. Absent
|
|
280
|
+
* (older daemons) means no, which is what lets a client that asked for
|
|
281
|
+
* `textInput` fall back rather than type into a socket that would answer
|
|
282
|
+
* every typed turn with an `unknown_type` error.
|
|
283
|
+
*/
|
|
284
|
+
readonly textInput?: boolean;
|
|
285
|
+
/**
|
|
286
|
+
* Whether the session's speech-to-text leg is live. Absent means yes, which
|
|
287
|
+
* is the only thing an older daemon can have meant: it rejects a session it
|
|
288
|
+
* cannot transcribe, so every session it readies can hear.
|
|
289
|
+
*
|
|
290
|
+
* False is reachable only for a client that declared `textInput`, and tells
|
|
291
|
+
* it to present the session as typed rather than draw a microphone that
|
|
292
|
+
* will never hear anything.
|
|
293
|
+
*/
|
|
294
|
+
readonly audioInput?: boolean;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Where the session holding the daemon's single live-voice slot is running,
|
|
299
|
+
* as much of it as the daemon knows.
|
|
300
|
+
*
|
|
301
|
+
* Display only: it is what lets a client refused a session say where the one
|
|
302
|
+
* that blocked it is. Nothing routes, authorizes, or decides capability on
|
|
303
|
+
* it, and every field is optional, so a holder that names neither its surface
|
|
304
|
+
* nor a conversation is simply less specific.
|
|
305
|
+
*/
|
|
306
|
+
export interface LiveVoiceSessionHolder {
|
|
307
|
+
/**
|
|
308
|
+
* The OS surface the holder is running on, from its `start` frame. A
|
|
309
|
+
* {@link ClientOs} carries no transport meaning by contract (see
|
|
310
|
+
* `channels/types.ts`), which is exactly right for a label: it says which
|
|
311
|
+
* device the user should go looking on, and answers nothing else.
|
|
312
|
+
*/
|
|
313
|
+
readonly client?: ClientOs;
|
|
314
|
+
/** The conversation the holder is talking in, once it has one. */
|
|
315
|
+
readonly conversationId?: string;
|
|
212
316
|
}
|
|
213
317
|
|
|
214
318
|
export interface LiveVoiceBusyServerFrame extends LiveVoiceServerFrameBase {
|
|
215
319
|
readonly type: "busy";
|
|
216
320
|
readonly activeSessionId: string;
|
|
321
|
+
/**
|
|
322
|
+
* Absent from daemons that predate it, and from a holder that revealed
|
|
323
|
+
* neither field, so a client must degrade to the unspecific copy rather
|
|
324
|
+
* than assume.
|
|
325
|
+
*/
|
|
326
|
+
readonly holder?: LiveVoiceSessionHolder;
|
|
217
327
|
}
|
|
218
328
|
|
|
219
329
|
/**
|
|
@@ -544,7 +654,73 @@ export function validateLiveVoiceClientFrame(
|
|
|
544
654
|
return validateUpdateConfigFrame(value);
|
|
545
655
|
case "attach_image":
|
|
546
656
|
return validateAttachImageFrame(value);
|
|
657
|
+
case "text":
|
|
658
|
+
return validateTextTurnFrame(value);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function validateTextTurnFrame(
|
|
663
|
+
value: Record<string, unknown>,
|
|
664
|
+
): LiveVoiceParseResult<LiveVoiceClientTextTurnFrame> {
|
|
665
|
+
if (!("text" in value)) {
|
|
666
|
+
return protocolError(
|
|
667
|
+
"missing_required_field",
|
|
668
|
+
"text frame is missing required field text",
|
|
669
|
+
"text",
|
|
670
|
+
"text",
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (typeof value.text !== "string") {
|
|
675
|
+
return protocolError(
|
|
676
|
+
"invalid_field",
|
|
677
|
+
"text frame field text must be a string",
|
|
678
|
+
"text",
|
|
679
|
+
"text",
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Trimmed before the emptiness check, so a frame carrying only whitespace is
|
|
684
|
+
// rejected here rather than reaching the session as a turn with nothing in
|
|
685
|
+
// it. The trimmed value is what travels, so the turn the session runs is the
|
|
686
|
+
// one that was validated.
|
|
687
|
+
const text = value.text.trim();
|
|
688
|
+
|
|
689
|
+
if (text.length === 0) {
|
|
690
|
+
return protocolError(
|
|
691
|
+
"invalid_field",
|
|
692
|
+
"text frame field text must not be empty",
|
|
693
|
+
"text",
|
|
694
|
+
"text",
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (text.length > MAX_TEXT_TURN_CHARS) {
|
|
699
|
+
return protocolError(
|
|
700
|
+
"invalid_field",
|
|
701
|
+
`text frame field text must be at most ${MAX_TEXT_TURN_CHARS} characters`,
|
|
702
|
+
"text",
|
|
703
|
+
"text",
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if ("hidden" in value && typeof value.hidden !== "boolean") {
|
|
708
|
+
return protocolError(
|
|
709
|
+
"invalid_field",
|
|
710
|
+
"text frame field hidden must be a boolean",
|
|
711
|
+
"hidden",
|
|
712
|
+
"text",
|
|
713
|
+
);
|
|
547
714
|
}
|
|
715
|
+
|
|
716
|
+
return {
|
|
717
|
+
ok: true,
|
|
718
|
+
frame: {
|
|
719
|
+
type: "text",
|
|
720
|
+
text,
|
|
721
|
+
...(value.hidden === true ? { hidden: true } : {}),
|
|
722
|
+
},
|
|
723
|
+
};
|
|
548
724
|
}
|
|
549
725
|
|
|
550
726
|
function validateAttachImageFrame(
|
|
@@ -744,6 +920,15 @@ function validateStartFrame(
|
|
|
744
920
|
);
|
|
745
921
|
}
|
|
746
922
|
|
|
923
|
+
if ("textInput" in value && typeof value.textInput !== "boolean") {
|
|
924
|
+
return protocolError(
|
|
925
|
+
"invalid_field",
|
|
926
|
+
"start frame field textInput must be a boolean",
|
|
927
|
+
"textInput",
|
|
928
|
+
"start",
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
|
|
747
932
|
// An unrecognized client is dropped rather than rejected: the field is an
|
|
748
933
|
// analytics dimension, and failing a session's startup over it would trade a
|
|
749
934
|
// gap in a chart for a user who cannot talk to their assistant.
|
|
@@ -767,6 +952,7 @@ function validateStartFrame(
|
|
|
767
952
|
...(typeof value.bargeInMinSpeechMs === "number"
|
|
768
953
|
? { bargeInMinSpeechMs: value.bargeInMinSpeechMs }
|
|
769
954
|
: {}),
|
|
955
|
+
...(value.textInput === true ? { textInput: true } : {}),
|
|
770
956
|
},
|
|
771
957
|
};
|
|
772
958
|
}
|
|
@@ -21,7 +21,8 @@ import { retryableCall } from "../retry-policy.js";
|
|
|
21
21
|
|
|
22
22
|
const log = getLogger("discord-api");
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
/** Exported so a caller validating a not-yet-stored token uses the same base. */
|
|
25
|
+
export const DISCORD_API_BASE_URL = "https://discord.com/api/v10";
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Discord requires HTTP API clients to identify themselves with a User-Agent
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Withdraw a Discord approval card when its underlying guardian request
|
|
3
|
+
* resolves.
|
|
4
|
+
*
|
|
5
|
+
* A Discord card is plain text whose actionable tail (the typed-command
|
|
6
|
+
* instructions) rides the last chunk, and that chunk's id is what the
|
|
7
|
+
* delivery row captured. Rewriting it to the terminal outcome removes every
|
|
8
|
+
* live affordance and shows the decision in one edit, so no separate status
|
|
9
|
+
* reply is needed. The delivery row's chat id is the guardian's *user*
|
|
10
|
+
* snowflake, never a room: the DM channel is resolved from the person at
|
|
11
|
+
* withdrawal time, the same way the adapter resolved it to deliver.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
isParkAction,
|
|
16
|
+
resolveDecisionStatusWord,
|
|
17
|
+
} from "../../../runtime/channel-approval-types.js";
|
|
18
|
+
import { getLogger } from "../../../util/logger.js";
|
|
19
|
+
import { openDiscordDmChannel } from "./api.js";
|
|
20
|
+
import { editDiscordMessage } from "./send.js";
|
|
21
|
+
|
|
22
|
+
const log = getLogger("discord-withdraw");
|
|
23
|
+
|
|
24
|
+
const STATUS_GLYPH: Record<string, string> = {
|
|
25
|
+
approved: "✅",
|
|
26
|
+
denied: "❌",
|
|
27
|
+
expired: "⌛",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const PARK_STATUS_GLYPH = "⏸️";
|
|
31
|
+
|
|
32
|
+
function buildStatusText(status: string, decidedAction?: string): string {
|
|
33
|
+
const park = status === "denied" && isParkAction(decidedAction);
|
|
34
|
+
const glyph = park ? PARK_STATUS_GLYPH : (STATUS_GLYPH[status] ?? "");
|
|
35
|
+
const word = resolveDecisionStatusWord(status, decidedAction);
|
|
36
|
+
return glyph ? `${glyph} ${word}` : word;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface WithdrawDiscordApprovalCardParams {
|
|
40
|
+
/** The guardian's user snowflake from the delivery row. */
|
|
41
|
+
guardianUserId: string;
|
|
42
|
+
/** The card's last-chunk message id from the delivery row. */
|
|
43
|
+
messageId: string;
|
|
44
|
+
status: string;
|
|
45
|
+
decidedAction?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function withdrawDiscordApprovalCard(
|
|
49
|
+
params: WithdrawDiscordApprovalCardParams,
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
const channelId = await openDiscordDmChannel(params.guardianUserId);
|
|
52
|
+
await editDiscordMessage(
|
|
53
|
+
{ channelId },
|
|
54
|
+
params.messageId,
|
|
55
|
+
buildStatusText(params.status, params.decidedAction),
|
|
56
|
+
{ emphasis: "muted" },
|
|
57
|
+
);
|
|
58
|
+
log.info(
|
|
59
|
+
{ guardianUserId: params.guardianUserId, messageId: params.messageId },
|
|
60
|
+
"Discord approval card withdrawn",
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ChannelId } from "../channels/types.js";
|
|
1
2
|
import { safeParseRecord } from "../util/json.js";
|
|
2
3
|
import {
|
|
3
4
|
type ProviderMessageMetadata,
|
|
@@ -49,3 +50,34 @@ export function readProviderMetadata(
|
|
|
49
50
|
const slackMeta = readSlackMetadataFromMessageMetadata(metadata, opts);
|
|
50
51
|
return slackMeta ? slackMetadataAsProviderMetadata(slackMeta) : null;
|
|
51
52
|
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Merge an edit or delete stamp into a row's serialized metadata, producing
|
|
56
|
+
* the neutral envelope to store back under `providerMeta`.
|
|
57
|
+
*
|
|
58
|
+
* The base is whatever the row already says about itself through
|
|
59
|
+
* {@link readProviderMetadata}, flat-legacy Slack rows included, so fields
|
|
60
|
+
* the old envelope carried (thread, actor, display name) survive the stamp
|
|
61
|
+
* rather than being shadowed by a minimal synthesis. A row that says
|
|
62
|
+
* nothing gets the envelope synthesized from the caller's lookup-derived
|
|
63
|
+
* facts, so the result always validates on read.
|
|
64
|
+
*/
|
|
65
|
+
export function mergeProviderMessageMetadata(
|
|
66
|
+
rowMetadata: string | null,
|
|
67
|
+
seed: {
|
|
68
|
+
source: ChannelId;
|
|
69
|
+
conversationExternalId: string;
|
|
70
|
+
messageId: string;
|
|
71
|
+
threadId?: string;
|
|
72
|
+
},
|
|
73
|
+
patch: { editedAt?: number; deletedAt?: number },
|
|
74
|
+
): string {
|
|
75
|
+
const base = readProviderMetadata(rowMetadata, { allowFlatLegacy: true }) ?? {
|
|
76
|
+
source: seed.source,
|
|
77
|
+
conversationExternalId: seed.conversationExternalId,
|
|
78
|
+
messageId: seed.messageId,
|
|
79
|
+
...(seed.threadId ? { threadId: seed.threadId } : {}),
|
|
80
|
+
eventKind: "message" as const,
|
|
81
|
+
};
|
|
82
|
+
return JSON.stringify({ ...base, ...patch });
|
|
83
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { describe, expect, test } from "bun:test";
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
+
collectFdUsage,
|
|
10
11
|
createFdPressureState,
|
|
11
12
|
evaluateFdPressure,
|
|
12
13
|
parseOpenFileLimits,
|
|
@@ -68,6 +69,37 @@ describe("parseOpenFileLimits", () => {
|
|
|
68
69
|
});
|
|
69
70
|
});
|
|
70
71
|
|
|
72
|
+
describe("collectFdUsage", () => {
|
|
73
|
+
test("uses Win32 handle counts on Windows", () => {
|
|
74
|
+
expect(
|
|
75
|
+
collectFdUsage({
|
|
76
|
+
platform: "win32",
|
|
77
|
+
processTable: [
|
|
78
|
+
{ pid: 10, ppid: 4, command: "low.exe", handleCount: 5 },
|
|
79
|
+
{ pid: 11, ppid: 4, command: "high.exe", handleCount: 20 },
|
|
80
|
+
],
|
|
81
|
+
}),
|
|
82
|
+
).toEqual([
|
|
83
|
+
{
|
|
84
|
+
pid: 11,
|
|
85
|
+
command: "high.exe",
|
|
86
|
+
openCount: 20,
|
|
87
|
+
softLimit: null,
|
|
88
|
+
hardLimit: null,
|
|
89
|
+
ratio: null,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
pid: 10,
|
|
93
|
+
command: "low.exe",
|
|
94
|
+
openCount: 5,
|
|
95
|
+
softLimit: null,
|
|
96
|
+
hardLimit: null,
|
|
97
|
+
ratio: null,
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
71
103
|
describe("evaluateFdPressure", () => {
|
|
72
104
|
const options = { thresholdRatio: 0.8, warnCooldownMs: 60_000 };
|
|
73
105
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
effectiveSizeBytes,
|
|
5
|
+
parseSmapsRollup,
|
|
6
|
+
topProcessesByMemory,
|
|
7
|
+
} from "../process-memory.js";
|
|
4
8
|
|
|
5
9
|
const SMAPS_ROLLUP = `555c98516000-7ffe248ca000 ---p 00000000 00:00 0 [rollup]
|
|
6
10
|
Rss: 524288 kB
|
|
@@ -65,3 +69,32 @@ describe("effectiveSizeBytes", () => {
|
|
|
65
69
|
).toBe(0);
|
|
66
70
|
});
|
|
67
71
|
});
|
|
72
|
+
|
|
73
|
+
describe("topProcessesByMemory", () => {
|
|
74
|
+
test("uses Win32 working-set data on Windows", () => {
|
|
75
|
+
const result = topProcessesByMemory(1, {
|
|
76
|
+
platform: "win32",
|
|
77
|
+
processTable: [
|
|
78
|
+
{ pid: 10, ppid: 4, command: "helper.exe", rssBytes: 1024 },
|
|
79
|
+
{
|
|
80
|
+
pid: 11,
|
|
81
|
+
ppid: 4,
|
|
82
|
+
command: '"C:\\Program Files\\Bun\\bun.exe" worker.ts',
|
|
83
|
+
rssBytes: 4096,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(result).toEqual([
|
|
89
|
+
{
|
|
90
|
+
pid: 11,
|
|
91
|
+
command: "worker",
|
|
92
|
+
rssBytes: 4096,
|
|
93
|
+
pssBytes: null,
|
|
94
|
+
pssAnonBytes: null,
|
|
95
|
+
pssFileBytes: null,
|
|
96
|
+
pssShmemBytes: null,
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -22,7 +22,12 @@ import { readdirSync, readFileSync } from "node:fs";
|
|
|
22
22
|
|
|
23
23
|
import type { MonitoringConfig } from "../config/schemas/monitoring.js";
|
|
24
24
|
import { getLogger } from "../util/logger.js";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
getProcessTableRows,
|
|
27
|
+
type ProcessTableOptions,
|
|
28
|
+
type ProcessTableRow,
|
|
29
|
+
} from "../util/process-table.js";
|
|
30
|
+
import { deriveName, readProcessCommand } from "../util/process-tree.js";
|
|
26
31
|
|
|
27
32
|
const log = getLogger("file-descriptors");
|
|
28
33
|
|
|
@@ -40,7 +45,7 @@ export interface ProcessFdUsage {
|
|
|
40
45
|
pid: number;
|
|
41
46
|
/** Redacted process descriptor (see {@link readProcessCommand}). */
|
|
42
47
|
command: string;
|
|
43
|
-
/**
|
|
48
|
+
/** Open descriptor count, or handle count on Windows. */
|
|
44
49
|
openCount: number;
|
|
45
50
|
softLimit: number | null;
|
|
46
51
|
hardLimit: number | null;
|
|
@@ -108,9 +113,34 @@ function readProcessFdUsage(pid: number): ProcessFdUsage | null {
|
|
|
108
113
|
/**
|
|
109
114
|
* Descriptor usage for every readable process, closest to its soft limit first
|
|
110
115
|
* (processes with an unknown limit sort last, by raw count). Empty when
|
|
111
|
-
* `/proc` is unavailable
|
|
116
|
+
* `/proc` is unavailable. Windows reports process handle counts without a
|
|
117
|
+
* comparable soft limit.
|
|
112
118
|
*/
|
|
113
|
-
export function collectFdUsage(
|
|
119
|
+
export function collectFdUsage(
|
|
120
|
+
options: ProcessTableOptions = {},
|
|
121
|
+
): ProcessFdUsage[] {
|
|
122
|
+
const platform = options.platform ?? process.platform;
|
|
123
|
+
if (platform === "win32") {
|
|
124
|
+
try {
|
|
125
|
+
return getProcessTableRows(options)
|
|
126
|
+
.filter(
|
|
127
|
+
(row): row is ProcessTableRow & { handleCount: number } =>
|
|
128
|
+
row.handleCount != null,
|
|
129
|
+
)
|
|
130
|
+
.map((row) => ({
|
|
131
|
+
pid: row.pid,
|
|
132
|
+
command: deriveName(row.command),
|
|
133
|
+
openCount: row.handleCount,
|
|
134
|
+
softLimit: null,
|
|
135
|
+
hardLimit: null,
|
|
136
|
+
ratio: null,
|
|
137
|
+
}))
|
|
138
|
+
.sort((a, b) => b.openCount - a.openCount);
|
|
139
|
+
} catch {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
114
144
|
let entries: string[];
|
|
115
145
|
try {
|
|
116
146
|
entries = readdirSync("/proc").filter((entry) => /^\d+$/.test(entry));
|
|
@@ -133,8 +163,11 @@ export function collectFdUsage(): ProcessFdUsage[] {
|
|
|
133
163
|
}
|
|
134
164
|
|
|
135
165
|
/** The top `limit` processes by descriptor pressure, most pressured first. */
|
|
136
|
-
export function topProcessesByFd(
|
|
137
|
-
|
|
166
|
+
export function topProcessesByFd(
|
|
167
|
+
limit: number,
|
|
168
|
+
options: ProcessTableOptions = {},
|
|
169
|
+
): ProcessFdUsage[] {
|
|
170
|
+
return collectFdUsage(options).slice(0, limit);
|
|
138
171
|
}
|
|
139
172
|
|
|
140
173
|
/** What a pass concluded about the container's descriptor pressure. */
|
|
@@ -11,7 +11,12 @@
|
|
|
11
11
|
|
|
12
12
|
import { readdirSync, readFileSync } from "node:fs";
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
getProcessTableRows,
|
|
16
|
+
type ProcessTableOptions,
|
|
17
|
+
type ProcessTableRow,
|
|
18
|
+
} from "../util/process-table.js";
|
|
19
|
+
import { deriveName, readProcessCommand } from "../util/process-tree.js";
|
|
15
20
|
|
|
16
21
|
/** Linux page size assumed when converting `/proc/<pid>/statm` pages to bytes. */
|
|
17
22
|
const PAGE_SIZE_BYTES = 4096;
|
|
@@ -82,11 +87,38 @@ function readProcessMemory(pid: number): SmapsRollup | null {
|
|
|
82
87
|
|
|
83
88
|
/**
|
|
84
89
|
* Best-effort snapshot of the top `limit` processes by PSS (RSS when PSS is
|
|
85
|
-
* unavailable), largest first.
|
|
86
|
-
*
|
|
87
|
-
*
|
|
90
|
+
* unavailable), largest first. Windows uses Win32 working-set sizes. Other
|
|
91
|
+
* hosts without `/proc` return an empty list while the process tree still
|
|
92
|
+
* captures what was running.
|
|
88
93
|
*/
|
|
89
|
-
export function topProcessesByMemory(
|
|
94
|
+
export function topProcessesByMemory(
|
|
95
|
+
limit: number,
|
|
96
|
+
options: ProcessTableOptions = {},
|
|
97
|
+
): ProcessMemory[] {
|
|
98
|
+
const platform = options.platform ?? process.platform;
|
|
99
|
+
if (platform === "win32") {
|
|
100
|
+
try {
|
|
101
|
+
return getProcessTableRows(options)
|
|
102
|
+
.filter(
|
|
103
|
+
(row): row is ProcessTableRow & { rssBytes: number } =>
|
|
104
|
+
row.rssBytes != null,
|
|
105
|
+
)
|
|
106
|
+
.map((row) => ({
|
|
107
|
+
pid: row.pid,
|
|
108
|
+
command: deriveName(row.command),
|
|
109
|
+
rssBytes: row.rssBytes,
|
|
110
|
+
pssBytes: null,
|
|
111
|
+
pssAnonBytes: null,
|
|
112
|
+
pssFileBytes: null,
|
|
113
|
+
pssShmemBytes: null,
|
|
114
|
+
}))
|
|
115
|
+
.sort((a, b) => b.rssBytes - a.rssBytes)
|
|
116
|
+
.slice(0, limit);
|
|
117
|
+
} catch {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
90
122
|
let pids: string[];
|
|
91
123
|
try {
|
|
92
124
|
pids = readdirSync("/proc").filter((e) => /^\d+$/.test(e));
|
|
@@ -34,7 +34,15 @@ import { diffCounters } from "../util/counter-diff.js";
|
|
|
34
34
|
import { getDiskUsageInfo } from "../util/disk-usage.js";
|
|
35
35
|
import { getLogger } from "../util/logger.js";
|
|
36
36
|
import { getMonitoringDataDir } from "../util/platform.js";
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
listProcessTableAsync,
|
|
39
|
+
type ProcessTableRow,
|
|
40
|
+
} from "../util/process-table.js";
|
|
41
|
+
import {
|
|
42
|
+
buildProcessTree,
|
|
43
|
+
buildSystemProcessTree,
|
|
44
|
+
listProcesses,
|
|
45
|
+
} from "../util/process-tree.js";
|
|
38
46
|
import { readActiveConversations } from "./active-conversations.js";
|
|
39
47
|
import { topProcessesByFd } from "./file-descriptors.js";
|
|
40
48
|
import { getTrackedDataFiles, readFileResidency } from "./page-cache.js";
|
|
@@ -149,10 +157,16 @@ export async function writeSnapshot(
|
|
|
149
157
|
mkdirSync(snapshotsDir, { recursive: true });
|
|
150
158
|
|
|
151
159
|
let tree: unknown = null;
|
|
160
|
+
let processTable: ProcessTableRow[] | null = null;
|
|
152
161
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
processTable = await listProcessTableAsync();
|
|
163
|
+
const procs = await listProcesses(processTable);
|
|
164
|
+
// pid 1 is the container init; its subtree is the whole container. Windows
|
|
165
|
+
// has no such root, so every process hangs off a synthetic one.
|
|
166
|
+
tree =
|
|
167
|
+
process.platform === "win32"
|
|
168
|
+
? buildSystemProcessTree(procs)
|
|
169
|
+
: buildProcessTree(procs, 1);
|
|
156
170
|
} catch (err) {
|
|
157
171
|
log.warn({ err }, "Failed to enumerate process tree for snapshot");
|
|
158
172
|
}
|
|
@@ -166,21 +180,24 @@ export async function writeSnapshot(
|
|
|
166
180
|
log.warn({ err }, "Failed to read page-cache residency for snapshot");
|
|
167
181
|
}
|
|
168
182
|
|
|
183
|
+
const processOptions = { platform: process.platform, processTable };
|
|
169
184
|
const snapshot = {
|
|
170
185
|
ts: sample.ts,
|
|
171
186
|
kind,
|
|
172
187
|
sample,
|
|
173
188
|
fileResidency,
|
|
174
189
|
// PSS-ranked with per-process anon/file split; PSS sums reconcile against
|
|
175
|
-
// the cgroup total where an RSS sum double-counts shared pages.
|
|
176
|
-
|
|
190
|
+
// the cgroup total where an RSS sum double-counts shared pages. Windows
|
|
191
|
+
// reports working-set size instead.
|
|
192
|
+
topProcesses: topProcessesByMemory(15, processOptions),
|
|
177
193
|
// Slab memory belongs to no process; without this, cgroup usage that
|
|
178
194
|
// exceeds the per-process sum has no visible owner.
|
|
179
195
|
topSlabCaches: topSlabCaches(10),
|
|
180
196
|
// Descriptor pressure, ranked against each process's own soft limit. An
|
|
181
197
|
// EMFILE storm and a memory spike look alike from the outside (unrelated
|
|
182
|
-
// failures across the daemon), so the capture records both.
|
|
183
|
-
|
|
198
|
+
// failures across the daemon), so the capture records both. Windows
|
|
199
|
+
// records raw handle counts since it has no comparable soft limit.
|
|
200
|
+
topProcessesByFd: topProcessesByFd(15, processOptions),
|
|
184
201
|
processTree: tree,
|
|
185
202
|
};
|
|
186
203
|
|