@vellumai/assistant 0.11.4 → 0.11.5-dev.202608220019.a778cf0
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 +6 -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__/ingress.test.ts +118 -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/ingress.ts +103 -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__/ingress.test.ts +118 -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/ingress.ts +103 -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 +30 -32
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -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/ingress.ts +103 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +487 -11
- package/package.json +1 -1
- package/scripts/sync-web-search-catalog.ts +6 -0
- 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-readiness-service.test.ts +126 -0
- package/src/__tests__/channel-reply-delivery.test.ts +30 -17
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
- package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
- package/src/__tests__/config-loader-backfill.test.ts +19 -10
- package/src/__tests__/container-cpu-sampler.test.ts +153 -0
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-delete-watch-timeline.test.ts +231 -0
- package/src/__tests__/conversation-error.test.ts +17 -0
- package/src/__tests__/conversation-lifecycle.test.ts +20 -26
- package/src/__tests__/conversation-pairing.test.ts +186 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
- package/src/__tests__/credential-broker-server-use.test.ts +24 -5
- package/src/__tests__/credential-routes.test.ts +22 -3
- package/src/__tests__/credential-security-invariants.test.ts +2 -1
- package/src/__tests__/daemon-credential-client.test.ts +88 -0
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/dm-backfill.test.ts +63 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -4
- package/src/__tests__/events-client-registration.test.ts +28 -0
- package/src/__tests__/file-write-tool.test.ts +4 -2
- package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/host-proxy-interface.test.ts +11 -1
- package/src/__tests__/host-shell-tool.test.ts +23 -4
- package/src/__tests__/image-conversion.test.ts +143 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/input-repairs.test.ts +207 -0
- package/src/__tests__/live-workspace-guard.test.ts +75 -0
- package/src/__tests__/managed-profile-guard.test.ts +4 -2
- package/src/__tests__/mcp-abort-signal.test.ts +1 -1
- package/src/__tests__/mcp-client-auth.test.ts +1 -1
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
- package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
- package/src/__tests__/mtime-cache.test.ts +2 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
- package/src/__tests__/openai-provider.test.ts +40 -0
- package/src/__tests__/openai-responses-provider.test.ts +18 -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__/sanitize-config-for-transfer.test.ts +28 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
- package/src/__tests__/settings-routes.test.ts +85 -3
- package/src/__tests__/skill-tool-factory.test.ts +161 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +63 -38
- package/src/__tests__/tool-policy.test.ts +97 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
- package/src/__tests__/user-plugin-loader.test.ts +2 -0
- package/src/__tests__/validate-input.test.ts +243 -7
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -0
- package/src/__tests__/voice-session-bridge.test.ts +42 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +8 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
- package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
- package/src/acp/acp-claude-oauth.ts +38 -21
- package/src/acp/prepare-agent-env.ts +146 -55
- package/src/agent/loop-tool-dedup.test.ts +161 -0
- package/src/agent/loop.ts +68 -22
- package/src/api/constants/app-tools.ts +34 -0
- package/src/api/constants/profile-config-validation.ts +60 -0
- package/src/api/events/resource-pressure-status-changed.ts +53 -0
- package/src/api/events/tool-result.ts +6 -1
- package/src/api/events/watch-retro-completed.ts +52 -0
- package/src/api/index.ts +30 -0
- package/src/api/responses/resource-pressure-status.ts +25 -0
- package/src/approvals/guardian-channel-delivery.ts +70 -6
- package/src/approvals/guardian-request-resolvers.ts +32 -66
- package/src/calls/__tests__/voice-session-bridge.test.ts +114 -0
- package/src/calls/voice-session-bridge.ts +60 -6
- package/src/channels/__tests__/message-audience.test.ts +49 -0
- package/src/channels/__tests__/types.test.ts +17 -3
- package/src/channels/message-audience.ts +40 -0
- package/src/channels/types.ts +16 -12
- package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
- package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
- package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +2 -1
- package/src/cli/commands/__tests__/inference-profiles.test.ts +16 -4
- package/src/cli/commands/__tests__/inference-providers.test.ts +135 -2
- 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-profiles.ts +56 -3
- package/src/cli/commands/inference-providers.ts +37 -6
- 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/oauth/request.test.ts +93 -0
- package/src/cli/commands/oauth/request.ts +23 -9
- 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 +26 -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/open-browser.test.ts +67 -0
- package/src/cli/lib/open-browser.ts +24 -5
- package/src/cli/lib/plugin-surfaces.ts +27 -0
- package/src/config/__tests__/balanced-model-experiment.test.ts +7 -7
- package/src/config/__tests__/default-profile-catalog.test.ts +3 -3
- package/src/config/__tests__/profile-materialization.test.ts +26 -0
- package/src/config/default-profile-catalog.ts +5 -6
- package/src/config/feature-flag-registry.json +36 -28
- package/src/config/profile-materialization.ts +29 -0
- package/src/config/sanitize-for-transfer.ts +16 -0
- package/src/config/schemas/llm.ts +7 -0
- package/src/config/schemas/services.ts +6 -0
- package/src/config/webhook-routing.ts +8 -0
- package/src/context/compactor.ts +31 -2
- package/src/context/outbound-sanitize.ts +6 -0
- package/src/daemon/__tests__/lifecycle-watch-timeline-sweep.test.ts +98 -0
- 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 +19 -2
- 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/daemon-control.ts +1 -0
- package/src/daemon/handlers/__tests__/config-ingress-tunnel-records.test.ts +208 -0
- package/src/daemon/handlers/config-ingress.ts +115 -5
- package/src/daemon/lifecycle.ts +30 -2
- package/src/daemon/message-types/conversations.ts +5 -7
- package/src/daemon/message-types/web-activity.ts +3 -2
- 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/inbound/__tests__/tunnel-probe.test.ts +448 -0
- package/src/inbound/tunnel-probe.ts +261 -0
- 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 +138 -73
- package/src/messaging/providers/channel-transport.ts +100 -16
- package/src/messaging/providers/discord/send.ts +16 -0
- package/src/messaging/providers/discord/transport.ts +23 -1
- package/src/messaging/providers/index.ts +84 -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 +88 -34
- package/src/messaging/providers/slack/send.ts +99 -50
- package/src/messaging/providers/slack/transport.ts +35 -37
- package/src/messaging/providers/telegram-bot/send.test.ts +88 -18
- package/src/messaging/providers/telegram-bot/send.ts +36 -0
- package/src/messaging/providers/telegram-bot/transport.ts +22 -8
- 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 +69 -20
- package/src/persistence/conversation-queries.ts +14 -1
- package/src/persistence/conversation-types.ts +22 -0
- package/src/persistence/db-async-query.ts +2 -1
- package/src/persistence/db-maintenance.ts +15 -0
- package/src/persistence/delivery-crud.ts +122 -15
- package/src/persistence/embeddings/qdrant-manager.ts +1 -0
- package/src/persistence/migrations/367-create-watch-timeline-entries.ts +46 -0
- package/src/persistence/migrations/368-watch-timeline-screenshot-blob.ts +33 -0
- package/src/persistence/migrations/__tests__/367-create-watch-timeline-entries.test.ts +98 -0
- package/src/persistence/migrations/__tests__/368-watch-timeline-screenshot-blob.test.ts +98 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/watch.ts +29 -0
- package/src/persistence/steps.ts +4 -0
- 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/__tests__/retry-network-error.test.ts +84 -0
- 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 +25 -2
- package/src/providers/content-blocks.ts +22 -0
- package/src/providers/fetch-provider-catalog.ts +19 -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__/endpoint-probe.test.ts +92 -0
- package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
- package/src/providers/inference/__tests__/profile-config-validation.test.ts +39 -0
- package/src/providers/inference/__tests__/profile-probe-classify.test.ts +66 -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/endpoint-probe.ts +115 -0
- package/src/providers/inference/missing-credential-guard.ts +110 -0
- package/src/providers/inference/profile-probe.ts +256 -0
- package/src/providers/inference/resolve-auth.ts +7 -7
- package/src/providers/media-resolve.ts +176 -15
- package/src/providers/model-catalog.ts +10 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +17 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
- package/src/providers/openai/__tests__/connection-error-wrap.test.ts +44 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/api-error-normalization.ts +16 -2
- package/src/providers/openai/chat-completions-provider.ts +326 -44
- package/src/providers/openai/responses-provider.ts +5 -2
- package/src/providers/provider-send-message.ts +11 -0
- package/src/providers/retry.ts +6 -0
- package/src/providers/search-provider-catalog.ts +20 -0
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +31 -10
- package/src/runtime/AGENTS.md +36 -0
- package/src/runtime/__tests__/host-observe.test.ts +302 -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-readiness-service.ts +39 -13
- package/src/runtime/channel-readiness-types.ts +49 -2
- package/src/runtime/channel-reply-delivery.ts +55 -32
- 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/host-observe.ts +246 -0
- package/src/runtime/http-errors.ts +1 -0
- package/src/runtime/http-server.ts +185 -6
- 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__/conversation-query-routes.test.ts +80 -0
- package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +118 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +72 -1
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +508 -0
- 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 +103 -71
- 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/__tests__/watch-routes-guardian-cache.test.ts +139 -0
- package/src/runtime/routes/__tests__/watch-routes.test.ts +598 -0
- 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/channel-readiness-routes.ts +14 -2
- 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-query-routes.ts +10 -0
- 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 +44 -53
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
- package/src/runtime/routes/host-cu-routes.ts +18 -0
- package/src/runtime/routes/identity-routes.ts +5 -121
- package/src/runtime/routes/inbound-message-handler.ts +74 -30
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +182 -297
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +173 -386
- 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 +4 -0
- package/src/runtime/routes/inference-profiles-routes.ts +232 -31
- package/src/runtime/routes/inference-provider-connection-routes.ts +33 -11
- package/src/runtime/routes/ingress-status-routes.ts +180 -0
- package/src/runtime/routes/monitoring-routes.ts +9 -1
- package/src/runtime/routes/oauth-commands-routes.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +28 -32
- 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 +15 -12
- 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/routes/watch-routes.ts +807 -0
- 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/security/__tests__/untrusted-content.test.ts +42 -0
- package/src/security/untrusted-content.ts +28 -9
- 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__/firecrawl-compat.test.ts +77 -0
- package/src/tools/network/__tests__/web-fetch-fastcrw.test.ts +169 -0
- package/src/tools/network/__tests__/web-search.test.ts +138 -3
- package/src/tools/network/firecrawl-compat.ts +90 -0
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/network/web-fetch.ts +142 -62
- package/src/tools/network/web-search.ts +162 -58
- 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/util/worker-process.ts +1 -0
- package/src/watch/__tests__/watch-retro.test.ts +665 -0
- package/src/watch/__tests__/watch-session-manager.test.ts +566 -0
- package/src/watch/__tests__/watch-timeline.test.ts +670 -0
- package/src/watch/watch-retro.ts +480 -0
- package/src/watch/watch-session-manager.ts +575 -0
- package/src/watch/watch-timeline.ts +848 -0
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
canonicalizeWebUrl,
|
|
5
|
+
looksLikeHostPortShorthand,
|
|
6
|
+
looksLikePathOnlyInput,
|
|
7
|
+
normalizeWebUrl,
|
|
8
|
+
} from "../url-normalization.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* These are the rules a trust rule is saved and matched under. A change here
|
|
12
|
+
* changes which saved rules still match, so each case states the target the
|
|
13
|
+
* two spellings must agree on.
|
|
14
|
+
*/
|
|
15
|
+
describe("normalizeWebUrl", () => {
|
|
16
|
+
test("keeps an ordinary https URL as-is", () => {
|
|
17
|
+
expect(normalizeWebUrl("https://example.com/docs/page")?.href).toBe(
|
|
18
|
+
"https://example.com/docs/page",
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("drops the fragment: it never reaches the server", () => {
|
|
23
|
+
expect(normalizeWebUrl("https://example.com/docs#section")?.href).toBe(
|
|
24
|
+
"https://example.com/docs",
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("drops userinfo so credentials cannot land in a saved rule", () => {
|
|
29
|
+
const credentialed = new URL("https://example.com/docs/page");
|
|
30
|
+
credentialed.username = "demo";
|
|
31
|
+
credentialed.password = ["c", "r", "e", "d", "1", "2", "3"].join("");
|
|
32
|
+
|
|
33
|
+
const normalized = normalizeWebUrl(credentialed.href);
|
|
34
|
+
expect(normalized?.href).toBe("https://example.com/docs/page");
|
|
35
|
+
expect(normalized?.username).toBe("");
|
|
36
|
+
expect(normalized?.password).toBe("");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("strips a trailing root dot from the hostname", () => {
|
|
40
|
+
expect(normalizeWebUrl("https://example.com./docs/page")?.href).toBe(
|
|
41
|
+
"https://example.com/docs/page",
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("decodes escaped path segments so one path has one spelling", () => {
|
|
46
|
+
// Without this, a rule scoped to /private is bypassed by /%70rivate.
|
|
47
|
+
expect(normalizeWebUrl("https://example.com/%70rivate")?.href).toBe(
|
|
48
|
+
"https://example.com/private",
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("reads scheme-less input as https", () => {
|
|
53
|
+
expect(normalizeWebUrl("example.com/docs")?.href).toBe(
|
|
54
|
+
"https://example.com/docs",
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("reads host:port shorthand as an https origin, not a scheme", () => {
|
|
59
|
+
expect(normalizeWebUrl("example.com:8443/status")?.origin).toBe(
|
|
60
|
+
"https://example.com:8443",
|
|
61
|
+
);
|
|
62
|
+
expect(normalizeWebUrl("[2001:db8::1]:8443/status")?.origin).toBe(
|
|
63
|
+
"https://[2001:db8::1]:8443",
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("rejects path-only input rather than coercing it to a host", () => {
|
|
68
|
+
for (const input of ["/etc/passwd", "./rel", "../up", "?q=1", "#frag"]) {
|
|
69
|
+
expect(normalizeWebUrl(input)).toBeNull();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("rejects every non-http scheme", () => {
|
|
74
|
+
for (const input of [
|
|
75
|
+
"file:///etc/passwd",
|
|
76
|
+
"data:text/html,<script>",
|
|
77
|
+
"javascript:alert(1)",
|
|
78
|
+
"ftp://example.com/f",
|
|
79
|
+
]) {
|
|
80
|
+
expect(normalizeWebUrl(input)).toBeNull();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("rejects empty and whitespace-only input", () => {
|
|
85
|
+
expect(normalizeWebUrl("")).toBeNull();
|
|
86
|
+
expect(normalizeWebUrl(" ")).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("trims surrounding whitespace", () => {
|
|
90
|
+
expect(normalizeWebUrl(" https://example.com/a ")?.href).toBe(
|
|
91
|
+
"https://example.com/a",
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("returns null rather than throwing on an unparseable authority", () => {
|
|
96
|
+
expect(normalizeWebUrl("https://")).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("canonicalizeWebUrl", () => {
|
|
101
|
+
test("keeps the parser's form when the path is not decodable, without throwing", () => {
|
|
102
|
+
// `%zz` and a truncated UTF-8 sequence are not valid escapes; the path
|
|
103
|
+
// must survive unchanged rather than the call throwing.
|
|
104
|
+
expect(canonicalizeWebUrl(new URL("https://example.com/100%zz")).href).toBe(
|
|
105
|
+
"https://example.com/100%zz",
|
|
106
|
+
);
|
|
107
|
+
expect(
|
|
108
|
+
canonicalizeWebUrl(new URL("https://example.com/%E0%A4%A")).href,
|
|
109
|
+
).toBe("https://example.com/%E0%A4%A");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("decodes an escaped percent, so `%25` and `%` are one path", () => {
|
|
113
|
+
expect(canonicalizeWebUrl(new URL("https://example.com/100%25")).href).toBe(
|
|
114
|
+
"https://example.com/100%",
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("input shape predicates", () => {
|
|
120
|
+
test("host:port shorthand is recognized, scheme-prefixed input is not", () => {
|
|
121
|
+
expect(looksLikeHostPortShorthand("example.com:8443/x")).toBe(true);
|
|
122
|
+
expect(looksLikeHostPortShorthand("[2001:db8::1]:443")).toBe(true);
|
|
123
|
+
expect(looksLikeHostPortShorthand("https://example.com/x")).toBe(false);
|
|
124
|
+
expect(looksLikeHostPortShorthand("example.com/x")).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("path-only input is recognized", () => {
|
|
128
|
+
expect(looksLikePathOnlyInput("/abs")).toBe(true);
|
|
129
|
+
expect(looksLikePathOnlyInput("./rel")).toBe(true);
|
|
130
|
+
expect(looksLikePathOnlyInput("../up")).toBe(true);
|
|
131
|
+
expect(looksLikePathOnlyInput("?q=1")).toBe(true);
|
|
132
|
+
expect(looksLikePathOnlyInput("#frag")).toBe(true);
|
|
133
|
+
expect(looksLikePathOnlyInput("example.com/x")).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -48,3 +48,106 @@ export function normalizeHttpPublicBaseUrl(value: unknown): string | undefined {
|
|
|
48
48
|
return undefined;
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The same validation as `normalizeHttpPublicBaseUrl`, minus the root path it
|
|
54
|
+
* always emits (`https://x` becomes `https://x/`). Callers that append a path
|
|
55
|
+
* or persist the result want this shape, so they neither join through a double
|
|
56
|
+
* slash nor re-strip what the normalizer just added.
|
|
57
|
+
*/
|
|
58
|
+
export function normalizeHttpPublicBaseUrlWithoutTrailingSlash(
|
|
59
|
+
value: unknown,
|
|
60
|
+
): string | undefined {
|
|
61
|
+
return normalizePublicBaseUrl(normalizeHttpPublicBaseUrl(value));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A trimmed string, or undefined when the value is not a non-blank string.
|
|
66
|
+
*
|
|
67
|
+
* Exported because the identity check in the daemon's tunnel probe compares an
|
|
68
|
+
* id recorded here against one an edge serves, and the two sides have to trim
|
|
69
|
+
* alike: a padded served id must read as a match, not as a different
|
|
70
|
+
* assistant.
|
|
71
|
+
*/
|
|
72
|
+
export function trimmedNonEmptyString(value: unknown): string | undefined {
|
|
73
|
+
if (typeof value !== "string") {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const trimmed = value.trim();
|
|
77
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Records `vellum tunnel` leaves in the workspace config's `ingress` section.
|
|
81
|
+
// The CLI writes them and the daemon reads them, and the two are separate
|
|
82
|
+
// build units that cannot import each other, so the key names, the provider
|
|
83
|
+
// allowlist, and the validation all live here rather than in a copy on each
|
|
84
|
+
// side.
|
|
85
|
+
|
|
86
|
+
/** Key under `ingress` holding the tunnel that most recently ran. */
|
|
87
|
+
export const INGRESS_LAST_TUNNEL_KEY = "lastTunnel";
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Key under `ingress` holding a tunnel published for device pairing alone.
|
|
91
|
+
*
|
|
92
|
+
* A tailnet-only tunnel started while webhook integrations are configured
|
|
93
|
+
* cannot carry their callbacks, so it leaves `publicBaseUrl` (the callback
|
|
94
|
+
* base) as it is and records itself here. Readers that answer the pairing card
|
|
95
|
+
* prefer this address over `publicBaseUrl`; teardown drops the key.
|
|
96
|
+
*/
|
|
97
|
+
export const INGRESS_PAIRING_TUNNEL_KEY = "pairingTunnel";
|
|
98
|
+
|
|
99
|
+
/** Key under `ingress` holding the assistant id that tunnel fronted. */
|
|
100
|
+
export const INGRESS_ASSISTANT_ID_KEY = "assistantId";
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The local tunnel providers the CLI can start, in the order they are offered.
|
|
104
|
+
* `vellum tunnel`'s accepted `--provider` values and the persisted-record
|
|
105
|
+
* validation below both derive from it.
|
|
106
|
+
*/
|
|
107
|
+
export const TUNNEL_PROVIDERS = ["ngrok", "cloudflare", "tailscale"] as const;
|
|
108
|
+
|
|
109
|
+
export type TunnelProviderName = (typeof TUNNEL_PROVIDERS)[number];
|
|
110
|
+
|
|
111
|
+
/** A recorded tunnel, kept after teardown so UIs can name the command to restart. */
|
|
112
|
+
export interface TunnelRecord {
|
|
113
|
+
provider: TunnelProviderName;
|
|
114
|
+
publicBaseUrl: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isTunnelProviderName(value: unknown): value is TunnelProviderName {
|
|
118
|
+
return (
|
|
119
|
+
typeof value === "string" &&
|
|
120
|
+
(TUNNEL_PROVIDERS as readonly string[]).includes(value)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Parse an `ingress.lastTunnel` or `ingress.pairingTunnel` value, or null when
|
|
126
|
+
* it is absent or malformed.
|
|
127
|
+
*
|
|
128
|
+
* A hand-edited config must not hand callers an unusable address or a provider
|
|
129
|
+
* name no command accepts: the provider is checked against the allowlist
|
|
130
|
+
* because readers render it into a restart command, and the URL faces the same
|
|
131
|
+
* absolute HTTP(S) constraint as every other public base URL. The URL comes
|
|
132
|
+
* back in the shape the validator produced, so readers never re-normalize it.
|
|
133
|
+
*/
|
|
134
|
+
export function parseTunnelRecord(value: unknown): TunnelRecord | null {
|
|
135
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const { provider, publicBaseUrl } = value as Record<string, unknown>;
|
|
139
|
+
if (!isTunnelProviderName(provider)) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const normalized =
|
|
143
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash(publicBaseUrl);
|
|
144
|
+
if (normalized === undefined) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return { provider, publicBaseUrl: normalized };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Parse an `ingress.assistantId` value, or null when it is absent or blank. */
|
|
151
|
+
export function parseRecordedAssistantId(value: unknown): string | null {
|
|
152
|
+
return trimmedNonEmptyString(value) ?? null;
|
|
153
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical URL normalization for web tool inputs, shared by the gateway's
|
|
3
|
+
* risk classifiers and the daemon's URL safety checks.
|
|
4
|
+
*
|
|
5
|
+
* A URL a model passes to `web_fetch` / `network_request` reaches more than
|
|
6
|
+
* one consumer, and they must agree on what counts as the same target: the
|
|
7
|
+
* gateway builds the trust-rule ladder from it, and anything that later
|
|
8
|
+
* compares a URL to a saved rule has to fold the same spellings together. Two
|
|
9
|
+
* normalizations would mean two answers, so it lives here rather than in
|
|
10
|
+
* either process.
|
|
11
|
+
*
|
|
12
|
+
* Data-only in spirit: no imports, no config, no I/O.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Whether a bare `host:port` (or `[v6]:port`) shorthand was written. */
|
|
16
|
+
export function looksLikeHostPortShorthand(value: string): boolean {
|
|
17
|
+
if (/^\[[0-9a-fA-F:.%]+\]:\d+(?:[/?#]|$)/.test(value)) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
return /^[^/?#@\s:]+:\d+(?:[/?#]|$)/.test(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Whether the input is a path, query, or fragment rather than a URL. */
|
|
24
|
+
export function looksLikePathOnlyInput(value: string): boolean {
|
|
25
|
+
return (
|
|
26
|
+
value.startsWith("/") ||
|
|
27
|
+
value.startsWith("./") ||
|
|
28
|
+
value.startsWith("../") ||
|
|
29
|
+
value.startsWith("?") ||
|
|
30
|
+
value.startsWith("#")
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Strip the parts of a URL that must not affect a trust decision, and fold
|
|
36
|
+
* the encodings that would otherwise let one URL wear two spellings.
|
|
37
|
+
*
|
|
38
|
+
* Percent-escaped path segments are decoded (`/%70rivate` and `/private` are
|
|
39
|
+
* one path, so a path-scoped rule cannot be bypassed by escaping), a trailing
|
|
40
|
+
* root dot is dropped from the hostname, and fragment and userinfo are
|
|
41
|
+
* removed: the fragment never reaches the server, and credentials in the
|
|
42
|
+
* authority must never end up in a saved rule.
|
|
43
|
+
*/
|
|
44
|
+
export function canonicalizeWebUrl(parsed: URL): URL {
|
|
45
|
+
parsed.hash = "";
|
|
46
|
+
parsed.username = "";
|
|
47
|
+
parsed.password = "";
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
parsed.pathname = decodeURI(parsed.pathname);
|
|
51
|
+
} catch {
|
|
52
|
+
// Keep the URL parser's canonical form when decoding fails.
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (parsed.hostname.endsWith(".")) {
|
|
56
|
+
parsed.hostname = parsed.hostname.replace(/\.+$/, "");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return parsed;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parse and canonicalize a web tool's `url` input, or `null` when it is not a
|
|
64
|
+
* URL this system will fetch.
|
|
65
|
+
*
|
|
66
|
+
* Accepts `https://host/path`, `http://…`, bare `host/path`, and `host:port`
|
|
67
|
+
* shorthand. Rejects path-only input and any other scheme (`file:`, `data:`,
|
|
68
|
+
* `javascript:`), so a non-http target can never acquire a web trust rule.
|
|
69
|
+
*/
|
|
70
|
+
export function normalizeWebUrl(rawUrl: string): URL | null {
|
|
71
|
+
const trimmed = rawUrl.trim();
|
|
72
|
+
if (!trimmed) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (looksLikeHostPortShorthand(trimmed)) {
|
|
77
|
+
try {
|
|
78
|
+
return canonicalizeWebUrl(new URL(`https://${trimmed}`));
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const parsed = new URL(trimmed);
|
|
86
|
+
if (parsed.protocol === "http:" || parsed.protocol === "https:") {
|
|
87
|
+
return canonicalizeWebUrl(parsed);
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
} catch {
|
|
91
|
+
// Not an absolute URL; fall through to the shorthand forms.
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (looksLikePathOnlyInput(trimmed)) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
return canonicalizeWebUrl(new URL(`https://${trimmed}`));
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -63,11 +63,16 @@ export type TrustRulesListIpcParams = z.infer<
|
|
|
63
63
|
typeof TrustRulesListIpcParamsSchema
|
|
64
64
|
>;
|
|
65
65
|
|
|
66
|
+
/** The three risk levels a trust rule can set and a registry default can carry. */
|
|
67
|
+
export const RISK_LEVEL_VALUES = ["low", "medium", "high"] as const;
|
|
68
|
+
export type RiskLevelValue = (typeof RISK_LEVEL_VALUES)[number];
|
|
69
|
+
export const RiskLevelValueSchema = z.enum(RISK_LEVEL_VALUES);
|
|
70
|
+
|
|
66
71
|
export const TrustRuleSchema = z.object({
|
|
67
72
|
id: z.string(),
|
|
68
73
|
tool: z.string(),
|
|
69
74
|
pattern: z.string(),
|
|
70
|
-
risk:
|
|
75
|
+
risk: RiskLevelValueSchema,
|
|
71
76
|
description: z.string(),
|
|
72
77
|
origin: z.enum(["default", "user_defined"]),
|
|
73
78
|
userModified: z.boolean(),
|
|
@@ -341,3 +346,163 @@ export const GetGuardianContactIpcResponseSchema = z.object({
|
|
|
341
346
|
export type GetGuardianContactIpcResponse = z.infer<
|
|
342
347
|
typeof GetGuardianContactIpcResponseSchema
|
|
343
348
|
>;
|
|
349
|
+
|
|
350
|
+
// ── classify_risk ────────────────────────────────────────────────────────────
|
|
351
|
+
// Risk classification is gateway-owned; the assistant sends one request per
|
|
352
|
+
// tool invocation and reads the whole answer back. The gateway validates the
|
|
353
|
+
// request against `ClassifyRiskIpcParamsSchema`; the assistant validates the
|
|
354
|
+
// response against `ClassifyRiskIpcResponseSchema` and fails closed on a
|
|
355
|
+
// mismatch.
|
|
356
|
+
|
|
357
|
+
/** A classified risk; `unknown` is the classifier's own "not in registry". */
|
|
358
|
+
export const ClassifiedRiskSchema = z.enum([...RISK_LEVEL_VALUES, "unknown"]);
|
|
359
|
+
export type ClassifiedRisk = z.infer<typeof ClassifiedRiskSchema>;
|
|
360
|
+
|
|
361
|
+
/** How a risk was determined: a user trust rule, the registry, or neither. */
|
|
362
|
+
export const RiskMatchTypeSchema = z.enum(["user_rule", "registry", "unknown"]);
|
|
363
|
+
export type RiskMatchType = z.infer<typeof RiskMatchTypeSchema>;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* File classifier context the assistant pre-resolves (it owns the workspace
|
|
367
|
+
* filesystem) so the gateway can classify file tools without assistant path
|
|
368
|
+
* helpers. Directories are canonicalized (symlinks resolved) by the sender.
|
|
369
|
+
*/
|
|
370
|
+
export const ClassifyRiskFileContextSchema = z.object({
|
|
371
|
+
protectedDir: z.string(),
|
|
372
|
+
deprecatedDir: z.string(),
|
|
373
|
+
hooksDir: z.string(),
|
|
374
|
+
pluginsDir: z.string().optional(),
|
|
375
|
+
toolsDir: z.string().optional(),
|
|
376
|
+
routesDir: z.string().optional(),
|
|
377
|
+
workflowsDir: z.string().optional(),
|
|
378
|
+
/** Monitoring data dir: the sentinel lives here, writes are code-injection risk. */
|
|
379
|
+
monitoringDir: z.string().optional(),
|
|
380
|
+
actorTokenSigningKeyPath: z.string(),
|
|
381
|
+
skillSourceDirs: z.array(z.string()),
|
|
382
|
+
});
|
|
383
|
+
export type ClassifyRiskFileContext = z.infer<
|
|
384
|
+
typeof ClassifyRiskFileContextSchema
|
|
385
|
+
>;
|
|
386
|
+
|
|
387
|
+
/** Skill metadata the assistant pre-resolves for skill-load classification. */
|
|
388
|
+
export const ClassifyRiskSkillMetadataSchema = z.object({
|
|
389
|
+
skillId: z.string(),
|
|
390
|
+
selector: z.string(),
|
|
391
|
+
versionHash: z.string(),
|
|
392
|
+
transitiveHash: z.string().optional(),
|
|
393
|
+
hasInlineExpansions: z.boolean(),
|
|
394
|
+
isDynamic: z.boolean(),
|
|
395
|
+
});
|
|
396
|
+
export type ClassifyRiskSkillMetadata = z.infer<
|
|
397
|
+
typeof ClassifyRiskSkillMetadataSchema
|
|
398
|
+
>;
|
|
399
|
+
|
|
400
|
+
export const ClassifyRiskIpcParamsSchema = z.object({
|
|
401
|
+
tool: z.string().min(1),
|
|
402
|
+
command: z.string().optional(),
|
|
403
|
+
url: z.string().optional(),
|
|
404
|
+
path: z.string().optional(),
|
|
405
|
+
/**
|
|
406
|
+
* The file tool's target path with symlinks resolved by the assistant. The
|
|
407
|
+
* gateway's security escalation prefix checks use it so a symlink cannot
|
|
408
|
+
* mask a write into a protected directory; falls back to lexical resolution
|
|
409
|
+
* of `path` when absent.
|
|
410
|
+
*/
|
|
411
|
+
resolvedPath: z.string().optional(),
|
|
412
|
+
/**
|
|
413
|
+
* The sandbox file tool's working directory with symlinks resolved. Paired
|
|
414
|
+
* with `resolvedPath` for the workspace-boundary check so a symlinked
|
|
415
|
+
* workspace prefix (macOS `/var` → `/private/var`) does not read as an
|
|
416
|
+
* escape. Absent for host tools.
|
|
417
|
+
*/
|
|
418
|
+
resolvedWorkingDir: z.string().optional(),
|
|
419
|
+
skill: z.string().optional(),
|
|
420
|
+
mode: z.string().optional(),
|
|
421
|
+
script: z.string().optional(),
|
|
422
|
+
workingDir: z.string().optional(),
|
|
423
|
+
allowPrivateNetwork: z.boolean().optional(),
|
|
424
|
+
networkMode: z.string().optional(),
|
|
425
|
+
isContainerized: z.boolean().optional(),
|
|
426
|
+
workspaceRoot: z.string().optional(),
|
|
427
|
+
fileContext: ClassifyRiskFileContextSchema.optional(),
|
|
428
|
+
skillMetadata: ClassifyRiskSkillMetadataSchema.optional(),
|
|
429
|
+
/**
|
|
430
|
+
* The tool's registry default risk, for tools with no dedicated classifier.
|
|
431
|
+
* The gateway answers with it (`matchType: "registry"`); absent, the tool is
|
|
432
|
+
* unknown and answers `medium`.
|
|
433
|
+
*/
|
|
434
|
+
registryDefaultRisk: RiskLevelValueSchema.optional(),
|
|
435
|
+
/** Number of credential references attached to this tool invocation. */
|
|
436
|
+
credentialRefCount: z.number().int().nonnegative().optional(),
|
|
437
|
+
/**
|
|
438
|
+
* For `host_file_transfer` to_sandbox: the workspace-side destination path
|
|
439
|
+
* and the sandbox working directory it resolves against, so a transfer that
|
|
440
|
+
* lands an executable file in a code-injection sink (tools, routes, hooks,
|
|
441
|
+
* plugins, skills) escalates even though `path` carries the host-side source.
|
|
442
|
+
*/
|
|
443
|
+
transferSandboxDestPath: z.string().optional(),
|
|
444
|
+
transferSandboxWorkingDir: z.string().optional(),
|
|
445
|
+
/** `transferSandboxDestPath` with symlinks resolved by the assistant. */
|
|
446
|
+
resolvedTransferDestPath: z.string().optional(),
|
|
447
|
+
});
|
|
448
|
+
export type ClassifyRiskIpcParams = z.infer<typeof ClassifyRiskIpcParamsSchema>;
|
|
449
|
+
|
|
450
|
+
/** Regex ladder shown when the user saves a classification (display only). */
|
|
451
|
+
export const RiskPatternScopeOptionSchema = z.object({
|
|
452
|
+
pattern: z.string(),
|
|
453
|
+
label: z.string(),
|
|
454
|
+
});
|
|
455
|
+
export type RiskPatternScopeOption = z.infer<
|
|
456
|
+
typeof RiskPatternScopeOptionSchema
|
|
457
|
+
>;
|
|
458
|
+
|
|
459
|
+
/** Minimatch ladder for the "always allow" prompt option (what a rule matches). */
|
|
460
|
+
export const RiskAllowlistOptionSchema = z.object({
|
|
461
|
+
label: z.string(),
|
|
462
|
+
description: z.string(),
|
|
463
|
+
pattern: z.string(),
|
|
464
|
+
});
|
|
465
|
+
export type RiskAllowlistOption = z.infer<typeof RiskAllowlistOptionSchema>;
|
|
466
|
+
|
|
467
|
+
/** Directory ladder for filesystem operations; `scope` is a path glob or `everywhere`. */
|
|
468
|
+
export const RiskDirectoryScopeOptionSchema = z.object({
|
|
469
|
+
scope: z.string(),
|
|
470
|
+
label: z.string(),
|
|
471
|
+
});
|
|
472
|
+
export type RiskDirectoryScopeOption = z.infer<
|
|
473
|
+
typeof RiskDirectoryScopeOptionSchema
|
|
474
|
+
>;
|
|
475
|
+
|
|
476
|
+
export const RiskDangerousPatternSchema = z.object({
|
|
477
|
+
type: z.string(),
|
|
478
|
+
description: z.string(),
|
|
479
|
+
text: z.string(),
|
|
480
|
+
});
|
|
481
|
+
export type RiskDangerousPattern = z.infer<typeof RiskDangerousPatternSchema>;
|
|
482
|
+
|
|
483
|
+
export const ClassifyRiskIpcResponseSchema = z.object({
|
|
484
|
+
risk: ClassifiedRiskSchema,
|
|
485
|
+
reason: z.string(),
|
|
486
|
+
matchType: RiskMatchTypeSchema,
|
|
487
|
+
scopeOptions: z.array(RiskPatternScopeOptionSchema),
|
|
488
|
+
allowlistOptions: z.array(RiskAllowlistOptionSchema).optional(),
|
|
489
|
+
actionKeys: z.array(z.string()).optional(),
|
|
490
|
+
commandCandidates: z.array(z.string()).optional(),
|
|
491
|
+
dangerousPatterns: z.array(RiskDangerousPatternSchema).optional(),
|
|
492
|
+
opaqueConstructs: z.boolean().optional(),
|
|
493
|
+
isComplexSyntax: z.boolean().optional(),
|
|
494
|
+
sandboxAutoApprove: z.boolean().optional(),
|
|
495
|
+
/**
|
|
496
|
+
* Lexically-resolved path arguments from sandbox-auto-approve-eligible bash
|
|
497
|
+
* segments. The gateway has no filesystem access; the assistant resolves
|
|
498
|
+
* these through symlinks and revokes `sandboxAutoApprove` if any escapes
|
|
499
|
+
* the workspace.
|
|
500
|
+
*/
|
|
501
|
+
sandboxPathArgs: z.array(z.string()).optional(),
|
|
502
|
+
directoryScopeOptions: z.array(RiskDirectoryScopeOptionSchema).optional(),
|
|
503
|
+
/** Fully resolved filesystem path arguments, for directory-scoped rule matching. */
|
|
504
|
+
resolvedPaths: z.array(z.string()).optional(),
|
|
505
|
+
});
|
|
506
|
+
export type ClassifyRiskIpcResponse = z.infer<
|
|
507
|
+
typeof ClassifyRiskIpcResponseSchema
|
|
508
|
+
>;
|
|
@@ -131,7 +131,6 @@ function buildManagedOutboundRequestId(
|
|
|
131
131
|
chatId: payload.chatId,
|
|
132
132
|
text: normalizedText,
|
|
133
133
|
assistantId: payload.assistantId ?? null,
|
|
134
|
-
chatAction: payload.chatAction ?? null,
|
|
135
134
|
hasAttachments:
|
|
136
135
|
Array.isArray(payload.attachments) && payload.attachments.length > 0,
|
|
137
136
|
approvalRequestId: payload.approval?.requestId ?? null,
|
|
@@ -212,7 +211,6 @@ async function deliverManagedOutboundReply(
|
|
|
212
211
|
chatId: payload.chatId,
|
|
213
212
|
text: payload.text ?? null,
|
|
214
213
|
assistantId: payload.assistantId ?? null,
|
|
215
|
-
chatAction: payload.chatAction ?? null,
|
|
216
214
|
hasAttachments,
|
|
217
215
|
sourceUpdateId: callback.sourceUpdateId ?? null,
|
|
218
216
|
},
|
|
@@ -325,7 +323,12 @@ export async function deliverChannelReply(
|
|
|
325
323
|
): Promise<ChannelDeliveryResult> {
|
|
326
324
|
const managedCallback = parseManagedOutboundCallback(callbackUrl);
|
|
327
325
|
if (managedCallback) {
|
|
328
|
-
await deliverManagedOutboundReply(
|
|
326
|
+
await deliverManagedOutboundReply(
|
|
327
|
+
managedCallback,
|
|
328
|
+
payload,
|
|
329
|
+
bearerToken,
|
|
330
|
+
log,
|
|
331
|
+
);
|
|
329
332
|
return { ok: true };
|
|
330
333
|
}
|
|
331
334
|
|
|
@@ -385,17 +388,7 @@ export async function deliverChannelReply(
|
|
|
385
388
|
// Response may not be JSON for non-Slack channels; that's fine.
|
|
386
389
|
}
|
|
387
390
|
|
|
388
|
-
|
|
389
|
-
log.debug(
|
|
390
|
-
{ chatId: payload.chatId, callbackUrl, chatAction: payload.chatAction },
|
|
391
|
-
"Channel action delivered",
|
|
392
|
-
);
|
|
393
|
-
} else {
|
|
394
|
-
log.info(
|
|
395
|
-
{ chatId: payload.chatId, callbackUrl },
|
|
396
|
-
"Channel reply delivered",
|
|
397
|
-
);
|
|
398
|
-
}
|
|
391
|
+
log.info({ chatId: payload.chatId, callbackUrl }, "Channel reply delivered");
|
|
399
392
|
|
|
400
393
|
return result;
|
|
401
394
|
}
|
|
@@ -26,6 +26,7 @@ export {
|
|
|
26
26
|
AttachmentMetadataSchema,
|
|
27
27
|
ChannelDeliveryResultSchema,
|
|
28
28
|
ChannelReplyPayloadSchema,
|
|
29
|
+
MessageAudienceSchema,
|
|
29
30
|
PermissionRequestDetailsSchema,
|
|
30
31
|
SlackStreamOpSchema,
|
|
31
32
|
SlackStreamTaskSchema,
|
|
@@ -37,6 +38,7 @@ export type {
|
|
|
37
38
|
AttachmentMetadata,
|
|
38
39
|
ChannelDeliveryResult,
|
|
39
40
|
ChannelReplyPayload,
|
|
41
|
+
MessageAudience,
|
|
40
42
|
PermissionRequestDetails,
|
|
41
43
|
SlackStreamOp,
|
|
42
44
|
SlackStreamTask,
|
|
@@ -156,6 +156,26 @@ export const SlackStreamOpSchema = z
|
|
|
156
156
|
|
|
157
157
|
export type SlackStreamOp = z.infer<typeof SlackStreamOpSchema>;
|
|
158
158
|
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Audience
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Restricts a message to a single reader in a room that has more.
|
|
165
|
+
*
|
|
166
|
+
* A single object rather than a flag beside an id, because the two are only
|
|
167
|
+
* meaningful together: a channel cannot address one reader without knowing
|
|
168
|
+
* which. Slack reads this as `chat.postEphemeral`; a channel whose rooms hold
|
|
169
|
+
* one reader already has nothing to do.
|
|
170
|
+
*/
|
|
171
|
+
export const MessageAudienceSchema = z.object({
|
|
172
|
+
kind: z.literal("oneReader"),
|
|
173
|
+
/** The reader, in the channel's own id space. */
|
|
174
|
+
userId: z.string(),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
export type MessageAudience = z.infer<typeof MessageAudienceSchema>;
|
|
178
|
+
|
|
159
179
|
// ---------------------------------------------------------------------------
|
|
160
180
|
// Channel reply payload — the full outbound wire format
|
|
161
181
|
// ---------------------------------------------------------------------------
|
|
@@ -163,43 +183,21 @@ export type SlackStreamOp = z.infer<typeof SlackStreamOpSchema>;
|
|
|
163
183
|
export const ChannelReplyPayloadSchema = z.object({
|
|
164
184
|
chatId: z.string(),
|
|
165
185
|
text: z.string().optional(),
|
|
166
|
-
/** Pre-formatted Block Kit blocks for Slack delivery. */
|
|
167
|
-
blocks: z.array(z.custom<KnownBlock>()).optional(),
|
|
168
186
|
assistantId: z.string().optional(),
|
|
169
187
|
attachments: z.array(AttachmentMetadataSchema).optional(),
|
|
170
188
|
approval: ApprovalUIMetadataSchema.optional(),
|
|
171
|
-
chatAction: z.literal("typing").optional(),
|
|
172
189
|
/**
|
|
173
|
-
*
|
|
174
|
-
*
|
|
190
|
+
* Who may see this message. Absent means everyone in the room, which is the
|
|
191
|
+
* only safe reading of an absent value: a message meant for one reader that
|
|
192
|
+
* loses its audience must not become a public one.
|
|
175
193
|
*/
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
/** When provided, add or remove an emoji reaction on a message. */
|
|
184
|
-
reaction: z
|
|
185
|
-
.object({
|
|
186
|
-
action: z.enum(["add", "remove"]),
|
|
187
|
-
name: z.string(),
|
|
188
|
-
messageTs: z.string(),
|
|
189
|
-
})
|
|
190
|
-
.optional(),
|
|
191
|
-
/** When provided, set or clear the Slack Assistants API thread status. */
|
|
192
|
-
assistantThreadStatus: z
|
|
193
|
-
.object({
|
|
194
|
-
channel: z.string(),
|
|
195
|
-
threadTs: z.string(),
|
|
196
|
-
status: z.string(),
|
|
197
|
-
/** Serialized to Slack as `loading_messages`. */
|
|
198
|
-
loadingMessages: z.array(z.string()).optional(),
|
|
199
|
-
})
|
|
200
|
-
.optional(),
|
|
201
|
-
/** When provided, perform one Slack streaming operation (start/append/stop). */
|
|
202
|
-
slackStream: SlackStreamOpSchema.optional(),
|
|
194
|
+
audience: MessageAudienceSchema.optional(),
|
|
195
|
+
/**
|
|
196
|
+
* Asks the channel to render the text richly rather than as plain text.
|
|
197
|
+
* Each channel decides what that means, and one that cannot ignores it.
|
|
198
|
+
* Slack is the only channel acting on it today, where it becomes Block Kit.
|
|
199
|
+
*/
|
|
200
|
+
renderRichly: z.boolean().optional(),
|
|
203
201
|
});
|
|
204
202
|
|
|
205
203
|
export type ChannelReplyPayload = z.infer<typeof ChannelReplyPayloadSchema>;
|