@vellumai/assistant 0.11.4 → 0.11.5-staging.1
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__/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__/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/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/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/feature-flag-registry.json +35 -19
- 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,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests that an unclassified 4xx provider rejection logs the upstream
|
|
3
|
+
* response body (bounded) plus the offending request content-part coordinates,
|
|
4
|
+
* so the failing message can be identified from the daemon log alone.
|
|
5
|
+
*/
|
|
6
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
7
|
+
|
|
8
|
+
import type { AssistantEvent } from "../../api/index.js";
|
|
9
|
+
import { ProviderError } from "../../util/errors.js";
|
|
10
|
+
import * as loggerModule from "../../util/logger.js";
|
|
11
|
+
import type {
|
|
12
|
+
EventHandlerDeps,
|
|
13
|
+
EventHandlerState,
|
|
14
|
+
} from "../conversation-agent-loop-handlers.js";
|
|
15
|
+
import {
|
|
16
|
+
buildProviderRejectionLogFields,
|
|
17
|
+
MAX_LOGGED_UPSTREAM_BODY_CHARS,
|
|
18
|
+
} from "../provider-rejection-log-fields.js";
|
|
19
|
+
|
|
20
|
+
const logRecords: Record<string, unknown>[] = [];
|
|
21
|
+
|
|
22
|
+
mock.module("../../util/logger.js", () => ({
|
|
23
|
+
...loggerModule,
|
|
24
|
+
getLogger: () => ({
|
|
25
|
+
error: (record: Record<string, unknown>) => {
|
|
26
|
+
logRecords.push(record);
|
|
27
|
+
},
|
|
28
|
+
warn: () => {},
|
|
29
|
+
info: () => {},
|
|
30
|
+
debug: () => {},
|
|
31
|
+
trace: () => {},
|
|
32
|
+
}),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
// The handlers module binds its logger at import time, so it is loaded
|
|
36
|
+
// dynamically once the logger stub is installed above.
|
|
37
|
+
const { createEventHandlerState, dispatchAgentEvent } =
|
|
38
|
+
await import("../conversation-agent-loop-handlers.js");
|
|
39
|
+
|
|
40
|
+
function createDeps(): EventHandlerDeps {
|
|
41
|
+
return {
|
|
42
|
+
ctx: {
|
|
43
|
+
conversationId: "conv-provider-rejection",
|
|
44
|
+
provider: { name: "openai" },
|
|
45
|
+
streamThinking: false,
|
|
46
|
+
emitActivityState: () => {},
|
|
47
|
+
markWorkspaceTopLevelDirty: () => {},
|
|
48
|
+
currentTurnSurfaces: [],
|
|
49
|
+
} as unknown as EventHandlerDeps["ctx"],
|
|
50
|
+
onEvent: (_msg: AssistantEvent) => {},
|
|
51
|
+
reqId: "req-provider-rejection",
|
|
52
|
+
isFirstMessage: false,
|
|
53
|
+
shouldGenerateTitle: false,
|
|
54
|
+
rlog: new Proxy({} as Record<string, unknown>, {
|
|
55
|
+
get: () => () => {},
|
|
56
|
+
}) as unknown as EventHandlerDeps["rlog"],
|
|
57
|
+
turnChannelContext: {
|
|
58
|
+
userMessageChannel: "vellum",
|
|
59
|
+
assistantMessageChannel: "vellum",
|
|
60
|
+
} as EventHandlerDeps["turnChannelContext"],
|
|
61
|
+
turnInterfaceContext: {
|
|
62
|
+
userMessageInterface: "macos",
|
|
63
|
+
assistantMessageInterface: "macos",
|
|
64
|
+
} as EventHandlerDeps["turnInterfaceContext"],
|
|
65
|
+
} as EventHandlerDeps;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe("buildProviderRejectionLogFields", () => {
|
|
69
|
+
test("carries the verbatim upstream body", () => {
|
|
70
|
+
// GIVEN a provider rejection that captured the upstream 400 body
|
|
71
|
+
const rawBody = JSON.stringify({
|
|
72
|
+
error: {
|
|
73
|
+
message: "Invalid body: failed to parse JSON value from the request.",
|
|
74
|
+
type: "invalid_request_error",
|
|
75
|
+
code: "invalid_body",
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
const error = new ProviderError(
|
|
79
|
+
"API error (400): Invalid body",
|
|
80
|
+
"openai",
|
|
81
|
+
400,
|
|
82
|
+
{
|
|
83
|
+
rawBody,
|
|
84
|
+
apiErrorCode: "invalid_body",
|
|
85
|
+
apiErrorType: "invalid_request_error",
|
|
86
|
+
requestId: "req_abc123",
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// WHEN diagnostic log fields are built for it
|
|
91
|
+
const fields = buildProviderRejectionLogFields(error);
|
|
92
|
+
|
|
93
|
+
// THEN the body and the upstream metadata ride the record
|
|
94
|
+
expect(fields.upstreamErrorBody).toBe(rawBody);
|
|
95
|
+
expect(fields.upstreamErrorBodyTruncated).toBeUndefined();
|
|
96
|
+
expect(fields.apiErrorCode).toBe("invalid_body");
|
|
97
|
+
expect(fields.apiErrorType).toBe("invalid_request_error");
|
|
98
|
+
expect(fields.requestId).toBe("req_abc123");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("bounds the body to 4 KB and flags the truncation", () => {
|
|
102
|
+
// GIVEN an upstream body larger than the logged ceiling
|
|
103
|
+
const rawBody = "x".repeat(MAX_LOGGED_UPSTREAM_BODY_CHARS + 500);
|
|
104
|
+
const error = new ProviderError("API error (400): huge", "openai", 400, {
|
|
105
|
+
rawBody,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// WHEN diagnostic log fields are built for it
|
|
109
|
+
const fields = buildProviderRejectionLogFields(error);
|
|
110
|
+
|
|
111
|
+
// THEN the logged body stops at the ceiling
|
|
112
|
+
expect(fields.upstreamErrorBody).toHaveLength(
|
|
113
|
+
MAX_LOGGED_UPSTREAM_BODY_CHARS,
|
|
114
|
+
);
|
|
115
|
+
// AND the record says the body was cut
|
|
116
|
+
expect(fields.upstreamErrorBodyTruncated).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("parses the offending content-part coordinates for oversized strings", () => {
|
|
120
|
+
// GIVEN an oversized-content-part rejection naming the request position
|
|
121
|
+
const error = new ProviderError(
|
|
122
|
+
"API error (400): Invalid 'input[191].content[1].text': string too long. Expected a string with maximum length 10485760, but got a string with length 11436754 instead.",
|
|
123
|
+
"openai",
|
|
124
|
+
400,
|
|
125
|
+
{ apiErrorCode: "string_above_max_length" },
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// WHEN diagnostic log fields are built for it
|
|
129
|
+
const fields = buildProviderRejectionLogFields(error);
|
|
130
|
+
|
|
131
|
+
// THEN the message and content indices are logged for a follow-up query
|
|
132
|
+
expect(fields.offendingMessageIndex).toBe(191);
|
|
133
|
+
expect(fields.offendingContentIndex).toBe(1);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("parses coordinates out of the Chat Completions `messages[N]` shape", () => {
|
|
137
|
+
// GIVEN a rejection using the Chat Completions request shape
|
|
138
|
+
const error = new ProviderError(
|
|
139
|
+
"API error (400): Invalid 'messages[7].content[0].text': string too long.",
|
|
140
|
+
"openai",
|
|
141
|
+
400,
|
|
142
|
+
{ apiErrorCode: "string_above_max_length" },
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// WHEN diagnostic log fields are built for it
|
|
146
|
+
const fields = buildProviderRejectionLogFields(error);
|
|
147
|
+
|
|
148
|
+
// THEN the same coordinates are extracted
|
|
149
|
+
expect(fields.offendingMessageIndex).toBe(7);
|
|
150
|
+
expect(fields.offendingContentIndex).toBe(0);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("leaves coordinates absent for codes that carry no content-part pointer", () => {
|
|
154
|
+
// GIVEN a rejection whose code is outside the narrow tap
|
|
155
|
+
const error = new ProviderError(
|
|
156
|
+
"API error (400): Invalid 'input[3].content[0].text': something else.",
|
|
157
|
+
"openai",
|
|
158
|
+
400,
|
|
159
|
+
{ apiErrorCode: "model_not_found" },
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// WHEN diagnostic log fields are built for it
|
|
163
|
+
const fields = buildProviderRejectionLogFields(error);
|
|
164
|
+
|
|
165
|
+
// THEN no coordinates are inferred from a coincidental match
|
|
166
|
+
expect(fields.offendingMessageIndex).toBeUndefined();
|
|
167
|
+
expect(fields.offendingContentIndex).toBeUndefined();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("scrubs secrets out of the upstream body", () => {
|
|
171
|
+
// GIVEN an upstream body that echoed back a credential
|
|
172
|
+
const projectKey = `sk-proj-${"A".repeat(48)}`;
|
|
173
|
+
const error = new ProviderError("API error (400): bad key", "openai", 400, {
|
|
174
|
+
rawBody: JSON.stringify({
|
|
175
|
+
error: { message: `Incorrect API key provided: ${projectKey}` },
|
|
176
|
+
headers: { authorization: "Bearer some-token-value" },
|
|
177
|
+
}),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// WHEN diagnostic log fields are built for it
|
|
181
|
+
const fields = buildProviderRejectionLogFields(error);
|
|
182
|
+
|
|
183
|
+
// THEN the credential never reaches the log record
|
|
184
|
+
expect(fields.upstreamErrorBody).not.toContain(projectKey);
|
|
185
|
+
expect(fields.upstreamErrorBody).not.toContain("some-token-value");
|
|
186
|
+
expect(fields.upstreamErrorBody).toContain("[REDACTED]");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("reports a null body for errors that are not provider errors", () => {
|
|
190
|
+
// GIVEN a non-provider error
|
|
191
|
+
const error = new Error("socket hang up");
|
|
192
|
+
|
|
193
|
+
// WHEN diagnostic log fields are built for it
|
|
194
|
+
const fields = buildProviderRejectionLogFields(error);
|
|
195
|
+
|
|
196
|
+
// THEN the body is explicitly null rather than missing
|
|
197
|
+
expect(fields.upstreamErrorBody).toBeNull();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("unclassified 4xx log line", () => {
|
|
202
|
+
let state: EventHandlerState;
|
|
203
|
+
|
|
204
|
+
beforeEach(() => {
|
|
205
|
+
logRecords.length = 0;
|
|
206
|
+
state = createEventHandlerState();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("includes the upstream response body", async () => {
|
|
210
|
+
// GIVEN an unclassified 4xx provider rejection carrying the upstream body
|
|
211
|
+
const rawBody = JSON.stringify({
|
|
212
|
+
error: {
|
|
213
|
+
message:
|
|
214
|
+
"Invalid body: failed to parse JSON value from the request. Common errors include trailing commas.",
|
|
215
|
+
type: "invalid_request_error",
|
|
216
|
+
code: "invalid_body",
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
const error = new ProviderError(
|
|
220
|
+
"API error (400): Invalid body: failed to parse JSON value from the request.",
|
|
221
|
+
"openai",
|
|
222
|
+
400,
|
|
223
|
+
{ rawBody, apiErrorCode: "invalid_body" },
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// WHEN the agent loop dispatches the error event
|
|
227
|
+
await dispatchAgentEvent(state, createDeps(), { type: "error", error });
|
|
228
|
+
|
|
229
|
+
// THEN the log record carries the body alongside the existing fields
|
|
230
|
+
const record = logRecords.find((r) => r.upstreamErrorBody !== undefined);
|
|
231
|
+
expect(record).toBeDefined();
|
|
232
|
+
expect(record?.upstreamErrorBody).toBe(rawBody);
|
|
233
|
+
expect(record?.conversationId).toBe("conv-provider-rejection");
|
|
234
|
+
expect(record?.statusCode).toBe(400);
|
|
235
|
+
expect(record?.provider).toBe("openai");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("scrubs secrets out of the error message it logs", async () => {
|
|
239
|
+
// GIVEN a rejection whose normalized message echoes a credential
|
|
240
|
+
const error = new ProviderError(
|
|
241
|
+
"API error (400): rejected request with Authorization: Bearer some-token-value",
|
|
242
|
+
"openai",
|
|
243
|
+
400,
|
|
244
|
+
{ rawBody: "{}" },
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// WHEN the agent loop dispatches the error event
|
|
248
|
+
await dispatchAgentEvent(state, createDeps(), { type: "error", error });
|
|
249
|
+
|
|
250
|
+
// THEN the credential is redacted out of the logged message
|
|
251
|
+
const record = logRecords.find((r) => r.upstreamErrorBody !== undefined);
|
|
252
|
+
expect(record?.errorMessage).not.toContain("some-token-value");
|
|
253
|
+
expect(record?.errorMessage).toContain("Bearer [REDACTED]");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("bounds a huge upstream body to 4 KB", async () => {
|
|
257
|
+
// GIVEN an unclassified 4xx rejection whose body exceeds the ceiling
|
|
258
|
+
const error = new ProviderError("API error (400): huge", "openai", 400, {
|
|
259
|
+
rawBody: "y".repeat(MAX_LOGGED_UPSTREAM_BODY_CHARS * 3),
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// WHEN the agent loop dispatches the error event
|
|
263
|
+
await dispatchAgentEvent(state, createDeps(), { type: "error", error });
|
|
264
|
+
|
|
265
|
+
// THEN the logged body is capped
|
|
266
|
+
const record = logRecords.find((r) => r.upstreamErrorBody !== undefined);
|
|
267
|
+
expect(record?.upstreamErrorBody).toHaveLength(
|
|
268
|
+
MAX_LOGGED_UPSTREAM_BODY_CHARS,
|
|
269
|
+
);
|
|
270
|
+
expect(record?.upstreamErrorBodyTruncated).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
@@ -89,6 +89,7 @@ import {
|
|
|
89
89
|
} from "../usage/pricing.js";
|
|
90
90
|
import { ProviderError } from "../util/errors.js";
|
|
91
91
|
import { faviconUrlForDomain } from "../util/favicon.js";
|
|
92
|
+
import { redactLogString } from "../util/log-redact.js";
|
|
92
93
|
import { getLogger } from "../util/logger.js";
|
|
93
94
|
import { withSqliteRetry } from "../util/sqlite-retry.js";
|
|
94
95
|
import type { DirectiveRequest } from "./assistant-attachments.js";
|
|
@@ -143,6 +144,7 @@ import type {
|
|
|
143
144
|
WebSearchResultItem,
|
|
144
145
|
} from "./message-types/web-activity.js";
|
|
145
146
|
import { referenceMediaBlocksForPersist } from "./persist-media-references.js";
|
|
147
|
+
import { buildProviderRejectionLogFields } from "./provider-rejection-log-fields.js";
|
|
146
148
|
import { turnOrRestingTrust } from "./trust-context-types.js";
|
|
147
149
|
import type { TurnLatencyTracker } from "./turn-latency-tracker.js";
|
|
148
150
|
|
|
@@ -2708,7 +2710,8 @@ function handleError(
|
|
|
2708
2710
|
event.error instanceof ProviderError
|
|
2709
2711
|
? event.error.provider
|
|
2710
2712
|
: undefined,
|
|
2711
|
-
errorMessage: event.error.message,
|
|
2713
|
+
errorMessage: redactLogString(event.error.message),
|
|
2714
|
+
...buildProviderRejectionLogFields(event.error),
|
|
2712
2715
|
},
|
|
2713
2716
|
"Provider rejected request with unclassified 4xx error",
|
|
2714
2717
|
);
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
isImageDimensionsTooLargeError,
|
|
12
12
|
isImageMediaTypeMismatchError,
|
|
13
13
|
isImageUnprocessableError,
|
|
14
|
+
isImageUnsupportedFormatError,
|
|
14
15
|
} from "../plugins/defaults/image-recovery/detect.js";
|
|
15
16
|
import { ConnectionResolutionError } from "../providers/connection-resolution.js";
|
|
16
17
|
import { PROVIDER_CATALOG } from "../providers/model-catalog.js";
|
|
@@ -539,6 +540,18 @@ function classifyCore(
|
|
|
539
540
|
errorCategory: "image_media_type_mismatch",
|
|
540
541
|
};
|
|
541
542
|
}
|
|
543
|
+
if (isImageUnsupportedFormatError(message)) {
|
|
544
|
+
// Same wire-code reuse as image_unprocessable above. The provider read
|
|
545
|
+
// the bytes and found no image it accepts, so no relabel or resize can
|
|
546
|
+
// rescue the attachment: name the accepted formats instead.
|
|
547
|
+
return {
|
|
548
|
+
code: "IMAGE_TOO_LARGE",
|
|
549
|
+
userMessage:
|
|
550
|
+
"An image in this conversation is not in a format the AI provider accepts (PNG, JPEG, GIF, and WebP are). Remove or convert it and send your message again.",
|
|
551
|
+
retryable: false,
|
|
552
|
+
errorCategory: "image_unsupported_format",
|
|
553
|
+
};
|
|
554
|
+
}
|
|
542
555
|
if (isVisionNotSupportedError(message)) {
|
|
543
556
|
return visionNotSupportedClassification();
|
|
544
557
|
}
|
|
@@ -69,6 +69,7 @@ import type { UserMessageAttachment } from "./message-protocol.js";
|
|
|
69
69
|
import type { ConversationTransportMetadata } from "./message-types/conversations.js";
|
|
70
70
|
import type { TrustContext } from "./trust-context-types.js";
|
|
71
71
|
import { restingTrust } from "./trust-context-types.js";
|
|
72
|
+
import { postUnsendableImageNotice } from "./unsendable-image-notice.js";
|
|
72
73
|
|
|
73
74
|
const log = getLogger("conversation-messaging");
|
|
74
75
|
|
|
@@ -855,7 +856,6 @@ export async function persistQueuedMessageBody(
|
|
|
855
856
|
filePath: attachment.filePath,
|
|
856
857
|
}),
|
|
857
858
|
);
|
|
858
|
-
const cleanMessage = await createUserMessage(content, attachmentInputs);
|
|
859
859
|
let pushedToHistory = false;
|
|
860
860
|
|
|
861
861
|
try {
|
|
@@ -1015,6 +1015,14 @@ export async function persistQueuedMessageBody(
|
|
|
1015
1015
|
attachmentInputs,
|
|
1016
1016
|
);
|
|
1017
1017
|
|
|
1018
|
+
// The turn sees exactly what was persisted: an attachment rejected during
|
|
1019
|
+
// materialization is absent from both, so a file the store refused cannot
|
|
1020
|
+
// reach the model through the in-memory message.
|
|
1021
|
+
const sentAttachments = preparedAttachments.map(
|
|
1022
|
+
(p) => attachmentInputs[p.position],
|
|
1023
|
+
);
|
|
1024
|
+
const cleanMessage = await createUserMessage(content, sentAttachments);
|
|
1025
|
+
|
|
1018
1026
|
// When displayContent is provided (e.g. original text before recording
|
|
1019
1027
|
// intent stripping), persist that to DB so users see the full message
|
|
1020
1028
|
// after restart. The in-memory userMessage (sent to the LLM) still uses
|
|
@@ -1119,7 +1127,7 @@ export async function persistQueuedMessageBody(
|
|
|
1119
1127
|
|
|
1120
1128
|
const llmMessage = enrichMessageWithSourcePaths(
|
|
1121
1129
|
cleanMessage,
|
|
1122
|
-
|
|
1130
|
+
sentAttachments,
|
|
1123
1131
|
);
|
|
1124
1132
|
log.info(
|
|
1125
1133
|
{
|
|
@@ -1144,6 +1152,18 @@ export async function persistQueuedMessageBody(
|
|
|
1144
1152
|
);
|
|
1145
1153
|
}
|
|
1146
1154
|
|
|
1155
|
+
// Read after the content is final (including any link-failure repair), so
|
|
1156
|
+
// the notice describes the blocks the send boundary will actually see.
|
|
1157
|
+
const persistedImages = preparedAttachments.flatMap((p, idx) => {
|
|
1158
|
+
const block = repairedBlocks?.[idx] ?? p.block;
|
|
1159
|
+
if (block.type !== "image") {
|
|
1160
|
+
return [];
|
|
1161
|
+
}
|
|
1162
|
+
const { filename } = attachmentInputs[p.position];
|
|
1163
|
+
return [{ filename, source: block.source }];
|
|
1164
|
+
});
|
|
1165
|
+
await postUnsendableImageNotice(ctx.conversationId, persistedImages);
|
|
1166
|
+
|
|
1147
1167
|
return { id: persistedUserMessage.id, deduplicated: false };
|
|
1148
1168
|
} catch (err) {
|
|
1149
1169
|
if (pushedToHistory) {
|
|
@@ -1827,6 +1827,13 @@ export interface ProcessMessageOptions {
|
|
|
1827
1827
|
displayContent?: string;
|
|
1828
1828
|
/** JWT-verified committer principal for turn-scoped host-proxy authorization. */
|
|
1829
1829
|
sourceActorPrincipalId?: string;
|
|
1830
|
+
/**
|
|
1831
|
+
* The actor this turn is being started for. Stamped onto the conversation
|
|
1832
|
+
* before history is scoped, so the run hydrates as its committer rather
|
|
1833
|
+
* than as whoever last left the resting slot set. Callers with no actor of
|
|
1834
|
+
* their own (internal dispatch) omit it and the resting actor stands.
|
|
1835
|
+
*/
|
|
1836
|
+
trustContext?: TrustContext;
|
|
1830
1837
|
/**
|
|
1831
1838
|
* True when this turn was auto-sent on the user's behalf rather than typed
|
|
1832
1839
|
* (see `PersistMessageOptions.scripted`). Forwarded to persistence so the
|
|
@@ -1873,7 +1880,11 @@ export async function processMessage(
|
|
|
1873
1880
|
sourceActorPrincipalId,
|
|
1874
1881
|
scripted,
|
|
1875
1882
|
metadata: callerMetadata,
|
|
1883
|
+
trustContext: committingTrustContext,
|
|
1876
1884
|
} = options;
|
|
1885
|
+
if (committingTrustContext) {
|
|
1886
|
+
conversation.setTrustContext(committingTrustContext);
|
|
1887
|
+
}
|
|
1877
1888
|
await conversation.ensureActorScopedHistory();
|
|
1878
1889
|
// Snapshot persona context at turn start so later tool turns can't pick up
|
|
1879
1890
|
// a different actor's context if a concurrent request mutates the live fields.
|
|
@@ -151,6 +151,13 @@ function applyTransportMetadata(
|
|
|
151
151
|
*
|
|
152
152
|
* Handles provider setup, rate limiting, system prompt, memory policy,
|
|
153
153
|
* and conversation hydration.
|
|
154
|
+
*
|
|
155
|
+
* The in-memory Conversation snapshots the tool registry at construction.
|
|
156
|
+
* An empty snapshot is an empty tool list on the wire for the life of the
|
|
157
|
+
* instance, so the create path initializes the registry before that
|
|
158
|
+
* constructor runs. A hit in `findConversation` already has its snapshot.
|
|
159
|
+
* `initializeTools` is idempotent: a process that already initialized at
|
|
160
|
+
* boot awaits the settled promise.
|
|
154
161
|
*/
|
|
155
162
|
export async function getOrCreateConversation(
|
|
156
163
|
conversationId: string,
|
|
@@ -228,6 +235,9 @@ export async function getOrCreateConversation(
|
|
|
228
235
|
const systemPrompt = await resolveInitialSystemPrompt(storedOptions);
|
|
229
236
|
const maxTokens = storedOptions?.maxResponseTokens;
|
|
230
237
|
|
|
238
|
+
const { initializeTools } = await import("../tools/registry.js");
|
|
239
|
+
await initializeTools();
|
|
240
|
+
|
|
231
241
|
const newConversation = new Conversation(
|
|
232
242
|
conversationId,
|
|
233
243
|
provider,
|
|
@@ -85,6 +85,8 @@ import type {
|
|
|
85
85
|
} from "./message-protocol.js";
|
|
86
86
|
import { INTERACTIVE_SURFACE_TYPES } from "./message-protocol.js";
|
|
87
87
|
import { isRowVisibleToUntrustedActor } from "./message-provenance.js";
|
|
88
|
+
import type { TrustContext } from "./trust-context-types.js";
|
|
89
|
+
import { restingTrust } from "./trust-context-types.js";
|
|
88
90
|
export {
|
|
89
91
|
buildSurfaceShowPair,
|
|
90
92
|
type CurrentTurnSurface,
|
|
@@ -1904,7 +1906,13 @@ export async function handleSurfaceAction(
|
|
|
1904
1906
|
// reconstruct the same-user binding for host proxies (CU / app-control),
|
|
1905
1907
|
// mirroring the normal message path.
|
|
1906
1908
|
sourceActorPrincipalId?: string,
|
|
1909
|
+
// Trust of the actor committing this action, resolved by the route from the
|
|
1910
|
+
// verified requester. Passed rather than read back off the conversation so a
|
|
1911
|
+
// click that lands mid-turn is attributed to its clicker instead of to
|
|
1912
|
+
// whoever occupies the resting slot.
|
|
1913
|
+
requesterTrustContext?: TrustContext,
|
|
1907
1914
|
): Promise<SurfaceActionResult> {
|
|
1915
|
+
const actionTrustContext = requesterTrustContext ?? restingTrust(ctx);
|
|
1908
1916
|
// ── Standalone surface interception ──────────────────────────────
|
|
1909
1917
|
// Daemon-driven surfaces (from `requestInteractiveUi`) register a
|
|
1910
1918
|
// pending entry in `pendingStandaloneSurfaces`. When the user clicks
|
|
@@ -2050,7 +2058,7 @@ export async function handleSurfaceAction(
|
|
|
2050
2058
|
// `await` resolves as soon as the conversation is created + titled +
|
|
2051
2059
|
// published to the event hub. The HTTP POST /v1/surface-actions
|
|
2052
2060
|
// response returns promptly — the seed turn runs in the background.
|
|
2053
|
-
const originTrustContext =
|
|
2061
|
+
const originTrustContext = actionTrustContext;
|
|
2054
2062
|
const { conversationId } = await launchConversation({
|
|
2055
2063
|
title,
|
|
2056
2064
|
seedPrompt,
|
|
@@ -2069,14 +2077,11 @@ export async function handleSurfaceAction(
|
|
|
2069
2077
|
return { accepted: true, conversationId };
|
|
2070
2078
|
}
|
|
2071
2079
|
|
|
2072
|
-
// Trust of the actor committing THIS action
|
|
2073
|
-
//
|
|
2074
|
-
//
|
|
2075
|
-
// the same value, so the completion path's persisted read stays scoped to
|
|
2076
|
-
// exactly what this actor's live view would have held. Unresolvable trust
|
|
2077
|
-
// fails closed to the untrusted filter.
|
|
2080
|
+
// Trust of the actor committing THIS action, so the completion path's
|
|
2081
|
+
// persisted read stays scoped to exactly what this actor's live view would
|
|
2082
|
+
// have held. Unresolvable trust fails closed to the untrusted filter.
|
|
2078
2083
|
const requesterCanAccessMemory = resolveCapabilities(
|
|
2079
|
-
|
|
2084
|
+
actionTrustContext?.trustClass,
|
|
2080
2085
|
).canAccessMemory;
|
|
2081
2086
|
|
|
2082
2087
|
const pending = ctx.pendingSurfaceActions.get(surfaceId);
|
|
@@ -2222,6 +2227,7 @@ export async function handleSurfaceAction(
|
|
|
2222
2227
|
activeSurfaceId: surfaceId,
|
|
2223
2228
|
displayContent,
|
|
2224
2229
|
sourceActorPrincipalId,
|
|
2230
|
+
trustContext: actionTrustContext,
|
|
2225
2231
|
isInteractive: SURFACE_ACTION_TURN_IS_INTERACTIVE,
|
|
2226
2232
|
// Rides the metadata bag rather than a typed option: the queue
|
|
2227
2233
|
// round-trips `metadata` but not `PersistMessageOptions`.
|
|
@@ -2285,6 +2291,9 @@ export async function handleSurfaceAction(
|
|
|
2285
2291
|
activeSurfaceId: surfaceId,
|
|
2286
2292
|
displayContent,
|
|
2287
2293
|
sourceActorPrincipalId,
|
|
2294
|
+
// Reached only when `enqueueMessage` declined to queue: the click is
|
|
2295
|
+
// starting this turn, so it is the actor the run belongs to.
|
|
2296
|
+
trustContext: actionTrustContext,
|
|
2288
2297
|
isInteractive: SURFACE_ACTION_TURN_IS_INTERACTIVE,
|
|
2289
2298
|
scripted: isSyntheticSurfaceActionContent(content),
|
|
2290
2299
|
})
|
|
@@ -2472,6 +2481,7 @@ export async function handleSurfaceAction(
|
|
|
2472
2481
|
activeSurfaceId: surfaceId,
|
|
2473
2482
|
displayContent,
|
|
2474
2483
|
sourceActorPrincipalId,
|
|
2484
|
+
trustContext: actionTrustContext,
|
|
2475
2485
|
isInteractive: SURFACE_ACTION_TURN_IS_INTERACTIVE,
|
|
2476
2486
|
// Rides the metadata bag rather than a typed option: the queue
|
|
2477
2487
|
// round-trips `metadata` but not `PersistMessageOptions`.
|
|
@@ -2545,6 +2555,9 @@ export async function handleSurfaceAction(
|
|
|
2545
2555
|
activeSurfaceId: surfaceId,
|
|
2546
2556
|
displayContent,
|
|
2547
2557
|
sourceActorPrincipalId,
|
|
2558
|
+
// Same as the history-restored branch: the enqueue declined, so this
|
|
2559
|
+
// click is the turn about to run.
|
|
2560
|
+
trustContext: actionTrustContext,
|
|
2548
2561
|
isInteractive: SURFACE_ACTION_TURN_IS_INTERACTIVE,
|
|
2549
2562
|
scripted: isSyntheticSurfaceActionContent(content),
|
|
2550
2563
|
})
|
|
@@ -1119,11 +1119,10 @@ export class Conversation {
|
|
|
1119
1119
|
preStrippedCount = boundary === -1 ? slicedDbMessages.length : boundary;
|
|
1120
1120
|
}
|
|
1121
1121
|
|
|
1122
|
-
// The injection-time personal-memory gate, so
|
|
1123
|
-
//
|
|
1124
|
-
// the
|
|
1125
|
-
//
|
|
1126
|
-
// effective trust class.
|
|
1122
|
+
// The injection-time personal-memory gate, so rehydration of the persisted
|
|
1123
|
+
// blocks admits exactly the actors injection would. The shared helper folds
|
|
1124
|
+
// in the HTTP-auth-disabled dev bypass, so a turn with no bound actor
|
|
1125
|
+
// resolves the same way on both paths.
|
|
1127
1126
|
const personalMemoryAllowed = isPersonalMemoryAllowed(this.trustContext);
|
|
1128
1127
|
// Pruned v3 card slugs, read lazily on the first row that carries a v3
|
|
1129
1128
|
// block (most conversations carry none, so most loads never query). The
|
|
@@ -1513,11 +1512,12 @@ export class Conversation {
|
|
|
1513
1512
|
|
|
1514
1513
|
async ensureActorScopedHistory(): Promise<void> {
|
|
1515
1514
|
const currentTrustClass = this.trustContext?.trustClass;
|
|
1516
|
-
//
|
|
1517
|
-
//
|
|
1518
|
-
//
|
|
1519
|
-
//
|
|
1520
|
-
//
|
|
1515
|
+
// Tracked alongside the trust class because `loadFromDb` gates
|
|
1516
|
+
// personal-memory rehydration on `isPersonalMemoryAllowed`, which folds in
|
|
1517
|
+
// the disabled-auth elevation of an unbound actor: two contexts can share a
|
|
1518
|
+
// trust class and still differ here. A reuse that changes the answer has to
|
|
1519
|
+
// reload, or stale personal-memory blocks persist into a turn that must not
|
|
1520
|
+
// see them, or stay stripped from one that should.
|
|
1521
1521
|
const currentPersonalMemoryAllowed = isPersonalMemoryAllowed(
|
|
1522
1522
|
this.trustContext,
|
|
1523
1523
|
);
|
|
@@ -2829,6 +2829,7 @@ export class Conversation {
|
|
|
2829
2829
|
actionId: string,
|
|
2830
2830
|
data?: Record<string, unknown>,
|
|
2831
2831
|
sourceActorPrincipalId?: string,
|
|
2832
|
+
requesterTrustContext?: TrustContext,
|
|
2832
2833
|
): Promise<SurfaceActionResult> {
|
|
2833
2834
|
return handleSurfaceActionImpl(
|
|
2834
2835
|
this,
|
|
@@ -2836,6 +2837,7 @@ export class Conversation {
|
|
|
2836
2837
|
actionId,
|
|
2837
2838
|
data,
|
|
2838
2839
|
sourceActorPrincipalId,
|
|
2840
|
+
requesterTrustContext,
|
|
2839
2841
|
);
|
|
2840
2842
|
}
|
|
2841
2843
|
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -98,6 +98,7 @@ import {
|
|
|
98
98
|
registerMessagingProviders,
|
|
99
99
|
registerWatcherProviders,
|
|
100
100
|
} from "./providers-setup.js";
|
|
101
|
+
import { startResourcePressureGuardForLifecycle } from "./resource-pressure-guard-lifecycle.js";
|
|
101
102
|
import { installShutdownHandlers } from "./shutdown-handlers.js";
|
|
102
103
|
import { broadcastDaemonStatus } from "./status.js";
|
|
103
104
|
|
|
@@ -197,8 +198,10 @@ export async function runDaemon(): Promise<void> {
|
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
|
|
200
|
-
// Start the runtime HTTP server early so /healthz answers ASAP.
|
|
201
|
-
//
|
|
201
|
+
// Start the runtime HTTP server early so /healthz answers ASAP. Throws on
|
|
202
|
+
// EADDRINUSE to abort startup: another process holds the port every HTTP
|
|
203
|
+
// client (and the gateway's /v1/* proxy) targets, so an IPC-only daemon
|
|
204
|
+
// would look healthy while all HTTP traffic 502s.
|
|
202
205
|
await startRuntimeHttpServer();
|
|
203
206
|
|
|
204
207
|
// Warms the configured-probe cache (credential reads only, no DB). Fired
|
|
@@ -700,6 +703,7 @@ export async function runDaemon(): Promise<void> {
|
|
|
700
703
|
|
|
701
704
|
startUsageTelemetryReporter();
|
|
702
705
|
startDiskPressureGuardForLifecycle();
|
|
706
|
+
startResourcePressureGuardForLifecycle();
|
|
703
707
|
startOrphanReaper();
|
|
704
708
|
startEventLoopWatchdog();
|
|
705
709
|
|
|
@@ -48,18 +48,16 @@ interface BaseTransportMetadata {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
* Transport metadata for interfaces that support
|
|
52
|
-
*
|
|
51
|
+
* Transport metadata for interfaces that support desktop host-proxy tools
|
|
52
|
+
* (see `HostProxyInterfaceId` / `supportsHostProxy`). Carries the host
|
|
53
53
|
* environment fields the client reports so the `<workspace>` block renders
|
|
54
54
|
* the user's actual machine rather than a containerized daemon's own OS.
|
|
55
55
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* clients (e.g. a native Linux or Windows desktop) get the same treatment
|
|
59
|
-
* automatically when added to `HostProxyInterfaceId`.
|
|
56
|
+
* The shape is capability-keyed so each native desktop client gets the same
|
|
57
|
+
* host-environment treatment through `HostProxyInterfaceId`.
|
|
60
58
|
*/
|
|
61
59
|
export interface HostProxyTransportMetadata extends BaseTransportMetadata {
|
|
62
|
-
/** Interface identifier
|
|
60
|
+
/** Interface identifier restricted to interfaces that support host proxies. */
|
|
63
61
|
interfaceId: HostProxyInterfaceId;
|
|
64
62
|
/** Home directory of the user on the host machine (e.g. `NSHomeDirectory()`). */
|
|
65
63
|
hostHomeDir?: string;
|