@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,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log-field builder for provider rejections that carry no actionable
|
|
3
|
+
* classification of their own (unclassified 4xx).
|
|
4
|
+
*
|
|
5
|
+
* The extracted fields on `ProviderError` (`apiErrorCode`, `message`) drop the
|
|
6
|
+
* detail an operator needs to find the offending request: which request field,
|
|
7
|
+
* which message, which byte pattern. The verbatim upstream body carries it, so
|
|
8
|
+
* it rides the log record, bounded so a large provider error page cannot flood
|
|
9
|
+
* the log stream.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { ProviderError } from "../util/errors.js";
|
|
13
|
+
import { redactLogString } from "../util/log-redact.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Ceiling for the logged upstream body. Bodies are already capped at 16 KiB
|
|
17
|
+
* when captured (`providers/openai/api-error-normalization.ts`); this second,
|
|
18
|
+
* tighter bound is what a log record carries.
|
|
19
|
+
*/
|
|
20
|
+
export const MAX_LOGGED_UPSTREAM_BODY_CHARS = 4096;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Upstream error codes whose message names the offending request content part,
|
|
24
|
+
* e.g. `Invalid 'input[191].content[1].text': string too long`. Kept narrow:
|
|
25
|
+
* codes outside this set have no such pointer, so parsing their message would
|
|
26
|
+
* only produce coincidental matches.
|
|
27
|
+
*/
|
|
28
|
+
const CONTENT_PART_POINTER_CODES = new Set([
|
|
29
|
+
"string_above_max_length",
|
|
30
|
+
"invalid_body",
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Positional pointer into the request payload. `input[N]` is the Responses API
|
|
35
|
+
* shape, `messages[N]` the Chat Completions shape; both index the request's
|
|
36
|
+
* message array in send order, so `N` is an offset into the conversation
|
|
37
|
+
* history that produced the request.
|
|
38
|
+
*/
|
|
39
|
+
const CONTENT_PART_POINTER = /\b(?:input|messages)\[(\d+)\]\.content\[(\d+)\]/i;
|
|
40
|
+
|
|
41
|
+
export interface ProviderRejectionLogFields {
|
|
42
|
+
/** Verbatim upstream non-2xx body, truncated to the logged ceiling. */
|
|
43
|
+
upstreamErrorBody: string | null;
|
|
44
|
+
/** True when the body was cut to fit the ceiling. */
|
|
45
|
+
upstreamErrorBodyTruncated?: boolean;
|
|
46
|
+
apiErrorCode?: string;
|
|
47
|
+
apiErrorType?: string;
|
|
48
|
+
apiErrorParam?: string;
|
|
49
|
+
requestId?: string;
|
|
50
|
+
/** Index of the offending message within the request's message array. */
|
|
51
|
+
offendingMessageIndex?: number;
|
|
52
|
+
/** Index of the offending content part within that message. */
|
|
53
|
+
offendingContentIndex?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Extract the offending `input[N].content[M]` coordinates from an upstream
|
|
58
|
+
* error message, for the codes that carry such a pointer.
|
|
59
|
+
*/
|
|
60
|
+
function parseContentPartPointer(
|
|
61
|
+
error: ProviderError,
|
|
62
|
+
): Pick<
|
|
63
|
+
ProviderRejectionLogFields,
|
|
64
|
+
"offendingMessageIndex" | "offendingContentIndex"
|
|
65
|
+
> {
|
|
66
|
+
const code = error.apiErrorCode?.toLowerCase();
|
|
67
|
+
if (!code || !CONTENT_PART_POINTER_CODES.has(code)) {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
const match = CONTENT_PART_POINTER.exec(
|
|
71
|
+
`${error.message} ${error.rawBody ?? ""}`,
|
|
72
|
+
);
|
|
73
|
+
if (!match) {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
const messageIndex = Number(match[1]);
|
|
77
|
+
const contentIndex = Number(match[2]);
|
|
78
|
+
if (
|
|
79
|
+
!Number.isSafeInteger(messageIndex) ||
|
|
80
|
+
!Number.isSafeInteger(contentIndex)
|
|
81
|
+
) {
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
offendingMessageIndex: messageIndex,
|
|
86
|
+
offendingContentIndex: contentIndex,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Diagnostic fields describing what the provider actually rejected. */
|
|
91
|
+
export function buildProviderRejectionLogFields(
|
|
92
|
+
error: Error,
|
|
93
|
+
): ProviderRejectionLogFields {
|
|
94
|
+
if (!(error instanceof ProviderError)) {
|
|
95
|
+
return { upstreamErrorBody: null };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The body is arbitrary upstream text under a custom field name, so it is
|
|
99
|
+
// outside the reach of the pino `err`/`req`/`res` serializers and is scrubbed
|
|
100
|
+
// here before it reaches the record.
|
|
101
|
+
const rawBody =
|
|
102
|
+
error.rawBody === undefined ? undefined : redactLogString(error.rawBody);
|
|
103
|
+
const truncated =
|
|
104
|
+
rawBody !== undefined && rawBody.length > MAX_LOGGED_UPSTREAM_BODY_CHARS;
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
upstreamErrorBody:
|
|
108
|
+
rawBody === undefined
|
|
109
|
+
? null
|
|
110
|
+
: rawBody.slice(0, MAX_LOGGED_UPSTREAM_BODY_CHARS),
|
|
111
|
+
...(truncated ? { upstreamErrorBodyTruncated: true } : {}),
|
|
112
|
+
...(error.apiErrorCode !== undefined
|
|
113
|
+
? { apiErrorCode: error.apiErrorCode }
|
|
114
|
+
: {}),
|
|
115
|
+
...(error.apiErrorType !== undefined
|
|
116
|
+
? { apiErrorType: error.apiErrorType }
|
|
117
|
+
: {}),
|
|
118
|
+
...(error.apiErrorParam !== undefined
|
|
119
|
+
? { apiErrorParam: error.apiErrorParam }
|
|
120
|
+
: {}),
|
|
121
|
+
...(error.requestId !== undefined ? { requestId: error.requestId } : {}),
|
|
122
|
+
...parseContentPartPointer(error),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource-pressure guard wiring for the daemon lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Starts the guard at boot and defers the first sample onto a macrotask so it
|
|
5
|
+
* never blocks startup, and stops the guard (cancelling any pending deferred
|
|
6
|
+
* sample) on shutdown. When the guard is disabled (non-platform), no deferred
|
|
7
|
+
* sample is scheduled.
|
|
8
|
+
*/
|
|
9
|
+
import { getLogger } from "../util/logger.js";
|
|
10
|
+
import {
|
|
11
|
+
evaluateResourcePressureNow,
|
|
12
|
+
startResourcePressureGuard,
|
|
13
|
+
stopResourcePressureGuard,
|
|
14
|
+
} from "./resource-pressure-guard.js";
|
|
15
|
+
|
|
16
|
+
const log = getLogger("resource-pressure-guard-lifecycle");
|
|
17
|
+
|
|
18
|
+
let resourcePressureStartupSampleTimer: ReturnType<typeof setTimeout> | null =
|
|
19
|
+
null;
|
|
20
|
+
|
|
21
|
+
function runDeferredResourcePressureStartupSample(): void {
|
|
22
|
+
resourcePressureStartupSampleTimer = null;
|
|
23
|
+
try {
|
|
24
|
+
const status = evaluateResourcePressureNow();
|
|
25
|
+
if (status.error) {
|
|
26
|
+
log.warn(
|
|
27
|
+
{ error: status.error },
|
|
28
|
+
"Resource pressure guard sample failed during startup, continuing",
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
log.warn(
|
|
33
|
+
{ err },
|
|
34
|
+
"Resource pressure guard failed during startup, continuing",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function startResourcePressureGuardForLifecycle(): void {
|
|
40
|
+
try {
|
|
41
|
+
const startedStatus = startResourcePressureGuard();
|
|
42
|
+
if (!startedStatus.enabled) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (!resourcePressureStartupSampleTimer) {
|
|
46
|
+
resourcePressureStartupSampleTimer = setTimeout(
|
|
47
|
+
runDeferredResourcePressureStartupSample,
|
|
48
|
+
0,
|
|
49
|
+
);
|
|
50
|
+
(resourcePressureStartupSampleTimer as { unref?: () => void }).unref?.();
|
|
51
|
+
}
|
|
52
|
+
} catch (err) {
|
|
53
|
+
log.warn(
|
|
54
|
+
{ err },
|
|
55
|
+
"Resource pressure guard failed during startup, continuing",
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function stopResourcePressureGuardForLifecycle(): void {
|
|
61
|
+
if (resourcePressureStartupSampleTimer) {
|
|
62
|
+
clearTimeout(resourcePressureStartupSampleTimer);
|
|
63
|
+
resourcePressureStartupSampleTimer = null;
|
|
64
|
+
}
|
|
65
|
+
stopResourcePressureGuard();
|
|
66
|
+
}
|
|
@@ -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
|