@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
|
@@ -160,6 +160,13 @@ function cachedPrefixBlock(text: string): ContentBlock {
|
|
|
160
160
|
} as unknown as ContentBlock;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/**
|
|
164
|
+
* A 1x1 PNG. The send boundary validates image bytes before serialization, so
|
|
165
|
+
* an image fixture has to carry a real signature to reach the wire.
|
|
166
|
+
*/
|
|
167
|
+
const PNG_B64 =
|
|
168
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFAAH/q842iQAAAABJRU5ErkJggg==";
|
|
169
|
+
|
|
163
170
|
const sampleTools: ToolDefinition[] = [
|
|
164
171
|
{
|
|
165
172
|
name: "file_read",
|
|
@@ -2754,7 +2761,7 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
|
|
|
2754
2761
|
source: {
|
|
2755
2762
|
type: "base64",
|
|
2756
2763
|
media_type: "image/png",
|
|
2757
|
-
data:
|
|
2764
|
+
data: PNG_B64,
|
|
2758
2765
|
},
|
|
2759
2766
|
},
|
|
2760
2767
|
{ type: "text", text: "extra error detail" },
|
|
@@ -2806,7 +2813,7 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
|
|
|
2806
2813
|
source: {
|
|
2807
2814
|
type: "base64",
|
|
2808
2815
|
media_type: "image/png",
|
|
2809
|
-
data:
|
|
2816
|
+
data: PNG_B64,
|
|
2810
2817
|
},
|
|
2811
2818
|
},
|
|
2812
2819
|
],
|
|
@@ -2853,7 +2860,7 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
|
|
|
2853
2860
|
source: {
|
|
2854
2861
|
type: "base64",
|
|
2855
2862
|
media_type: "image/png",
|
|
2856
|
-
data:
|
|
2863
|
+
data: PNG_B64,
|
|
2857
2864
|
},
|
|
2858
2865
|
},
|
|
2859
2866
|
],
|
|
@@ -188,6 +188,46 @@ describe("approval interception trust-class gates", () => {
|
|
|
188
188
|
);
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
+
test("the principal-gate rejection stays visible only to the actor on Slack", async () => {
|
|
192
|
+
// A rejection that loses its audience is posted to the whole channel,
|
|
193
|
+
// telling everyone present that someone failed a guardian check. The
|
|
194
|
+
// audience is one object rather than a flag beside an id so it cannot be
|
|
195
|
+
// half-dropped, and this pins that it is actually sent.
|
|
196
|
+
_anchorPrincipalId = "the-real-guardian-principal";
|
|
197
|
+
registerPendingInteraction(
|
|
198
|
+
"req-guardian-mismatch-slack",
|
|
199
|
+
CONVERSATION_ID,
|
|
200
|
+
TOOL_NAME,
|
|
201
|
+
TOOL_INPUT,
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
await handleApprovalInterception({
|
|
205
|
+
conversationId: CONVERSATION_ID,
|
|
206
|
+
callbackData: "apr:req-guardian-mismatch-slack:approve_once",
|
|
207
|
+
content: "",
|
|
208
|
+
conversationExternalId: "C0SLACKROOM",
|
|
209
|
+
sourceChannel: "slack",
|
|
210
|
+
actorExternalId: "U0STALEGUARDIAN",
|
|
211
|
+
replyCallbackUrl: "https://gateway.test/deliver",
|
|
212
|
+
trustCtx: {
|
|
213
|
+
sourceChannel: "slack",
|
|
214
|
+
trustClass: "guardian",
|
|
215
|
+
requesterExternalUserId: "U0STALEGUARDIAN",
|
|
216
|
+
guardianExternalUserId: "U0STALEGUARDIAN",
|
|
217
|
+
guardianPrincipalId: "some-other-principal",
|
|
218
|
+
},
|
|
219
|
+
assistantId: ASSISTANT_ID,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(deliverSpy).toHaveBeenCalledWith(
|
|
223
|
+
"https://gateway.test/deliver",
|
|
224
|
+
expect.objectContaining({
|
|
225
|
+
text: "Sorry, I couldn't process that. Please try again.",
|
|
226
|
+
audience: { kind: "oneReader", userId: "U0STALEGUARDIAN" },
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
|
|
191
231
|
test("guardian without a resolved acting principal is rejected before any decision", async () => {
|
|
192
232
|
// Address-only guardian classification (e.g. a binding row with a null
|
|
193
233
|
// principal) must not authorize decisions.
|
|
@@ -99,7 +99,7 @@ mock.module("../persistence/conversation-crud.js", () => ({
|
|
|
99
99
|
parseConversation: (row: unknown) => row,
|
|
100
100
|
provenanceFromTrustContext: () => ({ source: "user" }),
|
|
101
101
|
relinkAttachments: mock(() => {}),
|
|
102
|
-
|
|
102
|
+
selectProviderMetaCandidateMetadata: () => null,
|
|
103
103
|
setConversationOriginChannelIfUnset: mock(() => {}),
|
|
104
104
|
setConversationOriginInterfaceIfUnset: mock(() => {}),
|
|
105
105
|
setConversationInferenceProfile: mock(() => {}),
|
|
@@ -1614,6 +1614,10 @@ describe("background channel processing approval prompts", () => {
|
|
|
1614
1614
|
| { requestId?: string }
|
|
1615
1615
|
| undefined;
|
|
1616
1616
|
expect(approvalMeta?.requestId).toBe("req-bg-1");
|
|
1617
|
+
// Telegram's chat is already one-to-one, so the prompt stays where the
|
|
1618
|
+
// turn is. Slack, where it does not, is covered in
|
|
1619
|
+
// guardian-prompt-notice-privacy.test.ts.
|
|
1620
|
+
expect(deliverPromptSpy.mock.calls[0]?.[1]).toBe("chat-123");
|
|
1617
1621
|
|
|
1618
1622
|
deliverPromptSpy.mockRestore();
|
|
1619
1623
|
});
|
|
@@ -250,7 +250,7 @@ describe("channel inbound disk pressure gate", () => {
|
|
|
250
250
|
|
|
251
251
|
expect(body).toMatchObject({
|
|
252
252
|
accepted: true,
|
|
253
|
-
|
|
253
|
+
reaction: "dropped_disk_pressure",
|
|
254
254
|
diskPressure: "blocked",
|
|
255
255
|
reason: "trusted-contact",
|
|
256
256
|
});
|
|
@@ -268,9 +268,9 @@ describe("channel inbound disk pressure gate", () => {
|
|
|
268
268
|
eq(channelInboundEvents.externalMessageId, "slack-reaction-blocked"),
|
|
269
269
|
)
|
|
270
270
|
.get();
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
expect(event
|
|
271
|
+
// A blocked reaction writes nothing at all: it is dropped before the
|
|
272
|
+
// reacted message is resolved, so no event row is opened either.
|
|
273
|
+
expect(event).toBeUndefined();
|
|
274
274
|
expect(db.select().from(messages).all()).toHaveLength(0);
|
|
275
275
|
});
|
|
276
276
|
|
|
@@ -414,8 +414,7 @@ describe("channel inbound disk pressure gate", () => {
|
|
|
414
414
|
chatId: "slack-channel-1",
|
|
415
415
|
text: expectedRemoteBlockReply,
|
|
416
416
|
assistantId: "self",
|
|
417
|
-
|
|
418
|
-
user: "slack-user-1",
|
|
417
|
+
audience: { kind: "oneReader", userId: "slack-user-1" },
|
|
419
418
|
},
|
|
420
419
|
],
|
|
421
420
|
]);
|
|
@@ -58,6 +58,19 @@ const renderedHistoryContentQueue: RenderedHistoryStub[] = [];
|
|
|
58
58
|
|
|
59
59
|
let deliveryFailAtIndex = -1;
|
|
60
60
|
|
|
61
|
+
const editCalls: { callbackUrl: string; target: Record<string, unknown> }[] =
|
|
62
|
+
[];
|
|
63
|
+
|
|
64
|
+
mock.module("../messaging/providers/index.js", () => ({
|
|
65
|
+
editChannelMessage: async (
|
|
66
|
+
callbackUrl: string,
|
|
67
|
+
target: Record<string, unknown>,
|
|
68
|
+
) => {
|
|
69
|
+
editCalls.push({ callbackUrl, target });
|
|
70
|
+
return { ok: true };
|
|
71
|
+
},
|
|
72
|
+
}));
|
|
73
|
+
|
|
61
74
|
mock.module("../runtime/gateway-client.js", () => ({
|
|
62
75
|
deliverChannelReply: async (
|
|
63
76
|
callbackUrl: string,
|
|
@@ -782,31 +795,32 @@ describe("channel-reply-delivery", () => {
|
|
|
782
795
|
chatId: "chat-live",
|
|
783
796
|
attachments,
|
|
784
797
|
assistantId: undefined,
|
|
785
|
-
|
|
786
|
-
user: undefined,
|
|
787
|
-
messageTs: "1700000000.000055",
|
|
798
|
+
audience: undefined,
|
|
788
799
|
});
|
|
800
|
+
// Attachments post as new messages, so nothing is edited on this path.
|
|
801
|
+
expect(editCalls).toHaveLength(0);
|
|
789
802
|
expect(seenTs).toEqual(["1700000000.000055"]);
|
|
790
803
|
});
|
|
791
804
|
|
|
792
|
-
it("
|
|
805
|
+
it("carries the audience through to every delivery call", async () => {
|
|
806
|
+
const audience = { kind: "oneReader", userId: "U456" } as const;
|
|
793
807
|
await deliverRenderedReplyViaCallback({
|
|
794
808
|
callbackUrl: "http://gateway/deliver/slack",
|
|
795
809
|
chatId: "C123",
|
|
796
810
|
textSegments: ["Part 1.", "Part 2."],
|
|
797
811
|
interSegmentDelayMs: 0,
|
|
798
|
-
|
|
799
|
-
user: "U456",
|
|
812
|
+
audience,
|
|
800
813
|
});
|
|
801
814
|
|
|
802
815
|
expect(deliveryCalls).toHaveLength(2);
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
816
|
+
// Every segment, not just the first: a reply restricted to one reader
|
|
817
|
+
// that loses the restriction partway becomes a public one.
|
|
818
|
+
for (const call of deliveryCalls) {
|
|
819
|
+
expect(call.payload.audience).toEqual(audience);
|
|
820
|
+
}
|
|
807
821
|
});
|
|
808
822
|
|
|
809
|
-
it("
|
|
823
|
+
it("leaves the audience unset when the reply is for the room", async () => {
|
|
810
824
|
await deliverRenderedReplyViaCallback({
|
|
811
825
|
callbackUrl: "http://gateway/deliver/slack",
|
|
812
826
|
chatId: "C123",
|
|
@@ -815,8 +829,7 @@ describe("channel-reply-delivery", () => {
|
|
|
815
829
|
});
|
|
816
830
|
|
|
817
831
|
expect(deliveryCalls).toHaveLength(1);
|
|
818
|
-
expect(deliveryCalls[0].payload.
|
|
819
|
-
expect(deliveryCalls[0].payload.user).toBeUndefined();
|
|
832
|
+
expect(deliveryCalls[0].payload.audience).toBeUndefined();
|
|
820
833
|
});
|
|
821
834
|
|
|
822
835
|
it("suppresses delivery when the only text segment is <no_response/>", async () => {
|
|
@@ -39,6 +39,7 @@ mock.module("../util/logger.js", () => ({
|
|
|
39
39
|
|
|
40
40
|
import {
|
|
41
41
|
installIpcMock,
|
|
42
|
+
lastIpcParams,
|
|
42
43
|
mockIpcResponse,
|
|
43
44
|
} from "./helpers/gateway-classify-mock.js";
|
|
44
45
|
installIpcMock();
|
|
@@ -53,7 +54,8 @@ function mockRisk(
|
|
|
53
54
|
mockIpcResponse("classify_risk", {
|
|
54
55
|
risk,
|
|
55
56
|
reason: "test fixture",
|
|
56
|
-
matchType: "
|
|
57
|
+
matchType: "registry",
|
|
58
|
+
scopeOptions: [],
|
|
57
59
|
...extras,
|
|
58
60
|
});
|
|
59
61
|
}
|
|
@@ -82,8 +84,6 @@ import * as envRegistry from "../config/env-registry.js";
|
|
|
82
84
|
import {
|
|
83
85
|
check,
|
|
84
86
|
classifyRisk,
|
|
85
|
-
clearRiskCache,
|
|
86
|
-
generateAllowlistOptions,
|
|
87
87
|
generateScopeOptions,
|
|
88
88
|
SCOPE_AWARE_TOOLS,
|
|
89
89
|
} from "../permissions/checker.js";
|
|
@@ -180,8 +180,6 @@ describe("Permission Checker", () => {
|
|
|
180
180
|
mockIpcResponse("get_global_thresholds", DEFAULT_GATEWAY_THRESHOLDS);
|
|
181
181
|
// Clear the gateway threshold cache so each test gets a fresh threshold read
|
|
182
182
|
_clearGlobalCacheForTesting();
|
|
183
|
-
// Reset trust-store state and risk classification cache between tests
|
|
184
|
-
clearRiskCache();
|
|
185
183
|
// Reset guardian persona mock so each test opts in explicitly
|
|
186
184
|
mockGuardianPersonaPath = null;
|
|
187
185
|
loggerWarnCalls.length = 0;
|
|
@@ -447,268 +445,6 @@ describe("Permission Checker", () => {
|
|
|
447
445
|
});
|
|
448
446
|
});
|
|
449
447
|
|
|
450
|
-
// ── generateAllowlistOptions ───────────────────────────────────
|
|
451
|
-
|
|
452
|
-
describe("generateAllowlistOptions", () => {
|
|
453
|
-
test("file_write: generates prefixed file, ancestor directory wildcards, and tool wildcard", async () => {
|
|
454
|
-
const options = await generateAllowlistOptions("file_write", {
|
|
455
|
-
path: "/home/user/project/file.ts",
|
|
456
|
-
});
|
|
457
|
-
expect(options).toHaveLength(5);
|
|
458
|
-
// Patterns are prefixed with tool name to match check()'s "tool:path" format
|
|
459
|
-
expect(options[0].pattern).toBe("file_write:/home/user/project/file.ts");
|
|
460
|
-
expect(options[1].pattern).toBe("file_write:/home/user/project/**");
|
|
461
|
-
expect(options[2].pattern).toBe("file_write:/home/user/**");
|
|
462
|
-
expect(options[3].pattern).toBe("file_write:/home/**");
|
|
463
|
-
expect(options[4].pattern).toBe("file_write:*");
|
|
464
|
-
// Labels stay user-friendly
|
|
465
|
-
expect(options[0].label).toBe("/home/user/project/file.ts");
|
|
466
|
-
expect(options[1].label).toBe("/home/user/project/**");
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
test("file_read: generates prefixed file, directory, and tool wildcard", async () => {
|
|
470
|
-
const options = await generateAllowlistOptions("file_read", {
|
|
471
|
-
path: "/tmp/data.json",
|
|
472
|
-
});
|
|
473
|
-
expect(options).toHaveLength(3);
|
|
474
|
-
expect(options[0].pattern).toBe("file_read:/tmp/data.json");
|
|
475
|
-
expect(options[1].pattern).toBe("file_read:/tmp/**");
|
|
476
|
-
expect(options[2].pattern).toBe("file_read:*");
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
test("host_file_read: generates prefixed file, directory, and tool wildcard", async () => {
|
|
480
|
-
const options = await generateAllowlistOptions("host_file_read", {
|
|
481
|
-
path: "/etc/hosts",
|
|
482
|
-
});
|
|
483
|
-
expect(options).toHaveLength(3);
|
|
484
|
-
expect(options[0].pattern).toBe("host_file_read:/etc/hosts");
|
|
485
|
-
expect(options[1].pattern).toBe("host_file_read:/etc/**");
|
|
486
|
-
expect(options[2].pattern).toBe("host_file_read:*");
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
test("host_file_write with file_path key", async () => {
|
|
490
|
-
const options = await generateAllowlistOptions("host_file_write", {
|
|
491
|
-
file_path: "/tmp/out.txt",
|
|
492
|
-
});
|
|
493
|
-
expect(options[0].pattern).toBe("host_file_write:/tmp/out.txt");
|
|
494
|
-
expect(options[1].pattern).toBe("host_file_write:/tmp/**");
|
|
495
|
-
expect(options[2].pattern).toBe("host_file_write:*");
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
test("file_write with file_path key", async () => {
|
|
499
|
-
const options = await generateAllowlistOptions("file_write", {
|
|
500
|
-
file_path: "/tmp/out.txt",
|
|
501
|
-
});
|
|
502
|
-
expect(options[0].pattern).toBe("file_write:/tmp/out.txt");
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
test("unknown tool returns wildcard", async () => {
|
|
506
|
-
const options = await generateAllowlistOptions("other_tool", {
|
|
507
|
-
foo: "bar",
|
|
508
|
-
});
|
|
509
|
-
expect(options).toHaveLength(1);
|
|
510
|
-
expect(options[0].pattern).toBe("*");
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
test("web_fetch: generates exact url, origin wildcard, and tool wildcard", async () => {
|
|
514
|
-
const options = await generateAllowlistOptions("web_fetch", {
|
|
515
|
-
url: "https://example.com/docs/page",
|
|
516
|
-
});
|
|
517
|
-
expect(options).toHaveLength(3);
|
|
518
|
-
expect(options[0].pattern).toBe(
|
|
519
|
-
"web_fetch:https://example.com/docs/page",
|
|
520
|
-
);
|
|
521
|
-
expect(options[1].pattern).toBe("web_fetch:https://example.com/*");
|
|
522
|
-
expect(options[2].pattern).toBe("**");
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
test("web_fetch: strips fragments when generating allowlist options", async () => {
|
|
526
|
-
const options = await generateAllowlistOptions("web_fetch", {
|
|
527
|
-
url: "https://example.com/docs/page#section-1",
|
|
528
|
-
});
|
|
529
|
-
expect(options).toHaveLength(3);
|
|
530
|
-
expect(options[0].pattern).toBe(
|
|
531
|
-
"web_fetch:https://example.com/docs/page",
|
|
532
|
-
);
|
|
533
|
-
expect(options[1].pattern).toBe("web_fetch:https://example.com/*");
|
|
534
|
-
expect(options[2].pattern).toBe("**");
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
test("web_fetch: strips trailing-dot hostnames when generating allowlist options", async () => {
|
|
538
|
-
const options = await generateAllowlistOptions("web_fetch", {
|
|
539
|
-
url: "https://example.com./docs/page",
|
|
540
|
-
});
|
|
541
|
-
expect(options).toHaveLength(3);
|
|
542
|
-
expect(options[0].pattern).toBe(
|
|
543
|
-
"web_fetch:https://example.com/docs/page",
|
|
544
|
-
);
|
|
545
|
-
expect(options[1].pattern).toBe("web_fetch:https://example.com/*");
|
|
546
|
-
expect(options[2].pattern).toBe("**");
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
test("web_fetch: strips userinfo when generating allowlist options", async () => {
|
|
550
|
-
const username = "demo";
|
|
551
|
-
const credential = ["c", "r", "e", "d", "1", "2", "3"].join("");
|
|
552
|
-
const credentialedUrl = new URL("https://example.com/docs/page");
|
|
553
|
-
credentialedUrl.username = username;
|
|
554
|
-
credentialedUrl.password = credential;
|
|
555
|
-
const options = await generateAllowlistOptions("web_fetch", {
|
|
556
|
-
url: credentialedUrl.href,
|
|
557
|
-
});
|
|
558
|
-
expect(options).toHaveLength(3);
|
|
559
|
-
expect(options[0].pattern).toBe(
|
|
560
|
-
"web_fetch:https://example.com/docs/page",
|
|
561
|
-
);
|
|
562
|
-
expect(options[1].pattern).toBe("web_fetch:https://example.com/*");
|
|
563
|
-
expect(options[2].pattern).toBe("**");
|
|
564
|
-
expect(options[0].pattern).not.toContain("demo:cred123@");
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
test("web_fetch: normalizes scheme-less host:port for allowlist options", async () => {
|
|
568
|
-
const options = await generateAllowlistOptions("web_fetch", {
|
|
569
|
-
url: "example.com:8443/docs/page",
|
|
570
|
-
});
|
|
571
|
-
expect(options).toHaveLength(3);
|
|
572
|
-
expect(options[0].pattern).toBe(
|
|
573
|
-
"web_fetch:https://example.com:8443/docs/page",
|
|
574
|
-
);
|
|
575
|
-
expect(options[1].pattern).toBe("web_fetch:https://example.com:8443/*");
|
|
576
|
-
expect(options[2].pattern).toBe("**");
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
test("web_fetch: does not coerce path-only urls to https hostnames in allowlist options", async () => {
|
|
580
|
-
const options = await generateAllowlistOptions("web_fetch", {
|
|
581
|
-
url: "/docs/getting-started",
|
|
582
|
-
});
|
|
583
|
-
expect(options).toHaveLength(2);
|
|
584
|
-
expect(options[0].pattern).toBe("web_fetch:/docs/getting-started");
|
|
585
|
-
expect(options[1].pattern).toBe("**");
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
test("scaffold_managed_skill: generates per-skill and wildcard options", async () => {
|
|
589
|
-
const options = await generateAllowlistOptions("scaffold_managed_skill", {
|
|
590
|
-
skill_id: "my-tool",
|
|
591
|
-
});
|
|
592
|
-
expect(options).toHaveLength(2);
|
|
593
|
-
expect(options[0].label).toBe("my-tool");
|
|
594
|
-
expect(options[0].pattern).toBe("scaffold_managed_skill:my-tool");
|
|
595
|
-
expect(options[0].description).toBe("This skill only");
|
|
596
|
-
expect(options[1].label).toBe("scaffold_managed_skill:*");
|
|
597
|
-
expect(options[1].pattern).toBe("scaffold_managed_skill:*");
|
|
598
|
-
expect(options[1].description).toBe("All managed skill scaffolds");
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
test("delete_managed_skill: generates per-skill and wildcard options", async () => {
|
|
602
|
-
const options = await generateAllowlistOptions("delete_managed_skill", {
|
|
603
|
-
skill_id: "doomed",
|
|
604
|
-
});
|
|
605
|
-
expect(options).toHaveLength(2);
|
|
606
|
-
expect(options[0].pattern).toBe("delete_managed_skill:doomed");
|
|
607
|
-
expect(options[1].pattern).toBe("delete_managed_skill:*");
|
|
608
|
-
expect(options[1].description).toBe("All managed skill deletes");
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
test("scaffold_managed_skill with empty skill_id: only wildcard option", async () => {
|
|
612
|
-
const options = await generateAllowlistOptions("scaffold_managed_skill", {
|
|
613
|
-
skill_id: "",
|
|
614
|
-
});
|
|
615
|
-
expect(options).toHaveLength(1);
|
|
616
|
-
expect(options[0].pattern).toBe("scaffold_managed_skill:*");
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
test("web_fetch: escapes minimatch metacharacters in generated exact and origin patterns", async () => {
|
|
620
|
-
const options = await generateAllowlistOptions("web_fetch", {
|
|
621
|
-
url: "https://[2001:db8::1]/search?q=test",
|
|
622
|
-
});
|
|
623
|
-
expect(options).toHaveLength(3);
|
|
624
|
-
expect(options[0].label).toBe("https://[2001:db8::1]/search?q=test");
|
|
625
|
-
expect(options[0].pattern).toBe(
|
|
626
|
-
"web_fetch:https://\\[2001:db8::1\\]/search\\?q=test",
|
|
627
|
-
);
|
|
628
|
-
expect(options[1].pattern).toBe("web_fetch:https://\\[2001:db8::1\\]/*");
|
|
629
|
-
expect(options[2].pattern).toBe("**");
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
// ── network_request allowlist options ─────────────────────────
|
|
633
|
-
|
|
634
|
-
test("network_request: generates exact url, origin wildcard, and tool wildcard", async () => {
|
|
635
|
-
const options = await generateAllowlistOptions("network_request", {
|
|
636
|
-
url: "https://api.example.com/v1/data",
|
|
637
|
-
});
|
|
638
|
-
expect(options).toHaveLength(3);
|
|
639
|
-
expect(options[0].pattern).toBe(
|
|
640
|
-
"network_request:https://api.example.com/v1/data",
|
|
641
|
-
);
|
|
642
|
-
expect(options[1].pattern).toBe(
|
|
643
|
-
"network_request:https://api.example.com/*",
|
|
644
|
-
);
|
|
645
|
-
expect(options[2].pattern).toBe("**");
|
|
646
|
-
expect(options[2].label).toBe("network_request:*");
|
|
647
|
-
expect(options[2].description).toBe("All network requests");
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
test("network_request: origin wildcard uses friendly hostname", async () => {
|
|
651
|
-
const options = await generateAllowlistOptions("network_request", {
|
|
652
|
-
url: "https://www.example.com/path",
|
|
653
|
-
});
|
|
654
|
-
expect(options[1].description).toBe("Any page on example.com");
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
test("network_request: normalizes scheme-less host:port input", async () => {
|
|
658
|
-
const options = await generateAllowlistOptions("network_request", {
|
|
659
|
-
url: "api.example.com:8443/v1/data",
|
|
660
|
-
});
|
|
661
|
-
expect(options).toHaveLength(3);
|
|
662
|
-
expect(options[0].pattern).toBe(
|
|
663
|
-
"network_request:https://api.example.com:8443/v1/data",
|
|
664
|
-
);
|
|
665
|
-
expect(options[1].pattern).toBe(
|
|
666
|
-
"network_request:https://api.example.com:8443/*",
|
|
667
|
-
);
|
|
668
|
-
expect(options[2].pattern).toBe("**");
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
test("network_request: strips fragments and userinfo", async () => {
|
|
672
|
-
const username = "demo";
|
|
673
|
-
const credential = ["c", "r", "e", "d", "1", "2", "3"].join("");
|
|
674
|
-
const credentialedUrl = new URL(
|
|
675
|
-
"https://api.example.com/v1/data#section",
|
|
676
|
-
);
|
|
677
|
-
credentialedUrl.username = username;
|
|
678
|
-
credentialedUrl.password = credential;
|
|
679
|
-
const options = await generateAllowlistOptions("network_request", {
|
|
680
|
-
url: credentialedUrl.href,
|
|
681
|
-
});
|
|
682
|
-
expect(options).toHaveLength(3);
|
|
683
|
-
expect(options[0].pattern).toBe(
|
|
684
|
-
"network_request:https://api.example.com/v1/data",
|
|
685
|
-
);
|
|
686
|
-
expect(options[0].pattern).not.toContain("demo:cred123@");
|
|
687
|
-
expect(options[0].pattern).not.toContain("#section");
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
test("network_request: escapes minimatch metacharacters", async () => {
|
|
691
|
-
const options = await generateAllowlistOptions("network_request", {
|
|
692
|
-
url: "https://[2001:db8::1]/api?key=val",
|
|
693
|
-
});
|
|
694
|
-
expect(options).toHaveLength(3);
|
|
695
|
-
expect(options[0].pattern).toBe(
|
|
696
|
-
"network_request:https://\\[2001:db8::1\\]/api\\?key=val",
|
|
697
|
-
);
|
|
698
|
-
expect(options[1].pattern).toBe(
|
|
699
|
-
"network_request:https://\\[2001:db8::1\\]/*",
|
|
700
|
-
);
|
|
701
|
-
});
|
|
702
|
-
|
|
703
|
-
test("network_request: empty url produces only tool wildcard", async () => {
|
|
704
|
-
const options = await generateAllowlistOptions("network_request", {
|
|
705
|
-
url: "",
|
|
706
|
-
});
|
|
707
|
-
expect(options).toHaveLength(1);
|
|
708
|
-
expect(options[0].pattern).toBe("**");
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
|
-
|
|
712
448
|
// ── generateScopeOptions ───────────────────────────────────────
|
|
713
449
|
|
|
714
450
|
describe("generateScopeOptions", () => {
|
|
@@ -1287,65 +1023,39 @@ describe("Permission Checker", () => {
|
|
|
1287
1023
|
// pattern (skillId@hash) and an any-version pattern (bare skillId).
|
|
1288
1024
|
// Input-supplied version_hash is always ignored to prevent spoofing.
|
|
1289
1025
|
|
|
1290
|
-
describe("
|
|
1026
|
+
describe("skill metadata sent to the gateway", () => {
|
|
1291
1027
|
function ensureSkillsDir(): void {
|
|
1292
1028
|
mkdirSync(join(checkerTestDir, "skills"), { recursive: true });
|
|
1293
1029
|
}
|
|
1294
1030
|
|
|
1295
|
-
test("
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
const options = await generateAllowlistOptions("skill_load", {
|
|
1300
|
-
skill: "test-opts-skill",
|
|
1301
|
-
});
|
|
1302
|
-
|
|
1303
|
-
// Should have only the version-specific option
|
|
1304
|
-
expect(options).toHaveLength(1);
|
|
1305
|
-
expect(options[0].pattern).toMatch(/^skill_load:test-opts-skill@v1:/);
|
|
1306
|
-
expect(options[0].description).toBe("This exact version");
|
|
1307
|
-
});
|
|
1308
|
-
|
|
1309
|
-
test("allowlist options ignore input version_hash and use disk-computed hash (regression)", async () => {
|
|
1031
|
+
test("the version hash comes from disk, never from the model's input", async () => {
|
|
1032
|
+
// The gateway builds the version-pinned trust-rule option from the hash
|
|
1033
|
+
// the daemon sends; taking it from the invocation would let a caller pin
|
|
1034
|
+
// a rule to a version that was never on disk.
|
|
1310
1035
|
ensureSkillsDir();
|
|
1311
1036
|
writeSkill("test-opts-explicit", "Test Opts Explicit");
|
|
1312
1037
|
|
|
1313
|
-
|
|
1314
|
-
// options must use the disk-computed hash, not the input value.
|
|
1315
|
-
const options = await generateAllowlistOptions("skill_load", {
|
|
1038
|
+
await classifyRisk("skill_load", {
|
|
1316
1039
|
skill: "test-opts-explicit",
|
|
1317
1040
|
version_hash: "v1:customhash123",
|
|
1318
1041
|
});
|
|
1319
1042
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
expect(
|
|
1324
|
-
|
|
1325
|
-
);
|
|
1326
|
-
expect(options[0].description).toBe("This exact version");
|
|
1043
|
+
const metadata = lastIpcParams("classify_risk")?.skillMetadata as
|
|
1044
|
+
| { skillId: string; versionHash: string }
|
|
1045
|
+
| undefined;
|
|
1046
|
+
expect(metadata?.skillId).toBe("test-opts-explicit");
|
|
1047
|
+
expect(metadata?.versionHash).toMatch(/^v1:/);
|
|
1048
|
+
expect(metadata?.versionHash).not.toBe("v1:customhash123");
|
|
1327
1049
|
});
|
|
1328
1050
|
|
|
1329
|
-
test("
|
|
1051
|
+
test("an unresolvable selector is sent with no metadata", async () => {
|
|
1330
1052
|
ensureSkillsDir();
|
|
1331
1053
|
|
|
1332
|
-
|
|
1333
|
-
skill: "no-such-skill",
|
|
1334
|
-
});
|
|
1335
|
-
|
|
1336
|
-
// Should have only the raw selector
|
|
1337
|
-
expect(options).toHaveLength(1);
|
|
1338
|
-
expect(options[0].pattern).toBe("skill_load:no-such-skill");
|
|
1339
|
-
expect(options[0].description).toBe("This skill");
|
|
1340
|
-
});
|
|
1341
|
-
|
|
1342
|
-
test("allowlist options for empty skill selector only has wildcard", async () => {
|
|
1343
|
-
const options = await generateAllowlistOptions("skill_load", {
|
|
1344
|
-
skill: "",
|
|
1345
|
-
});
|
|
1054
|
+
await classifyRisk("skill_load", { skill: "no-such-skill" });
|
|
1346
1055
|
|
|
1347
|
-
|
|
1348
|
-
expect(
|
|
1056
|
+
const params = lastIpcParams("classify_risk");
|
|
1057
|
+
expect(params?.skill).toBe("no-such-skill");
|
|
1058
|
+
expect(params?.skillMetadata).toBeUndefined();
|
|
1349
1059
|
});
|
|
1350
1060
|
});
|
|
1351
1061
|
|
|
@@ -1514,7 +1224,6 @@ describe("bash network_mode=proxied — risk capped at medium", () => {
|
|
|
1514
1224
|
mockRisk("low");
|
|
1515
1225
|
mockIpcResponse("get_global_thresholds", DEFAULT_GATEWAY_THRESHOLDS);
|
|
1516
1226
|
_clearGlobalCacheForTesting();
|
|
1517
|
-
clearRiskCache();
|
|
1518
1227
|
});
|
|
1519
1228
|
|
|
1520
1229
|
test("proxied bash follows risk-based policy (medium risk → prompt outside container)", async () => {
|
|
@@ -1562,7 +1271,6 @@ describe("credentialed proxied bash — high risk escalation", () => {
|
|
|
1562
1271
|
mockRisk("low");
|
|
1563
1272
|
mockIpcResponse("get_global_thresholds", DEFAULT_GATEWAY_THRESHOLDS);
|
|
1564
1273
|
_clearGlobalCacheForTesting();
|
|
1565
|
-
clearRiskCache();
|
|
1566
1274
|
});
|
|
1567
1275
|
|
|
1568
1276
|
test("proxied bash with credential_ids sends credentialRefCount in IPC params", async () => {
|
|
@@ -1652,7 +1360,6 @@ describe("workspace mode — auto-allow workspace-scoped operations", () => {
|
|
|
1652
1360
|
mockRisk("low");
|
|
1653
1361
|
mockIpcResponse("get_global_thresholds", DEFAULT_GATEWAY_THRESHOLDS);
|
|
1654
1362
|
_clearGlobalCacheForTesting();
|
|
1655
|
-
clearRiskCache();
|
|
1656
1363
|
try {
|
|
1657
1364
|
rmSync(join(checkerTestDir, "protected", "trust.json"));
|
|
1658
1365
|
} catch {
|
|
@@ -1796,7 +1503,6 @@ describe("integration regressions (PR 11)", () => {
|
|
|
1796
1503
|
mockRisk("low");
|
|
1797
1504
|
mockIpcResponse("get_global_thresholds", DEFAULT_GATEWAY_THRESHOLDS);
|
|
1798
1505
|
_clearGlobalCacheForTesting();
|
|
1799
|
-
clearRiskCache();
|
|
1800
1506
|
// Delete the trust file to prevent stale default rules from prior tests
|
|
1801
1507
|
try {
|
|
1802
1508
|
rmSync(join(checkerTestDir, "protected", "trust.json"));
|
|
@@ -45,6 +45,10 @@ mock.module("../plugins/defaults/memory/substrate/skill-store.js", () => ({
|
|
|
45
45
|
seedV2SkillEntries: async () => {
|
|
46
46
|
seedCallCount += 1;
|
|
47
47
|
},
|
|
48
|
+
// The validate route builds the page index, which reads the seeded
|
|
49
|
+
// skill catalog; an empty catalog keeps that walk inert here.
|
|
50
|
+
listSkillEntries: () => [],
|
|
51
|
+
SKILL_SLUG_PREFIX: "skills/",
|
|
48
52
|
}));
|
|
49
53
|
|
|
50
54
|
// ---------------------------------------------------------------------------
|
|
@@ -254,7 +258,7 @@ describe("all memory v2 routes — MEMORY_V2_DISABLED gate", () => {
|
|
|
254
258
|
const result = (await route!.handler({ body: {} })) as {
|
|
255
259
|
pageCount: number;
|
|
256
260
|
edgeCount: number;
|
|
257
|
-
|
|
261
|
+
danglingLinks: unknown[];
|
|
258
262
|
oversizedPages: unknown[];
|
|
259
263
|
parseFailures: unknown[];
|
|
260
264
|
};
|
|
@@ -263,7 +267,7 @@ describe("all memory v2 routes — MEMORY_V2_DISABLED gate", () => {
|
|
|
263
267
|
// returned at all (no MEMORY_V2_DISABLED throw).
|
|
264
268
|
expect(result.pageCount).toBe(0);
|
|
265
269
|
expect(result.edgeCount).toBe(0);
|
|
266
|
-
expect(result.
|
|
270
|
+
expect(result.danglingLinks).toEqual([]);
|
|
267
271
|
expect(result.oversizedPages).toEqual([]);
|
|
268
272
|
expect(result.parseFailures).toEqual([]);
|
|
269
273
|
});
|