@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,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sustained CPU/memory pressure guard for platform-hosted assistants.
|
|
3
|
+
*
|
|
4
|
+
* Samples container CPU utilization and working-set memory against the
|
|
5
|
+
* container's allocation on a fixed cadence and reports an `elevated` state
|
|
6
|
+
* only when a signal stays over its threshold for nearly the whole sampling
|
|
7
|
+
* window (so a single heavy agent task never trips it). Clearing requires a
|
|
8
|
+
* sustained run of samples below a lower clear threshold (hysteresis), so the
|
|
9
|
+
* state never flaps while usage hovers near a threshold.
|
|
10
|
+
*
|
|
11
|
+
* The guard is platform-gated: off-platform there is no plan allocation to
|
|
12
|
+
* measure against, so the guard stays disabled and samples nothing.
|
|
13
|
+
*/
|
|
14
|
+
import {
|
|
15
|
+
type ResourcePressureState,
|
|
16
|
+
type ResourcePressureStatus,
|
|
17
|
+
} from "../api/events/resource-pressure-status-changed.js";
|
|
18
|
+
import { getIsPlatform } from "../config/env-registry.js";
|
|
19
|
+
import { broadcastMessage } from "../runtime/assistant-event-hub.js";
|
|
20
|
+
import { getContainerCpuCores } from "../util/cgroup-cpu.js";
|
|
21
|
+
import {
|
|
22
|
+
getContainerMemoryLimitBytes,
|
|
23
|
+
getContainerMemoryStat,
|
|
24
|
+
getContainerMemoryUsageBytes,
|
|
25
|
+
} from "../util/cgroup-memory.js";
|
|
26
|
+
import { getAverageContainerCpuPercentOrNull } from "../util/container-cpu-sampler.js";
|
|
27
|
+
import { getLogger } from "../util/logger.js";
|
|
28
|
+
|
|
29
|
+
const log = getLogger("resource-pressure-guard");
|
|
30
|
+
|
|
31
|
+
const RESOURCE_PRESSURE_SAMPLE_INTERVAL_MS = 30_000;
|
|
32
|
+
// 20 samples at a 30s cadence: 10 minutes of history.
|
|
33
|
+
export const RESOURCE_PRESSURE_WINDOW_SAMPLES = 20;
|
|
34
|
+
// A signal must exceed its threshold in at least this many of the last
|
|
35
|
+
// RESOURCE_PRESSURE_WINDOW_SAMPLES samples, with the window full, to enter
|
|
36
|
+
// `elevated`.
|
|
37
|
+
export const RESOURCE_PRESSURE_ENTER_SAMPLES = 18;
|
|
38
|
+
// Consecutive samples below the clear threshold (5 minutes) required to leave
|
|
39
|
+
// `elevated`.
|
|
40
|
+
export const RESOURCE_PRESSURE_CLEAR_CONSECUTIVE_SAMPLES = 10;
|
|
41
|
+
const CPU_PRESSURE_THRESHOLD_PERCENT = 85;
|
|
42
|
+
const CPU_PRESSURE_CLEAR_THRESHOLD_PERCENT = 70;
|
|
43
|
+
const MEMORY_PRESSURE_THRESHOLD_PERCENT = 90;
|
|
44
|
+
const MEMORY_PRESSURE_CLEAR_THRESHOLD_PERCENT = 80;
|
|
45
|
+
|
|
46
|
+
export { type ResourcePressureState, type ResourcePressureStatus };
|
|
47
|
+
|
|
48
|
+
interface ResourcePressureMemorySample {
|
|
49
|
+
usageBytes: number;
|
|
50
|
+
limitBytes: number;
|
|
51
|
+
reclaimableBytes: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ResourcePressureSamplers {
|
|
55
|
+
sampleCpuPercent?: () => number | null;
|
|
56
|
+
sampleMemory?: () => ResourcePressureMemorySample | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface SignalWindow {
|
|
60
|
+
/** Ring buffer of over-threshold results, oldest first. */
|
|
61
|
+
overThreshold: boolean[];
|
|
62
|
+
consecutiveBelowClear: number;
|
|
63
|
+
elevated: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface ResourcePressureGuardState {
|
|
67
|
+
timer: ReturnType<typeof setInterval> | null;
|
|
68
|
+
status: ResourcePressureStatus;
|
|
69
|
+
cpuWindow: SignalWindow;
|
|
70
|
+
memoryWindow: SignalWindow;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const DISABLED_STATUS: ResourcePressureStatus = {
|
|
74
|
+
enabled: false,
|
|
75
|
+
state: "disabled",
|
|
76
|
+
cpuPercent: null,
|
|
77
|
+
memoryPercent: null,
|
|
78
|
+
cpuElevated: false,
|
|
79
|
+
memoryElevated: false,
|
|
80
|
+
cpuThresholdPercent: CPU_PRESSURE_THRESHOLD_PERCENT,
|
|
81
|
+
memoryThresholdPercent: MEMORY_PRESSURE_THRESHOLD_PERCENT,
|
|
82
|
+
lastCheckedAt: null,
|
|
83
|
+
error: null,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const OK_STATUS: ResourcePressureStatus = {
|
|
87
|
+
...DISABLED_STATUS,
|
|
88
|
+
enabled: true,
|
|
89
|
+
state: "ok",
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
function emptySignalWindow(): SignalWindow {
|
|
93
|
+
return { overThreshold: [], consecutiveBelowClear: 0, elevated: false };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const state: ResourcePressureGuardState = {
|
|
97
|
+
timer: null,
|
|
98
|
+
status: cloneStatus(DISABLED_STATUS),
|
|
99
|
+
cpuWindow: emptySignalWindow(),
|
|
100
|
+
memoryWindow: emptySignalWindow(),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
function cloneStatus(status: ResourcePressureStatus): ResourcePressureStatus {
|
|
104
|
+
return { ...status };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// The raw percents and timestamp change on every sample; broadcasting each
|
|
108
|
+
// tick would spam the SSE hub, so only substantive fields participate.
|
|
109
|
+
function statusFingerprint(status: ResourcePressureStatus): string {
|
|
110
|
+
const {
|
|
111
|
+
lastCheckedAt: _lastCheckedAt,
|
|
112
|
+
cpuPercent: _cpuPercent,
|
|
113
|
+
memoryPercent: _memoryPercent,
|
|
114
|
+
...substantiveStatus
|
|
115
|
+
} = status;
|
|
116
|
+
return JSON.stringify(substantiveStatus);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function publishStatusChangedIfNeeded(previous: ResourcePressureStatus): void {
|
|
120
|
+
if (statusFingerprint(previous) === statusFingerprint(state.status)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const status = cloneStatus(state.status);
|
|
124
|
+
broadcastMessage({
|
|
125
|
+
type: "resource_pressure_status_changed",
|
|
126
|
+
status,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function replaceStatus(next: ResourcePressureStatus): ResourcePressureStatus {
|
|
131
|
+
const previous = cloneStatus(state.status);
|
|
132
|
+
state.status = cloneStatus(next);
|
|
133
|
+
publishStatusChangedIfNeeded(previous);
|
|
134
|
+
return cloneStatus(state.status);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function ensureEnabledStatus(): void {
|
|
138
|
+
if (!state.status.enabled) {
|
|
139
|
+
state.status = cloneStatus(OK_STATUS);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function roundPercent(value: number): number {
|
|
144
|
+
return Math.round(value * 100) / 100;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function formatError(error: unknown): string {
|
|
148
|
+
return error instanceof Error ? error.message : String(error);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function sampleFailureStatus(errorMessage: string): ResourcePressureStatus {
|
|
152
|
+
return {
|
|
153
|
+
...OK_STATUS,
|
|
154
|
+
state: "unknown",
|
|
155
|
+
lastCheckedAt: new Date().toISOString(),
|
|
156
|
+
error: errorMessage,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function resetSignalWindow(window: SignalWindow): void {
|
|
161
|
+
window.overThreshold = [];
|
|
162
|
+
window.consecutiveBelowClear = 0;
|
|
163
|
+
window.elevated = false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Feed one sample into a signal's window and derive its elevated flag.
|
|
168
|
+
*
|
|
169
|
+
* An unavailable sample (null percent) resets the window: the signal cannot
|
|
170
|
+
* hold `elevated` without data, and a fresh full window is required before it
|
|
171
|
+
* can enter again.
|
|
172
|
+
*/
|
|
173
|
+
function updateSignal(
|
|
174
|
+
window: SignalWindow,
|
|
175
|
+
percent: number | null,
|
|
176
|
+
thresholdPercent: number,
|
|
177
|
+
clearThresholdPercent: number,
|
|
178
|
+
): boolean {
|
|
179
|
+
if (percent === null) {
|
|
180
|
+
resetSignalWindow(window);
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
window.overThreshold.push(percent >= thresholdPercent);
|
|
185
|
+
if (window.overThreshold.length > RESOURCE_PRESSURE_WINDOW_SAMPLES) {
|
|
186
|
+
window.overThreshold.shift();
|
|
187
|
+
}
|
|
188
|
+
if (percent < clearThresholdPercent) {
|
|
189
|
+
window.consecutiveBelowClear += 1;
|
|
190
|
+
} else {
|
|
191
|
+
window.consecutiveBelowClear = 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (window.elevated) {
|
|
195
|
+
// Hysteresis: hold until enough consecutive samples land below the clear
|
|
196
|
+
// threshold.
|
|
197
|
+
if (
|
|
198
|
+
window.consecutiveBelowClear >=
|
|
199
|
+
RESOURCE_PRESSURE_CLEAR_CONSECUTIVE_SAMPLES
|
|
200
|
+
) {
|
|
201
|
+
window.elevated = false;
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
const overCount = window.overThreshold.filter(Boolean).length;
|
|
205
|
+
window.elevated =
|
|
206
|
+
window.overThreshold.length >= RESOURCE_PRESSURE_WINDOW_SAMPLES &&
|
|
207
|
+
overCount >= RESOURCE_PRESSURE_ENTER_SAMPLES;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return window.elevated;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function defaultSampleCpuPercent(): number | null {
|
|
214
|
+
if (getContainerCpuCores() <= 0) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
// Average the rolling sampler's 5s windows across this guard's own 30s
|
|
218
|
+
// cadence: reading one instantaneous value would let a short spike that
|
|
219
|
+
// happens to align with the guard's ticks alias into "sustained" load
|
|
220
|
+
// (and an out-of-phase one be missed). Null while the sampler is still
|
|
221
|
+
// warming up or CPU accounting is unreadable: a placeholder 0% would look
|
|
222
|
+
// like a real measurement and mask the signal being unavailable.
|
|
223
|
+
return getAverageContainerCpuPercentOrNull(
|
|
224
|
+
RESOURCE_PRESSURE_SAMPLE_INTERVAL_MS,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function defaultSampleMemory(): ResourcePressureMemorySample | null {
|
|
229
|
+
const limitBytes = getContainerMemoryLimitBytes();
|
|
230
|
+
const usageBytes = getContainerMemoryUsageBytes();
|
|
231
|
+
if (limitBytes === null || limitBytes <= 0 || usageBytes === null) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
// Raw usage includes cache the kernel can drop under pressure; counting
|
|
235
|
+
// it would false-positive, so the working set subtracts the READILY
|
|
236
|
+
// reclaimable part: inactive file pages plus reclaimable slab. The full
|
|
237
|
+
// `file` counter is not used because active file pages (hot mmaps, a hot
|
|
238
|
+
// cache) are not disposable and belong in the working set. When the
|
|
239
|
+
// breakdown is unreadable (cgroups v1, where memory.stat lives
|
|
240
|
+
// elsewhere, or missing v2 counters) the working set is unknowable:
|
|
241
|
+
// report the signal unavailable rather than guessing.
|
|
242
|
+
const stat = getContainerMemoryStat();
|
|
243
|
+
const inactiveFileBytes = stat?.inactiveFileBytes ?? null;
|
|
244
|
+
if (inactiveFileBytes === null) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
usageBytes,
|
|
249
|
+
limitBytes,
|
|
250
|
+
reclaimableBytes: inactiveFileBytes + (stat?.slabReclaimableBytes ?? 0),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function startResourcePressureGuard(): ResourcePressureStatus {
|
|
255
|
+
if (!getIsPlatform()) {
|
|
256
|
+
return cloneStatus(state.status);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
ensureEnabledStatus();
|
|
260
|
+
|
|
261
|
+
if (!state.timer) {
|
|
262
|
+
state.timer = setInterval(() => {
|
|
263
|
+
evaluateResourcePressureNow();
|
|
264
|
+
}, RESOURCE_PRESSURE_SAMPLE_INTERVAL_MS);
|
|
265
|
+
(state.timer as { unref?: () => void }).unref?.();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return cloneStatus(state.status);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function stopResourcePressureGuard(): void {
|
|
272
|
+
if (!state.timer) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
clearInterval(state.timer);
|
|
276
|
+
state.timer = null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function evaluateResourcePressureNow(
|
|
280
|
+
deps?: ResourcePressureSamplers,
|
|
281
|
+
): ResourcePressureStatus {
|
|
282
|
+
if (!getIsPlatform()) {
|
|
283
|
+
return cloneStatus(state.status);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
ensureEnabledStatus();
|
|
287
|
+
|
|
288
|
+
const sampleCpuPercent = deps?.sampleCpuPercent ?? defaultSampleCpuPercent;
|
|
289
|
+
const sampleMemory = deps?.sampleMemory ?? defaultSampleMemory;
|
|
290
|
+
|
|
291
|
+
// A sampler returning null means the signal is legitimately unavailable
|
|
292
|
+
// (e.g. no cgroup limit) and is not an error; only a throwing sampler
|
|
293
|
+
// contributes to sampleError.
|
|
294
|
+
const sampleErrors: string[] = [];
|
|
295
|
+
|
|
296
|
+
let cpuPercent: number | null = null;
|
|
297
|
+
try {
|
|
298
|
+
cpuPercent = sampleCpuPercent();
|
|
299
|
+
} catch (error) {
|
|
300
|
+
sampleErrors.push(`CPU sample failed: ${formatError(error)}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let memoryPercent: number | null = null;
|
|
304
|
+
try {
|
|
305
|
+
const memorySample = sampleMemory();
|
|
306
|
+
if (memorySample && memorySample.limitBytes > 0) {
|
|
307
|
+
const workingSetBytes = Math.max(
|
|
308
|
+
0,
|
|
309
|
+
memorySample.usageBytes - memorySample.reclaimableBytes,
|
|
310
|
+
);
|
|
311
|
+
memoryPercent = roundPercent(
|
|
312
|
+
(workingSetBytes / memorySample.limitBytes) * 100,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
} catch (error) {
|
|
316
|
+
sampleErrors.push(`Memory sample failed: ${formatError(error)}`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const sampleError = sampleErrors.length > 0 ? sampleErrors.join("; ") : null;
|
|
320
|
+
|
|
321
|
+
if (cpuPercent === null && memoryPercent === null) {
|
|
322
|
+
resetSignalWindow(state.cpuWindow);
|
|
323
|
+
resetSignalWindow(state.memoryWindow);
|
|
324
|
+
return replaceStatus(
|
|
325
|
+
sampleFailureStatus(
|
|
326
|
+
sampleError ?? "Resource pressure samples unavailable",
|
|
327
|
+
),
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Log on the transition into a failing state, not on every 30s sample. The
|
|
332
|
+
// status fingerprint includes `error`, so the SSE broadcast below reflects
|
|
333
|
+
// the same transition exactly once.
|
|
334
|
+
if (sampleError !== null && sampleError !== state.status.error) {
|
|
335
|
+
log.warn(
|
|
336
|
+
{ error: sampleError },
|
|
337
|
+
"Resource pressure sampler failed; the remaining signal still evaluated",
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const cpuElevated = updateSignal(
|
|
342
|
+
state.cpuWindow,
|
|
343
|
+
cpuPercent,
|
|
344
|
+
CPU_PRESSURE_THRESHOLD_PERCENT,
|
|
345
|
+
CPU_PRESSURE_CLEAR_THRESHOLD_PERCENT,
|
|
346
|
+
);
|
|
347
|
+
const memoryElevated = updateSignal(
|
|
348
|
+
state.memoryWindow,
|
|
349
|
+
memoryPercent,
|
|
350
|
+
MEMORY_PRESSURE_THRESHOLD_PERCENT,
|
|
351
|
+
MEMORY_PRESSURE_CLEAR_THRESHOLD_PERCENT,
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
return replaceStatus({
|
|
355
|
+
...OK_STATUS,
|
|
356
|
+
state: cpuElevated || memoryElevated ? "elevated" : "ok",
|
|
357
|
+
cpuPercent,
|
|
358
|
+
memoryPercent,
|
|
359
|
+
cpuElevated,
|
|
360
|
+
memoryElevated,
|
|
361
|
+
lastCheckedAt: new Date().toISOString(),
|
|
362
|
+
error: sampleError,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function getResourcePressureStatus(): ResourcePressureStatus {
|
|
367
|
+
if (!getIsPlatform()) {
|
|
368
|
+
return cloneStatus(DISABLED_STATUS);
|
|
369
|
+
}
|
|
370
|
+
if (!state.status.enabled) {
|
|
371
|
+
return cloneStatus(OK_STATUS);
|
|
372
|
+
}
|
|
373
|
+
return cloneStatus(state.status);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function __resetResourcePressureGuardForTests(): void {
|
|
377
|
+
stopResourcePressureGuard();
|
|
378
|
+
state.status = cloneStatus(DISABLED_STATUS);
|
|
379
|
+
resetSignalWindow(state.cpuWindow);
|
|
380
|
+
resetSignalWindow(state.memoryWindow);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function __getResourcePressureGuardTimerForTests(): ReturnType<
|
|
384
|
+
typeof setInterval
|
|
385
|
+
> | null {
|
|
386
|
+
return state.timer;
|
|
387
|
+
}
|
|
@@ -38,6 +38,7 @@ import { isStartupComplete } from "./daemon-readiness.js";
|
|
|
38
38
|
import { stopDiskPressureGuardForLifecycle } from "./disk-pressure-guard-lifecycle.js";
|
|
39
39
|
import { stopEventLoopWatchdog } from "./event-loop-watchdog.js";
|
|
40
40
|
import { stopOrphanReaper } from "./orphan-reaper.js";
|
|
41
|
+
import { stopResourcePressureGuardForLifecycle } from "./resource-pressure-guard-lifecycle.js";
|
|
41
42
|
|
|
42
43
|
const log = getLogger("lifecycle");
|
|
43
44
|
|
|
@@ -49,6 +50,7 @@ const log = getLogger("lifecycle");
|
|
|
49
50
|
function stopBackgroundServicesAndCleanupPidFile(): void {
|
|
50
51
|
stopGatewayFlagListener();
|
|
51
52
|
stopDiskPressureGuardForLifecycle();
|
|
53
|
+
stopResourcePressureGuardForLifecycle();
|
|
52
54
|
stopOrphanReaper();
|
|
53
55
|
stopEventLoopWatchdog();
|
|
54
56
|
cleanupPidFile();
|
|
@@ -23,6 +23,22 @@ export interface DaemonStartupError {
|
|
|
23
23
|
|
|
24
24
|
const DAEMON_ERROR_PREFIX = "DAEMON_ERROR:";
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Build an `EADDRINUSE`-coded error so callers (and {@link categorizeDaemonError})
|
|
28
|
+
* can branch on `err.code` and surface the structured "already running"
|
|
29
|
+
* guidance instead of a generic UNKNOWN. Used by every transport that detects
|
|
30
|
+
* an occupied address itself rather than receiving a kernel-coded error.
|
|
31
|
+
*/
|
|
32
|
+
export function makeAddrInUseError(
|
|
33
|
+
message: string,
|
|
34
|
+
cause?: unknown,
|
|
35
|
+
): NodeJS.ErrnoException {
|
|
36
|
+
const err = new Error(message, cause === undefined ? undefined : { cause });
|
|
37
|
+
const errno = err as NodeJS.ErrnoException;
|
|
38
|
+
errno.code = "EADDRINUSE";
|
|
39
|
+
return errno;
|
|
40
|
+
}
|
|
41
|
+
|
|
26
42
|
/**
|
|
27
43
|
* Inspect an error and return a categorized {@link DaemonStartupError}.
|
|
28
44
|
*/
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type { ChannelConversationType } from "@vellumai/gateway-client";
|
|
8
8
|
|
|
9
9
|
import { isHttpAuthDisabled } from "../config/env.js";
|
|
10
|
-
import {
|
|
10
|
+
import { canSeePersonalMemory } from "../runtime/effective-capabilities.js";
|
|
11
11
|
import type { TrustClass } from "../runtime/trust-class.js";
|
|
12
12
|
import type { TrustContext } from "./trust-context-types.js";
|
|
13
13
|
|
|
@@ -67,21 +67,25 @@ export function resolveTrustClass(
|
|
|
67
67
|
* Whether personal-memory content may be surfaced for the actor described by
|
|
68
68
|
* `trustContext`: the gate admits guardian-class actors and internal/local
|
|
69
69
|
* flows (including turns with no trust context), and blocks remote untrusted
|
|
70
|
-
* actors
|
|
70
|
+
* actors.
|
|
71
71
|
*
|
|
72
72
|
* This is THE personal-memory trust gate. Every surface that exposes private
|
|
73
73
|
* user content — the v2 dynamic/static `<memory>` layers, PKB context, NOW.md,
|
|
74
74
|
* memory-v3 cards/spotlight, and the `loadFromDb` rehydration of persisted
|
|
75
75
|
* memory blocks — must call this one helper so the exposure rule cannot drift
|
|
76
|
-
* between copies.
|
|
77
|
-
*
|
|
76
|
+
* between copies.
|
|
77
|
+
*
|
|
78
|
+
* The trust-context binding lives here; the policy itself is
|
|
79
|
+
* {@link canSeePersonalMemory}, alongside the other capability-plus-context
|
|
80
|
+
* compositions. This adapter folds in {@link resolveTrustClass} so the
|
|
81
|
+
* dev-bypass (HTTP auth disabled → guardian) applies uniformly at every call
|
|
82
|
+
* site.
|
|
78
83
|
*/
|
|
79
84
|
export function isPersonalMemoryAllowed(
|
|
80
85
|
trustContext: TrustContext | undefined,
|
|
81
86
|
): boolean {
|
|
82
|
-
return
|
|
83
|
-
|
|
84
|
-
isTrustedActor: resolveTrustClass(trustContext) === "guardian",
|
|
87
|
+
return canSeePersonalMemory({
|
|
88
|
+
trustClass: resolveTrustClass(trustContext),
|
|
85
89
|
});
|
|
86
90
|
}
|
|
87
91
|
|
|
@@ -109,9 +113,9 @@ export function mapChatTypeToConversationType(
|
|
|
109
113
|
// it for the axis rather than inventing a native-looking word.
|
|
110
114
|
case "dm":
|
|
111
115
|
return "dm";
|
|
112
|
-
// "mpim" is Slack's multi-party DM
|
|
113
|
-
//
|
|
114
|
-
//
|
|
116
|
+
// "mpim" is Slack's multi-party DM, forwarded under its own name rather
|
|
117
|
+
// than reported as "im": a room with several people in it takes the
|
|
118
|
+
// tighter `private` cell and the group-chat etiquette, not the `dm` one.
|
|
115
119
|
case "mpim":
|
|
116
120
|
case "group": // Telegram group
|
|
117
121
|
case "supergroup": // Telegram supergroup
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript notice for images a turn cannot show the model.
|
|
3
|
+
*
|
|
4
|
+
* Providers accept exactly four image formats (PNG, JPEG, GIF, WebP), so an
|
|
5
|
+
* attachment whose bytes are anything else (a corrupt payload, or a HEIC/AVIF
|
|
6
|
+
* file carrying a `.png` name) is dropped at the provider send boundary by
|
|
7
|
+
* `resolveMediaReferences`. Dropping is what keeps the rest of the turn alive,
|
|
8
|
+
* but on its own it is silent: the user sees their image in the transcript and
|
|
9
|
+
* a reply that ignores it. The card names the files, so the removal is visible
|
|
10
|
+
* and actionable (convert and re-attach) instead of a mystery.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { isUnsendableImageSource } from "../providers/media-resolve.js";
|
|
14
|
+
import type { MediaSource } from "../providers/types.js";
|
|
15
|
+
import { persistSystemCard } from "../runtime/routes/canned-message-complete.js";
|
|
16
|
+
import { getLogger } from "../util/logger.js";
|
|
17
|
+
|
|
18
|
+
const log = getLogger("unsendable-image-notice");
|
|
19
|
+
|
|
20
|
+
/** An image attachment of a persisted message, as the card check sees it. */
|
|
21
|
+
export interface AttachedImage {
|
|
22
|
+
filename: string;
|
|
23
|
+
source: MediaSource;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The attachments the send boundary will drop, by filename. */
|
|
27
|
+
export function unsendableImageFilenames(images: AttachedImage[]): string[] {
|
|
28
|
+
return images
|
|
29
|
+
.filter((image) => isUnsendableImageSource(image.source))
|
|
30
|
+
.map((image) => image.filename);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Transcript copy for the images this message cannot send to the model. */
|
|
34
|
+
export function unsendableImageCardText(filenames: string[]): string {
|
|
35
|
+
const names = filenames.join(", ");
|
|
36
|
+
return filenames.length === 1
|
|
37
|
+
? `${names} was not sent to the model: the file is not a PNG, JPEG, GIF, or WebP image. Convert it and attach it again to include it.`
|
|
38
|
+
: `${names} were not sent to the model: the files are not PNG, JPEG, GIF, or WebP images. Convert them and attach them again to include them.`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Post a card naming the attachments of a just-persisted message that the send
|
|
43
|
+
* boundary will drop, or do nothing when every attachment is readable.
|
|
44
|
+
*
|
|
45
|
+
* The card is non-terminal: it rides the messages-changed invalidation without
|
|
46
|
+
* a `message_complete`, so posting it while the turn it describes is in flight
|
|
47
|
+
* leaves that turn's streaming state untouched. Failures are logged and
|
|
48
|
+
* swallowed: the turn is already usable without the notice, and losing the
|
|
49
|
+
* user's whole message to a card write is a worse outcome than losing the card.
|
|
50
|
+
*/
|
|
51
|
+
export async function postUnsendableImageNotice(
|
|
52
|
+
conversationId: string,
|
|
53
|
+
images: AttachedImage[],
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
const filenames = unsendableImageFilenames(images);
|
|
56
|
+
if (filenames.length === 0) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
log.warn(
|
|
60
|
+
{ conversationId, filenames },
|
|
61
|
+
"User attachments are not a provider-readable image format; posting notice",
|
|
62
|
+
);
|
|
63
|
+
try {
|
|
64
|
+
await persistSystemCard({
|
|
65
|
+
conversationId,
|
|
66
|
+
text: unsendableImageCardText(filenames),
|
|
67
|
+
metadata: { unsendableImageFilenames: filenames },
|
|
68
|
+
endsTurn: false,
|
|
69
|
+
});
|
|
70
|
+
} catch (err) {
|
|
71
|
+
log.error(
|
|
72
|
+
{ conversationId, err },
|
|
73
|
+
"Failed to post unsendable-image notice",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|