@vellumai/assistant 0.11.4 → 0.11.5-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/AGENTS.md +2 -2
- package/ARCHITECTURE.md +29 -4
- package/README.md +1 -1
- package/docs/architecture/memory.md +17 -5
- package/docs/architecture/security.md +96 -152
- package/docs/guardian-request-flow.md +13 -2
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +29 -31
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +193 -3
- package/package.json +1 -1
- package/src/__tests__/anthropic-provider.test.ts +10 -3
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +4 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
- package/src/__tests__/channel-reply-delivery.test.ts +26 -13
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
- package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
- package/src/__tests__/config-loader-backfill.test.ts +19 -10
- package/src/__tests__/container-cpu-sampler.test.ts +153 -0
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +17 -0
- package/src/__tests__/conversation-lifecycle.test.ts +20 -26
- package/src/__tests__/conversation-pairing.test.ts +186 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
- package/src/__tests__/credential-broker-server-use.test.ts +24 -5
- package/src/__tests__/credential-routes.test.ts +22 -3
- package/src/__tests__/credential-security-invariants.test.ts +2 -1
- package/src/__tests__/daemon-credential-client.test.ts +88 -0
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/dm-backfill.test.ts +63 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -4
- package/src/__tests__/events-client-registration.test.ts +28 -0
- package/src/__tests__/file-write-tool.test.ts +4 -2
- package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/host-proxy-interface.test.ts +11 -1
- package/src/__tests__/host-shell-tool.test.ts +23 -4
- package/src/__tests__/image-conversion.test.ts +143 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/input-repairs.test.ts +207 -0
- package/src/__tests__/live-workspace-guard.test.ts +75 -0
- package/src/__tests__/managed-profile-guard.test.ts +4 -2
- package/src/__tests__/mcp-abort-signal.test.ts +1 -1
- package/src/__tests__/mcp-client-auth.test.ts +1 -1
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
- package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
- package/src/__tests__/mtime-cache.test.ts +2 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
- package/src/__tests__/openai-provider.test.ts +22 -0
- package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/platform.test.ts +16 -1
- package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
- package/src/__tests__/plugin-api-shim.test.ts +5 -0
- package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
- package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/resource-pressure-guard.test.ts +428 -0
- package/src/__tests__/resource-pressure-routes.test.ts +113 -0
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
- package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
- package/src/__tests__/skill-tool-factory.test.ts +161 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +63 -38
- package/src/__tests__/tool-policy.test.ts +97 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
- package/src/__tests__/user-plugin-loader.test.ts +2 -0
- package/src/__tests__/validate-input.test.ts +243 -7
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -0
- package/src/__tests__/voice-session-bridge.test.ts +42 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
- package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
- package/src/acp/acp-claude-oauth.ts +38 -21
- package/src/acp/prepare-agent-env.ts +146 -55
- package/src/agent/loop-tool-dedup.test.ts +161 -0
- package/src/agent/loop.ts +68 -22
- package/src/api/constants/app-tools.ts +34 -0
- package/src/api/events/resource-pressure-status-changed.ts +53 -0
- package/src/api/index.ts +19 -0
- package/src/api/responses/resource-pressure-status.ts +25 -0
- package/src/approvals/guardian-channel-delivery.ts +70 -6
- package/src/approvals/guardian-request-resolvers.ts +32 -66
- package/src/calls/__tests__/voice-session-bridge.test.ts +114 -0
- package/src/calls/voice-session-bridge.ts +60 -6
- package/src/channels/__tests__/message-audience.test.ts +49 -0
- package/src/channels/__tests__/types.test.ts +17 -3
- package/src/channels/message-audience.ts +40 -0
- package/src/channels/types.ts +16 -12
- package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
- package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
- package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/__tests__/inference-providers.test.ts +68 -0
- package/src/cli/commands/__tests__/keys.test.ts +89 -8
- package/src/cli/commands/__tests__/plugins.test.ts +57 -1
- package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
- package/src/cli/commands/channel-verification-sessions.ts +11 -21
- package/src/cli/commands/conversations.help.ts +34 -0
- package/src/cli/commands/conversations.ts +125 -0
- package/src/cli/commands/inference-providers.ts +9 -4
- package/src/cli/commands/inference.help.ts +9 -4
- package/src/cli/commands/keys.help.ts +13 -1
- package/src/cli/commands/keys.ts +26 -15
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/index.help.ts +41 -27
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/monitoring.ts +1 -0
- package/src/cli/commands/plugins.help.ts +28 -31
- package/src/cli/commands/plugins.ts +10 -0
- package/src/cli/commands/skills.help.ts +13 -16
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
- package/src/cli/lib/bundled-marketplace.json +13 -0
- package/src/cli/lib/daemon-credential-client.ts +25 -3
- package/src/cli/lib/install-from-github.ts +35 -24
- package/src/cli/lib/merge-plugin-tree.ts +22 -4
- package/src/cli/lib/plugin-surfaces.ts +27 -0
- package/src/config/__tests__/balanced-model-experiment.test.ts +7 -7
- package/src/config/__tests__/default-profile-catalog.test.ts +3 -3
- package/src/config/default-profile-catalog.ts +5 -6
- package/src/config/feature-flag-registry.json +32 -32
- package/src/config/webhook-routing.ts +8 -0
- package/src/context/compactor.ts +31 -2
- package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
- package/src/daemon/conversation-error.ts +13 -0
- package/src/daemon/conversation-messaging.ts +22 -2
- package/src/daemon/conversation-process.ts +11 -0
- package/src/daemon/conversation-store.ts +10 -0
- package/src/daemon/conversation-surfaces.ts +21 -8
- package/src/daemon/conversation.ts +12 -10
- package/src/daemon/lifecycle.ts +6 -2
- package/src/daemon/message-types/conversations.ts +5 -7
- package/src/daemon/provider-rejection-log-fields.ts +124 -0
- package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
- package/src/daemon/resource-pressure-guard.ts +387 -0
- package/src/daemon/shutdown-handlers.ts +2 -0
- package/src/daemon/startup-error.ts +16 -0
- package/src/daemon/trust-context.ts +14 -10
- package/src/daemon/unsendable-image-notice.ts +76 -0
- package/src/hooks/registry.ts +65 -51
- package/src/ipc/__tests__/socket-path.test.ts +15 -7
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/socket-cleanup.ts +2 -11
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
- package/src/messaging/provider-message-metadata.ts +128 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +116 -68
- package/src/messaging/providers/channel-transport.ts +90 -13
- package/src/messaging/providers/discord/send.ts +16 -0
- package/src/messaging/providers/discord/transport.ts +11 -1
- package/src/messaging/providers/index.ts +109 -16
- package/src/messaging/providers/slack/message-metadata.ts +45 -0
- package/src/messaging/providers/slack/render-transcript.ts +1 -4
- package/src/messaging/providers/slack/send.test.ts +6 -9
- package/src/messaging/providers/slack/send.ts +40 -28
- package/src/messaging/providers/slack/transport.ts +22 -26
- package/src/messaging/providers/telegram-bot/send.test.ts +2 -8
- package/src/messaging/providers/telegram-bot/transport.ts +3 -6
- package/src/messaging/read-provider-metadata.test.ts +157 -0
- package/src/messaging/read-provider-metadata.ts +51 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
- package/src/notifications/__tests__/edit-notification.test.ts +174 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
- package/src/notifications/adapters/macos.ts +55 -0
- package/src/notifications/adapters/slack.ts +10 -5
- package/src/notifications/assistant-reply-producer.ts +37 -4
- package/src/notifications/conversation-pairing.ts +111 -21
- package/src/notifications/edit-notification.ts +38 -8
- package/src/notifications/emit-signal.ts +12 -14
- package/src/notifications/home-feed-side-effect.ts +218 -15
- package/src/notifications/types.ts +5 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +28 -3
- package/src/permissions/confirmation-guardian-request.ts +5 -1
- package/src/persistence/conversation-crud.ts +23 -20
- package/src/persistence/conversation-queries.ts +14 -1
- package/src/persistence/conversation-types.ts +22 -0
- package/src/persistence/delivery-crud.ts +122 -15
- package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
- package/src/plugin-api/conversation-turn.ts +27 -0
- package/src/plugin-api/credential-scope.test.ts +15 -0
- package/src/plugin-api/credential-scope.ts +14 -0
- package/src/plugin-api/index.ts +19 -1
- package/src/plugin-api/resolve-credential.ts +15 -10
- package/src/plugin-api/store-credential.ts +148 -0
- package/src/plugin-api/system-card.ts +36 -0
- package/src/plugin-api/vision-support.test.ts +82 -0
- package/src/plugin-api/vision-support.ts +14 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
- package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
- package/src/plugins/defaults/image-recovery/detect.ts +24 -1
- package/src/plugins/defaults/main.ts +15 -0
- package/src/plugins/defaults/memory/AGENTS.md +11 -3
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/mtime-cache.ts +1 -1
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/plugin-tree-walk.ts +63 -4
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/connection-resolution.ts +2 -1
- package/src/providers/content-blocks.ts +22 -0
- package/src/providers/gemini/client.ts +5 -2
- package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
- package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
- package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
- package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
- package/src/providers/inference/adapter-factory.ts +33 -2
- package/src/providers/inference/auth.ts +13 -2
- package/src/providers/inference/credential-usage.ts +37 -0
- package/src/providers/inference/missing-credential-guard.ts +110 -0
- package/src/providers/inference/resolve-auth.ts +7 -7
- package/src/providers/media-resolve.ts +176 -15
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/chat-completions-provider.ts +322 -42
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +31 -10
- package/src/runtime/AGENTS.md +35 -0
- package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
- package/src/runtime/__tests__/web-presence.test.ts +234 -0
- package/src/runtime/assistant-event-hub.ts +38 -0
- package/src/runtime/channel-reply-delivery.ts +54 -31
- package/src/runtime/channel-retry-sweep.ts +6 -4
- package/src/runtime/effective-capabilities.test.ts +19 -0
- package/src/runtime/effective-capabilities.ts +25 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/http-errors.ts +1 -0
- package/src/runtime/http-server.ts +20 -5
- package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
- package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +52 -1
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +88 -35
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
- package/src/runtime/routes/approval-routes.ts +37 -4
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
- package/src/runtime/routes/canned-message-complete.ts +68 -29
- package/src/runtime/routes/client-routes.ts +113 -0
- package/src/runtime/routes/conversation-list-routes.ts +22 -7
- package/src/runtime/routes/conversation-management-routes.ts +15 -9
- package/src/runtime/routes/conversation-routes.ts +36 -22
- package/src/runtime/routes/credential-in-use.ts +85 -0
- package/src/runtime/routes/credential-routes.ts +55 -85
- package/src/runtime/routes/guardian-approval-interception.ts +22 -22
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
- package/src/runtime/routes/identity-routes.ts +5 -121
- package/src/runtime/routes/inbound-message-handler.ts +71 -27
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +77 -50
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -97
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +9 -7
- package/src/runtime/routes/monitoring-routes.ts +9 -1
- package/src/runtime/routes/plugins-routes.ts +17 -5
- package/src/runtime/routes/resource-pressure-routes.ts +22 -0
- package/src/runtime/routes/secret-routes.ts +39 -8
- package/src/runtime/routes/settings-routes.ts +6 -6
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/user-route-dispatcher.ts +31 -2
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/slack-reply-session.test.ts +23 -13
- package/src/runtime/slack-reply-session.ts +38 -55
- package/src/runtime/web-presence.ts +90 -0
- package/src/skills/validate-input.ts +177 -40
- package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
- package/src/subagent/consult-context.ts +7 -23
- package/src/tools/credentials/broker.ts +24 -79
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/credentials/tool-policy.ts +67 -0
- package/src/tools/executor.ts +53 -15
- package/src/tools/network/__tests__/web-search.test.ts +41 -1
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/network/web-search.ts +21 -3
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/schema-transforms.ts +40 -5
- package/src/tools/shared/input-repairs.ts +160 -0
- package/src/tools/skills/skill-tool-factory.ts +18 -4
- package/src/tools/subagent/spawn.ts +0 -1
- package/src/tools/tool-approval-handler.ts +16 -10
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/util/__tests__/cgroup-memory.test.ts +3 -0
- package/src/util/cgroup-memory.ts +3 -0
- package/src/util/container-cpu-sampler.ts +250 -0
- package/src/util/image-conversion.ts +178 -14
- package/src/util/platform.ts +93 -10
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app tools that mutate the stored app set, and what each one leaves
|
|
3
|
+
* behind. Mirrors `document-tools.ts`: a single declaration both the daemon and
|
|
4
|
+
* the web client derive their tool sets from, so a new mutating app tool is
|
|
5
|
+
* added here once and every consumer picks it up.
|
|
6
|
+
*
|
|
7
|
+
* Tool names are the ones the runner sees. Calls arriving through the bundled
|
|
8
|
+
* `app-builder` skill reach the daemon already unwrapped to the inner tool
|
|
9
|
+
* name, and the web client unwraps the `skill_execute` envelope itself.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface AppMutationTool {
|
|
13
|
+
/** The executed tool name. */
|
|
14
|
+
readonly name: string;
|
|
15
|
+
/**
|
|
16
|
+
* Whether the app is still openable once the tool has run. A deleted app has
|
|
17
|
+
* nothing to open, so it cannot anchor a card back to itself.
|
|
18
|
+
*/
|
|
19
|
+
readonly reopenable: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const APP_MUTATION_TOOLS: readonly AppMutationTool[] = [
|
|
23
|
+
{ name: "app_create", reopenable: true },
|
|
24
|
+
{ name: "app_update", reopenable: true },
|
|
25
|
+
{ name: "app_delete", reopenable: false },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The mutating app tools that leave the app openable. The web transcript
|
|
30
|
+
* anchors its end-of-response app cards on these, so a delete never produces a
|
|
31
|
+
* card pointing at an app that is gone.
|
|
32
|
+
*/
|
|
33
|
+
export const APP_MUTATION_TOOL_NAMES: readonly string[] =
|
|
34
|
+
APP_MUTATION_TOOLS.filter((tool) => tool.reopenable).map((tool) => tool.name);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `resource_pressure_status_changed` SSE event.
|
|
3
|
+
*
|
|
4
|
+
* Global broadcast pushed when the daemon's resource-pressure snapshot
|
|
5
|
+
* changes. Mirrors the REST `GET /resource-pressure/status` shape so the
|
|
6
|
+
* client's monitor can apply stream updates and polled fetches through
|
|
7
|
+
* the same code path. Carries no `conversationId`: it is workspace-wide.
|
|
8
|
+
*
|
|
9
|
+
* Canonical wire-contract source. Daemon code imports the type
|
|
10
|
+
* directly from this file; external consumers import via
|
|
11
|
+
* `@vellumai/assistant-api`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
|
|
16
|
+
export const ResourcePressureStateSchema = z.enum([
|
|
17
|
+
"disabled",
|
|
18
|
+
"ok",
|
|
19
|
+
"elevated",
|
|
20
|
+
"unknown",
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export type ResourcePressureState = z.infer<typeof ResourcePressureStateSchema>;
|
|
24
|
+
|
|
25
|
+
export const ResourcePressureStatusSchema = z.object({
|
|
26
|
+
enabled: z.boolean(),
|
|
27
|
+
state: ResourcePressureStateSchema,
|
|
28
|
+
/** Latest CPU sample as a percent of the CPU allocation. */
|
|
29
|
+
cpuPercent: z.number().nullable(),
|
|
30
|
+
/** Working set as a percent of the memory limit. */
|
|
31
|
+
memoryPercent: z.number().nullable(),
|
|
32
|
+
/** Whether the CPU signal is holding the `elevated` state. */
|
|
33
|
+
cpuElevated: z.boolean(),
|
|
34
|
+
/** Whether the memory signal is holding the `elevated` state. */
|
|
35
|
+
memoryElevated: z.boolean(),
|
|
36
|
+
cpuThresholdPercent: z.number(),
|
|
37
|
+
memoryThresholdPercent: z.number(),
|
|
38
|
+
lastCheckedAt: z.string().nullable(),
|
|
39
|
+
error: z.string().nullable(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export type ResourcePressureStatus = z.infer<
|
|
43
|
+
typeof ResourcePressureStatusSchema
|
|
44
|
+
>;
|
|
45
|
+
|
|
46
|
+
export const ResourcePressureStatusChangedEventSchema = z.object({
|
|
47
|
+
type: z.literal("resource_pressure_status_changed"),
|
|
48
|
+
status: ResourcePressureStatusSchema,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export type ResourcePressureStatusChangedEvent = z.infer<
|
|
52
|
+
typeof ResourcePressureStatusChangedEventSchema
|
|
53
|
+
>;
|
package/src/api/index.ts
CHANGED
|
@@ -102,6 +102,7 @@ import {
|
|
|
102
102
|
RecordingStopEventSchema,
|
|
103
103
|
} from "./events/recording.js";
|
|
104
104
|
import { RelationshipStateUpdatedEventSchema } from "./events/relationship-state-updated.js";
|
|
105
|
+
import { ResourcePressureStatusChangedEventSchema } from "./events/resource-pressure-status-changed.js";
|
|
105
106
|
import { ScheduleConversationCreatedEventSchema } from "./events/schedule-conversation-created.js";
|
|
106
107
|
import { SecretRequestEventSchema } from "./events/secret-request.js";
|
|
107
108
|
import { ServiceGroupUpdateCompleteEventSchema } from "./events/service-group-update-complete.js";
|
|
@@ -134,6 +135,11 @@ import { WorkflowLeafStartedEventSchema } from "./events/workflow-leaf-started.j
|
|
|
134
135
|
import { WorkflowProgressEventSchema } from "./events/workflow-progress.js";
|
|
135
136
|
import { WorkflowStartedEventSchema } from "./events/workflow-started.js";
|
|
136
137
|
|
|
138
|
+
export {
|
|
139
|
+
APP_MUTATION_TOOL_NAMES,
|
|
140
|
+
APP_MUTATION_TOOLS,
|
|
141
|
+
type AppMutationTool,
|
|
142
|
+
} from "./constants/app-tools.js";
|
|
137
143
|
export {
|
|
138
144
|
CALL_SITE_COMPACTION_AGENT,
|
|
139
145
|
CALL_SITE_SYNTHETIC_AGENT_ERROR_MESSAGE,
|
|
@@ -536,6 +542,14 @@ export {
|
|
|
536
542
|
type RelationshipStateUpdatedEvent,
|
|
537
543
|
RelationshipStateUpdatedEventSchema,
|
|
538
544
|
} from "./events/relationship-state-updated.js";
|
|
545
|
+
export {
|
|
546
|
+
type ResourcePressureState,
|
|
547
|
+
ResourcePressureStateSchema,
|
|
548
|
+
type ResourcePressureStatus,
|
|
549
|
+
type ResourcePressureStatusChangedEvent,
|
|
550
|
+
ResourcePressureStatusChangedEventSchema,
|
|
551
|
+
ResourcePressureStatusSchema,
|
|
552
|
+
} from "./events/resource-pressure-status-changed.js";
|
|
539
553
|
export {
|
|
540
554
|
type ScheduleConversationCreatedEvent,
|
|
541
555
|
ScheduleConversationCreatedEventSchema,
|
|
@@ -802,6 +816,10 @@ export {
|
|
|
802
816
|
type MemoryV3SelectionRow,
|
|
803
817
|
MemoryV3SelectionRowSchema,
|
|
804
818
|
} from "./responses/memory-v3-selection-log.js";
|
|
819
|
+
export {
|
|
820
|
+
type ResourcePressureStatusResponse,
|
|
821
|
+
ResourcePressureStatusResponseSchema,
|
|
822
|
+
} from "./responses/resource-pressure-status.js";
|
|
805
823
|
export {
|
|
806
824
|
type SubagentDetailEvent,
|
|
807
825
|
SubagentDetailEventSchema,
|
|
@@ -987,6 +1005,7 @@ export const AssistantEventSchema = z.discriminatedUnion("type", [
|
|
|
987
1005
|
RecordingStartEventSchema,
|
|
988
1006
|
RecordingStopEventSchema,
|
|
989
1007
|
RelationshipStateUpdatedEventSchema,
|
|
1008
|
+
ResourcePressureStatusChangedEventSchema,
|
|
990
1009
|
ScheduleConversationCreatedEventSchema,
|
|
991
1010
|
SecretRequestEventSchema,
|
|
992
1011
|
ServiceGroupUpdateCompleteEventSchema,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire contract for the resource-pressure REST endpoint
|
|
3
|
+
* (`GET /resource-pressure/status`). Returns the current snapshot
|
|
4
|
+
* wrapped as `{ status }`.
|
|
5
|
+
*
|
|
6
|
+
* Reuses the canonical `ResourcePressureStatusSchema` defined alongside
|
|
7
|
+
* the `resource_pressure_status_changed` SSE event so the polled REST
|
|
8
|
+
* fetch and the streamed update share a single shape.
|
|
9
|
+
*
|
|
10
|
+
* Canonical wire-contract source. Assistant code imports the types
|
|
11
|
+
* directly from this file via relative paths; external consumers
|
|
12
|
+
* (web client, gateway, evals) import via `@vellumai/assistant-api`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
|
|
17
|
+
import { ResourcePressureStatusSchema } from "../events/resource-pressure-status-changed.js";
|
|
18
|
+
|
|
19
|
+
export const ResourcePressureStatusResponseSchema = z.object({
|
|
20
|
+
status: ResourcePressureStatusSchema,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export type ResourcePressureStatusResponse = z.infer<
|
|
24
|
+
typeof ResourcePressureStatusResponseSchema
|
|
25
|
+
>;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared addressing helpers for guardian
|
|
2
|
+
* Shared addressing helpers for guardian-flow channel notices.
|
|
3
3
|
*
|
|
4
|
-
* Requester notices (approval, denial, expiry)
|
|
5
|
-
*
|
|
6
|
-
* guardian-facing notification pipeline. Centralizing the
|
|
7
|
-
* keeps the decision resolvers
|
|
8
|
-
* apart on
|
|
4
|
+
* Requester notices (approval, denial, expiry) and the guardian's own approval
|
|
5
|
+
* prompt are delivered straight to a chat via `deliverChannelReply` -
|
|
6
|
+
* independent of the guardian-facing notification pipeline. Centralizing the
|
|
7
|
+
* addressing rules here keeps the decision resolvers, the timer-driven expiry
|
|
8
|
+
* sweep and the in-turn approval prompt from drifting apart on who a message
|
|
9
|
+
* is put in front of.
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -111,3 +112,66 @@ export function resolveRequesterDeliveryTarget(params: {
|
|
|
111
112
|
}
|
|
112
113
|
return requesterChatId;
|
|
113
114
|
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Reduce a reply callback to its channel route, dropping every query param.
|
|
118
|
+
*
|
|
119
|
+
* A callback addresses the turn it came from. The gateway hangs the turn's
|
|
120
|
+
* coordinates on it as params, and each transport reads a different one:
|
|
121
|
+
* Slack a `threadTs`, Telegram a forum topic `threadId`, Discord a `threadId`
|
|
122
|
+
* that replaces the destination outright rather than narrowing it. Carried
|
|
123
|
+
* onto a delivery aimed elsewhere they either fail the send or quietly win
|
|
124
|
+
* over the new address, and a redirect that leaves one behind is
|
|
125
|
+
* indistinguishable from no redirect at all.
|
|
126
|
+
*
|
|
127
|
+
* Everything is dropped rather than a named set, so a channel added later, or
|
|
128
|
+
* a param a transport starts reading later, cannot silently escape this. The
|
|
129
|
+
* channel itself survives because it is the path, which is also what
|
|
130
|
+
* `isDirectDelivery` resolves a transport from, and nothing the gateway hangs
|
|
131
|
+
* on a deliver callback is needed to authorize or route the send.
|
|
132
|
+
*
|
|
133
|
+
* Relative or malformed URLs are returned as-is; they carry no params.
|
|
134
|
+
*/
|
|
135
|
+
export function stripTurnDestination(replyCallbackUrl: string): string {
|
|
136
|
+
try {
|
|
137
|
+
const url = new URL(replyCallbackUrl);
|
|
138
|
+
url.search = "";
|
|
139
|
+
return url.toString();
|
|
140
|
+
} catch {
|
|
141
|
+
return replyCallbackUrl;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Resolve where a guardian's own approval prompt is delivered.
|
|
147
|
+
*
|
|
148
|
+
* The prompt is raised by a turn the guardian is having, and that turn can be
|
|
149
|
+
* running in a room: a shared Slack channel, a Telegram group. The card
|
|
150
|
+
* carries the tool name and a preview of the command, so delivering it where
|
|
151
|
+
* the turn is shows both to everyone there.
|
|
152
|
+
*
|
|
153
|
+
* It goes to the guardian's own bound chat instead, the address they
|
|
154
|
+
* nominated when they verified and the one the notification pipeline already
|
|
155
|
+
* sends guardian cards to. No channel is named in this rule and none can be
|
|
156
|
+
* forgotten by it: a bound chat is the guardian's by definition, on any
|
|
157
|
+
* channel, including ones not built yet.
|
|
158
|
+
*
|
|
159
|
+
* A bound chat equal to the turn's chat means the turn is already there, so
|
|
160
|
+
* nothing moves. That also covers a turn where no binding resolved, since the
|
|
161
|
+
* trust context falls back to the turn's own chat, leaving delivery exactly as
|
|
162
|
+
* it is rather than dropping it.
|
|
163
|
+
*/
|
|
164
|
+
export function resolveGuardianPromptDelivery(params: {
|
|
165
|
+
turnChatId: string;
|
|
166
|
+
turnCallbackUrl: string;
|
|
167
|
+
guardianChatId: string | undefined;
|
|
168
|
+
}): { chatId: string; callbackUrl: string } {
|
|
169
|
+
const { turnChatId, turnCallbackUrl, guardianChatId } = params;
|
|
170
|
+
if (!guardianChatId || guardianChatId === turnChatId) {
|
|
171
|
+
return { chatId: turnChatId, callbackUrl: turnCallbackUrl };
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
chatId: guardianChatId,
|
|
175
|
+
callbackUrl: stripTurnDestination(turnCallbackUrl),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -23,8 +23,10 @@ import type {
|
|
|
23
23
|
GuardianRequestWire,
|
|
24
24
|
} from "../channels/gateway-guardian-requests.js";
|
|
25
25
|
import { getGuardianRequestOrNull } from "../channels/gateway-guardian-requests.js";
|
|
26
|
+
import { audienceForReader } from "../channels/message-audience.js";
|
|
26
27
|
import { findContactChannel } from "../contacts/contact-store.js";
|
|
27
28
|
import { findConversation } from "../daemon/conversation-registry.js";
|
|
29
|
+
import { isSlackDmConversation } from "../messaging/providers/slack/message-metadata.js";
|
|
28
30
|
import { emitNotificationSignal } from "../notifications/emit-signal.js";
|
|
29
31
|
import { parseQuestionAnswerActionId } from "../notifications/guardian-question-mode.js";
|
|
30
32
|
import {
|
|
@@ -62,6 +64,7 @@ import {
|
|
|
62
64
|
channelDeliversToUserId,
|
|
63
65
|
resolveDeliverCallbackUrlForChannel,
|
|
64
66
|
resolveRequesterDeliveryTarget,
|
|
67
|
+
stripTurnDestination,
|
|
65
68
|
} from "./guardian-channel-delivery.js";
|
|
66
69
|
|
|
67
70
|
const log = getLogger("guardian-request-resolvers");
|
|
@@ -70,38 +73,6 @@ const log = getLogger("guardian-request-resolvers");
|
|
|
70
73
|
// Helpers
|
|
71
74
|
// ---------------------------------------------------------------------------
|
|
72
75
|
|
|
73
|
-
/**
|
|
74
|
-
* Determines whether a Slack delivery should use ephemeral mode.
|
|
75
|
-
*
|
|
76
|
-
* Ephemeral messages (`chat.postEphemeral`) require a real channel ID
|
|
77
|
-
* (starts with `C` for public/private channels, or `D` for DM conversations).
|
|
78
|
-
* When the chat ID is a user ID (starts with `U`), `chat.postEphemeral` fails
|
|
79
|
-
* with `channel_not_found`. In that case the message is already going to a DM
|
|
80
|
-
* opened by `chat.postMessage`, so ephemeral isn't needed.
|
|
81
|
-
*
|
|
82
|
-
* Returns `true` only when the source channel is Slack AND the chatId is a
|
|
83
|
-
* shared channel (starts with `C`), meaning other users could see the message.
|
|
84
|
-
*/
|
|
85
|
-
function shouldUseEphemeral(sourceChannel: string, chatId: string): boolean {
|
|
86
|
-
return sourceChannel === "slack" && chatId.startsWith("C");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Strip the `threadTs` query param from a reply callback URL. The param
|
|
91
|
-
* addresses the guardian's channel thread; reusing it for a DM delivery
|
|
92
|
-
* raises `thread_not_found`. Relative or malformed URLs are returned as-is —
|
|
93
|
-
* they carry no threadTs to strip.
|
|
94
|
-
*/
|
|
95
|
-
function stripThreadTsParam(replyCallbackUrl: string): string {
|
|
96
|
-
try {
|
|
97
|
-
const url = new URL(replyCallbackUrl);
|
|
98
|
-
url.searchParams.delete("threadTs");
|
|
99
|
-
return url.toString();
|
|
100
|
-
} catch {
|
|
101
|
-
return replyCallbackUrl;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
76
|
/**
|
|
106
77
|
* Deliver the verification code straight to the requester's DM so the
|
|
107
78
|
* guardian is never an out-of-band courier for the secret.
|
|
@@ -125,7 +96,7 @@ async function deliverVerificationCodeToRequester(params: {
|
|
|
125
96
|
verificationCode: string;
|
|
126
97
|
assistantId: string;
|
|
127
98
|
}): Promise<boolean> {
|
|
128
|
-
const callbackUrl =
|
|
99
|
+
const callbackUrl = stripTurnDestination(params.replyCallbackUrl);
|
|
129
100
|
|
|
130
101
|
try {
|
|
131
102
|
await deliverChannelReply(callbackUrl, {
|
|
@@ -194,13 +165,13 @@ function buildRequesterChannelNotice(params: {
|
|
|
194
165
|
text: params.text,
|
|
195
166
|
assistantId: params.assistantId,
|
|
196
167
|
};
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
168
|
+
payload.audience = audienceForReader(
|
|
169
|
+
params.channel,
|
|
170
|
+
|
|
171
|
+
params.requesterChatId,
|
|
172
|
+
|
|
173
|
+
params.requesterExternalUserId,
|
|
174
|
+
);
|
|
204
175
|
return payload;
|
|
205
176
|
}
|
|
206
177
|
|
|
@@ -1309,16 +1280,11 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
1309
1280
|
};
|
|
1310
1281
|
// On Slack shared channels, deliver the verification code as ephemeral
|
|
1311
1282
|
// so only the guardian sees the secret — not all channel members.
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
ctx.actor.actorExternalUserId
|
|
1318
|
-
) {
|
|
1319
|
-
codePayload.ephemeral = true;
|
|
1320
|
-
codePayload.user = ctx.actor.actorExternalUserId;
|
|
1321
|
-
}
|
|
1283
|
+
codePayload.audience = audienceForReader(
|
|
1284
|
+
ctx.actor.channel,
|
|
1285
|
+
guardianInBandContext.guardianChatId,
|
|
1286
|
+
ctx.actor.actorExternalUserId,
|
|
1287
|
+
);
|
|
1322
1288
|
await deliverChannelReply(
|
|
1323
1289
|
guardianInBandContext.replyCallbackUrl,
|
|
1324
1290
|
codePayload,
|
|
@@ -1339,9 +1305,9 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
1339
1305
|
codeDelivered &&
|
|
1340
1306
|
ctx.actor.channel === "slack" &&
|
|
1341
1307
|
guardianUserId &&
|
|
1342
|
-
!guardianInBandContext.guardianChatId
|
|
1308
|
+
!isSlackDmConversation(guardianInBandContext.guardianChatId)
|
|
1343
1309
|
) {
|
|
1344
|
-
const dmCallbackUrl =
|
|
1310
|
+
const dmCallbackUrl = stripTurnDestination(
|
|
1345
1311
|
guardianInBandContext.replyCallbackUrl,
|
|
1346
1312
|
);
|
|
1347
1313
|
|
|
@@ -1543,13 +1509,13 @@ const toolGrantRequestResolver: GuardianRequestResolver = {
|
|
|
1543
1509
|
text: `Your request to use "${request.toolName}" has been denied by the guardian.`,
|
|
1544
1510
|
assistantId,
|
|
1545
1511
|
};
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1512
|
+
grantDenialPayload.audience = audienceForReader(
|
|
1513
|
+
request.sourceChannel,
|
|
1514
|
+
|
|
1515
|
+
requesterChatId,
|
|
1516
|
+
|
|
1517
|
+
request.requesterExternalUserId,
|
|
1518
|
+
);
|
|
1553
1519
|
await deliverChannelReply(
|
|
1554
1520
|
channelDeliveryContext.replyCallbackUrl,
|
|
1555
1521
|
grantDenialPayload,
|
|
@@ -1642,13 +1608,13 @@ const toolGrantRequestResolver: GuardianRequestResolver = {
|
|
|
1642
1608
|
text: `Your request to use "${request.toolName}" has been approved. Please retry your request.`,
|
|
1643
1609
|
assistantId,
|
|
1644
1610
|
};
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1611
|
+
grantApprovalPayload.audience = audienceForReader(
|
|
1612
|
+
request.sourceChannel,
|
|
1613
|
+
|
|
1614
|
+
requesterChatId,
|
|
1615
|
+
|
|
1616
|
+
request.requesterExternalUserId,
|
|
1617
|
+
);
|
|
1652
1618
|
await deliverChannelReply(
|
|
1653
1619
|
channelDeliveryContext.replyCallbackUrl,
|
|
1654
1620
|
grantApprovalPayload,
|
|
@@ -31,6 +31,14 @@ mock.module("../../daemon/conversation-store.js", () => ({
|
|
|
31
31
|
getOrCreateConversation: async () => fakeConversation,
|
|
32
32
|
}));
|
|
33
33
|
|
|
34
|
+
// Vision capability of the image pin's target profile. Install-dependent in
|
|
35
|
+
// production (a BYO provider resolves the profile key through its own column
|
|
36
|
+
// of the intent matrix), so it is scripted rather than read from a catalog.
|
|
37
|
+
let pinProfileSupportsVision = true;
|
|
38
|
+
mock.module("../../plugin-api/vision-support.js", () => ({
|
|
39
|
+
doesSupportVision: () => pinProfileSupportsVision,
|
|
40
|
+
}));
|
|
41
|
+
|
|
34
42
|
// Conversation-CRUD doubles for the teardown transcript-hygiene pass. The
|
|
35
43
|
// real module is spread so every other export keeps its production behavior;
|
|
36
44
|
// only the functions the hygiene pass (and discard) touch are recorded.
|
|
@@ -133,6 +141,7 @@ interface FakeConversation {
|
|
|
133
141
|
opts?: { decisionContext?: string },
|
|
134
142
|
) => void;
|
|
135
143
|
runAgentLoop: (...args: unknown[]) => Promise<void>;
|
|
144
|
+
getMessages: () => Array<{ role: string; content: unknown[] }>;
|
|
136
145
|
abort: (reason?: unknown) => void;
|
|
137
146
|
loadFromDb: () => Promise<void>;
|
|
138
147
|
toolsDisabledDepth: number;
|
|
@@ -149,6 +158,8 @@ function makeFakeConversation(opts: {
|
|
|
149
158
|
onPersist?: (attempt: number) => void;
|
|
150
159
|
/** Workspace root; pass empty to model a missing boundary. */
|
|
151
160
|
workingDir?: string;
|
|
161
|
+
/** In-memory history the profile pin reads; undefined models a text-only call. */
|
|
162
|
+
messages?: Array<{ role: string; content: unknown[] }>;
|
|
152
163
|
}) {
|
|
153
164
|
const waitForIdleCalls: WaitForIdleCall[] = [];
|
|
154
165
|
const confirmationDecisions: Array<{ requestId: string; decision: string }> =
|
|
@@ -206,6 +217,7 @@ function makeFakeConversation(opts: {
|
|
|
206
217
|
confirmationDecisions.push({ requestId, decision });
|
|
207
218
|
},
|
|
208
219
|
runAgentLoop: () => (opts.runAgentLoop ?? (async () => {}))(),
|
|
220
|
+
getMessages: () => opts.messages ?? [],
|
|
209
221
|
abort: () => {},
|
|
210
222
|
loadFromDb: async () => {
|
|
211
223
|
opts.events?.push("loadFromDb");
|
|
@@ -2165,3 +2177,105 @@ describe("transcript hygiene (teardown pass)", () => {
|
|
|
2165
2177
|
expect(crudLog.deletes).toContain("assistant-row-1");
|
|
2166
2178
|
});
|
|
2167
2179
|
});
|
|
2180
|
+
|
|
2181
|
+
describe("startVoiceTurn image-bearing profile pin", () => {
|
|
2182
|
+
/** A persisted user message carrying a photo taken mid-call. */
|
|
2183
|
+
const PHOTO_HISTORY = [
|
|
2184
|
+
{ role: "user", content: [{ type: "text", text: "here's a photo:" }] },
|
|
2185
|
+
{
|
|
2186
|
+
role: "user",
|
|
2187
|
+
content: [
|
|
2188
|
+
{ type: "text", text: "here's a photo:" },
|
|
2189
|
+
{ type: "image", source: { type: "base64", data: "abc" } },
|
|
2190
|
+
],
|
|
2191
|
+
},
|
|
2192
|
+
];
|
|
2193
|
+
|
|
2194
|
+
beforeEach(() => {
|
|
2195
|
+
pinProfileSupportsVision = true;
|
|
2196
|
+
});
|
|
2197
|
+
|
|
2198
|
+
async function runOptionsFor(opts: {
|
|
2199
|
+
messages?: Array<{ role: string; content: unknown[] }>;
|
|
2200
|
+
turn?: Record<string, unknown>;
|
|
2201
|
+
}): Promise<Record<string, unknown>> {
|
|
2202
|
+
const fake = makeFakeConversation({
|
|
2203
|
+
processing: false,
|
|
2204
|
+
...(opts.messages ? { messages: opts.messages } : {}),
|
|
2205
|
+
});
|
|
2206
|
+
fakeConversation = fake.conversation;
|
|
2207
|
+
let runOptions: Record<string, unknown> = {};
|
|
2208
|
+
fake.conversation.runAgentLoop = async (...args: unknown[]) => {
|
|
2209
|
+
runOptions = args[2] as Record<string, unknown>;
|
|
2210
|
+
};
|
|
2211
|
+
await startVoiceTurn({ ...makeTurnOptions(), ...(opts.turn ?? {}) });
|
|
2212
|
+
return runOptions;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
test("a text-only call keeps the call-site profile", async () => {
|
|
2216
|
+
const runOptions = await runOptionsFor({});
|
|
2217
|
+
|
|
2218
|
+
expect(runOptions.overrideProfile).toBeUndefined();
|
|
2219
|
+
expect(runOptions.forceOverrideProfile).toBeUndefined();
|
|
2220
|
+
});
|
|
2221
|
+
|
|
2222
|
+
test("an image in history pins the image-capable profile", async () => {
|
|
2223
|
+
// `callAgent`'s balanced profile carries no guarantee that its model takes
|
|
2224
|
+
// an image, and a model that rejects one fails the whole leg.
|
|
2225
|
+
const runOptions = await runOptionsFor({ messages: PHOTO_HISTORY });
|
|
2226
|
+
|
|
2227
|
+
expect(runOptions.overrideProfile).toBe("latency-optimized");
|
|
2228
|
+
// callAgent is not `mainAgent`, so an unforced override would sit below
|
|
2229
|
+
// the call-site profile and never apply.
|
|
2230
|
+
expect(runOptions.forceOverrideProfile).toBe(true);
|
|
2231
|
+
});
|
|
2232
|
+
|
|
2233
|
+
test("an image nested in a tool result counts too", async () => {
|
|
2234
|
+
const runOptions = await runOptionsFor({
|
|
2235
|
+
messages: [
|
|
2236
|
+
{
|
|
2237
|
+
role: "user",
|
|
2238
|
+
content: [
|
|
2239
|
+
{
|
|
2240
|
+
type: "tool_result",
|
|
2241
|
+
contentBlocks: [{ type: "image", source: { data: "abc" } }],
|
|
2242
|
+
},
|
|
2243
|
+
],
|
|
2244
|
+
},
|
|
2245
|
+
],
|
|
2246
|
+
});
|
|
2247
|
+
|
|
2248
|
+
expect(runOptions.overrideProfile).toBe("latency-optimized");
|
|
2249
|
+
});
|
|
2250
|
+
|
|
2251
|
+
test("a front-door leg is left alone — its own call site pins it", async () => {
|
|
2252
|
+
const runOptions = await runOptionsFor({
|
|
2253
|
+
messages: PHOTO_HISTORY,
|
|
2254
|
+
turn: { routingLeg: "front-door" },
|
|
2255
|
+
});
|
|
2256
|
+
|
|
2257
|
+
expect(runOptions.overrideProfile).toBeUndefined();
|
|
2258
|
+
expect(runOptions.callSite).toBe("voiceFrontDoor");
|
|
2259
|
+
});
|
|
2260
|
+
|
|
2261
|
+
test("no pin when the pin target can't take an image either", async () => {
|
|
2262
|
+
// Fireworks: `latency-optimized` resolves to a text-only model while
|
|
2263
|
+
// `balanced` is vision-capable, so pinning would break the very turn the
|
|
2264
|
+
// pin exists to save.
|
|
2265
|
+
pinProfileSupportsVision = false;
|
|
2266
|
+
|
|
2267
|
+
const runOptions = await runOptionsFor({ messages: PHOTO_HISTORY });
|
|
2268
|
+
|
|
2269
|
+
expect(runOptions.overrideProfile).toBeUndefined();
|
|
2270
|
+
expect(runOptions.forceOverrideProfile).toBeUndefined();
|
|
2271
|
+
});
|
|
2272
|
+
|
|
2273
|
+
test("an explicit routing pin wins over the image pin", async () => {
|
|
2274
|
+
const runOptions = await runOptionsFor({
|
|
2275
|
+
messages: PHOTO_HISTORY,
|
|
2276
|
+
turn: { overrideProfile: "quality-optimized" },
|
|
2277
|
+
});
|
|
2278
|
+
|
|
2279
|
+
expect(runOptions.overrideProfile).toBe("quality-optimized");
|
|
2280
|
+
});
|
|
2281
|
+
});
|
|
@@ -35,8 +35,9 @@ import {
|
|
|
35
35
|
updateMessageContent,
|
|
36
36
|
} from "../persistence/conversation-crud.js";
|
|
37
37
|
import { VOICE_ESCALATION_CONTINUATION_MESSAGE_KIND } from "../plugin-api/constants.js";
|
|
38
|
+
import { doesSupportVision } from "../plugin-api/vision-support.js";
|
|
38
39
|
import { pinnedListeningLanguage } from "../providers/speech-to-text/provider-catalog.js";
|
|
39
|
-
import type { ContentBlock } from "../providers/types.js";
|
|
40
|
+
import type { ContentBlock, Message } from "../providers/types.js";
|
|
40
41
|
import { broadcastMessage } from "../runtime/assistant-event-hub.js";
|
|
41
42
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
42
43
|
import { getCurrentSeq } from "../runtime/assistant-stream-state.js";
|
|
@@ -67,6 +68,40 @@ import {
|
|
|
67
68
|
|
|
68
69
|
const log = getLogger("voice-session-bridge");
|
|
69
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Profile an image-bearing voice leg is pinned to.
|
|
73
|
+
*
|
|
74
|
+
* The latency-class profile is the one voice already leans on (it fronts every
|
|
75
|
+
* turn through `voiceFrontDoor`); `callAgent`'s `balanced` profile carries no
|
|
76
|
+
* guarantee that its model takes images, and a model that rejects an image
|
|
77
|
+
* fails the whole leg rather than degrading it.
|
|
78
|
+
*
|
|
79
|
+
* Whether THIS profile takes images is an install-level question, not a
|
|
80
|
+
* constant: a BYO provider resolves the key through its own column of the
|
|
81
|
+
* intent matrix, and on Fireworks that lands on a text-only model while its
|
|
82
|
+
* `balanced` column is vision-capable. Pinning there would break the exact
|
|
83
|
+
* turns this pin exists to save, hence the capability check at the call site.
|
|
84
|
+
*/
|
|
85
|
+
const VOICE_IMAGE_PROFILE = "latency-optimized";
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Does this conversation's history carry an image?
|
|
89
|
+
*
|
|
90
|
+
* Images persist inline and are re-sent on every later turn, so one photo
|
|
91
|
+
* taken mid-call makes every remaining turn of that call an image turn -- the
|
|
92
|
+
* check is over the whole history, not just this turn's own content.
|
|
93
|
+
*/
|
|
94
|
+
function conversationCarriesImage(messages: readonly Message[]): boolean {
|
|
95
|
+
return messages.some((message) =>
|
|
96
|
+
message.content.some(
|
|
97
|
+
(block) =>
|
|
98
|
+
block.type === "image" ||
|
|
99
|
+
(block.type === "tool_result" &&
|
|
100
|
+
block.contentBlocks?.some((nested) => nested.type === "image")),
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
70
105
|
/**
|
|
71
106
|
* Front-door decision rule with the registry-derived capability digest. The
|
|
72
107
|
* front-door leg runs toolless (see the `toolsDisabledDepth` bracket in
|
|
@@ -1657,6 +1692,23 @@ export async function startVoiceTurn(
|
|
|
1657
1692
|
conversation.toolsDisabledDepth++;
|
|
1658
1693
|
frontDoorToolsSuppressed = true;
|
|
1659
1694
|
}
|
|
1695
|
+
// Resolved once here rather than inside the options literal below, so
|
|
1696
|
+
// the history scan happens once per leg. A front-door leg is skipped:
|
|
1697
|
+
// its own call site already resolves to the same profile. The
|
|
1698
|
+
// capability check comes before the scan because it is the cheaper of
|
|
1699
|
+
// the two and it decides whether the pin is worth anything at all.
|
|
1700
|
+
const carriesImage =
|
|
1701
|
+
opts.routingLeg !== "front-door" &&
|
|
1702
|
+
doesSupportVision(VOICE_IMAGE_PROFILE) &&
|
|
1703
|
+
conversationCarriesImage(conversation.getMessages());
|
|
1704
|
+
if (carriesImage) {
|
|
1705
|
+
log.info(
|
|
1706
|
+
{ turnId, routingLeg: opts.routingLeg ?? null },
|
|
1707
|
+
"Voice leg carries an image; pinning the image-capable profile",
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
const profilePin =
|
|
1711
|
+
opts.overrideProfile ?? (carriesImage ? VOICE_IMAGE_PROFILE : null);
|
|
1660
1712
|
await conversation.runAgentLoop(persistedContent, messageId, {
|
|
1661
1713
|
onEvent: (msg: AssistantEvent) => {
|
|
1662
1714
|
if (msg.type === "assistant_turn_start") {
|
|
@@ -1737,11 +1789,13 @@ export async function startVoiceTurn(
|
|
|
1737
1789
|
// strong escalation profile. `forceOverrideProfile` floats it above the
|
|
1738
1790
|
// callAgent call-site layers (callAgent is not `mainAgent`, so the
|
|
1739
1791
|
// override would otherwise sit below the call-site profile).
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1792
|
+
//
|
|
1793
|
+
// An explicit routing pin wins; failing that, a leg whose history
|
|
1794
|
+
// carries an image is pinned to a profile whose model takes one. A
|
|
1795
|
+
// front-door leg needs neither: its own call site already resolves
|
|
1796
|
+
// there.
|
|
1797
|
+
...(profilePin != null
|
|
1798
|
+
? { overrideProfile: profilePin, forceOverrideProfile: true }
|
|
1745
1799
|
: {}),
|
|
1746
1800
|
});
|
|
1747
1801
|
if (lastError) {
|