@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
|
@@ -2,7 +2,12 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
normalizeHttpPublicBaseUrl,
|
|
5
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash,
|
|
5
6
|
normalizePublicBaseUrl,
|
|
7
|
+
parseRecordedAssistantId,
|
|
8
|
+
parseTunnelRecord,
|
|
9
|
+
trimmedNonEmptyString,
|
|
10
|
+
TUNNEL_PROVIDERS,
|
|
6
11
|
velayHostForPlatformHost,
|
|
7
12
|
} from "../ingress.js";
|
|
8
13
|
import {
|
|
@@ -85,6 +90,35 @@ describe("normalizeHttpPublicBaseUrl", () => {
|
|
|
85
90
|
});
|
|
86
91
|
});
|
|
87
92
|
|
|
93
|
+
describe("normalizeHttpPublicBaseUrlWithoutTrailingSlash", () => {
|
|
94
|
+
test("drops the root path its sibling always emits", () => {
|
|
95
|
+
expect(
|
|
96
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash("https://x.test"),
|
|
97
|
+
).toBe("https://x.test");
|
|
98
|
+
expect(
|
|
99
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash("https://x.test/"),
|
|
100
|
+
).toBe("https://x.test");
|
|
101
|
+
expect(
|
|
102
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash(" https://x.test/api/// "),
|
|
103
|
+
).toBe("https://x.test/api");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("rejects everything its sibling rejects", () => {
|
|
107
|
+
for (const value of [
|
|
108
|
+
"",
|
|
109
|
+
" ",
|
|
110
|
+
"notaurl",
|
|
111
|
+
"ftp://x.test",
|
|
112
|
+
"https://x.test?a=b",
|
|
113
|
+
42,
|
|
114
|
+
]) {
|
|
115
|
+
expect(
|
|
116
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash(value),
|
|
117
|
+
).toBeUndefined();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
88
122
|
describe("Twilio ingress helpers", () => {
|
|
89
123
|
test("resolves public base URL with fallback", () => {
|
|
90
124
|
expect(
|
|
@@ -123,3 +157,87 @@ describe("Twilio ingress helpers", () => {
|
|
|
123
157
|
});
|
|
124
158
|
});
|
|
125
159
|
});
|
|
160
|
+
|
|
161
|
+
describe("parseTunnelRecord", () => {
|
|
162
|
+
const TUNNEL_URL = "https://assistant-1.example.ts.net";
|
|
163
|
+
|
|
164
|
+
test("accepts every provider in the registry", () => {
|
|
165
|
+
for (const provider of TUNNEL_PROVIDERS) {
|
|
166
|
+
expect(
|
|
167
|
+
parseTunnelRecord({ provider, publicBaseUrl: TUNNEL_URL }),
|
|
168
|
+
).toEqual({ provider, publicBaseUrl: TUNNEL_URL });
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("returns the URL in the shape its own validator produces", () => {
|
|
173
|
+
// Padding and trailing slashes are normalized away rather than handed
|
|
174
|
+
// back, so readers never re-normalize what they were given.
|
|
175
|
+
for (const publicBaseUrl of [
|
|
176
|
+
` ${TUNNEL_URL} `,
|
|
177
|
+
`${TUNNEL_URL}/`,
|
|
178
|
+
`${TUNNEL_URL}///`,
|
|
179
|
+
]) {
|
|
180
|
+
expect(
|
|
181
|
+
parseTunnelRecord({ provider: "ngrok", publicBaseUrl }),
|
|
182
|
+
).toEqual({ provider: "ngrok", publicBaseUrl: TUNNEL_URL });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("rejects values that are not a record", () => {
|
|
187
|
+
for (const value of [undefined, null, "nonsense", 42, []]) {
|
|
188
|
+
expect(parseTunnelRecord(value)).toBeNull();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("rejects a record missing either field", () => {
|
|
193
|
+
expect(parseTunnelRecord({ provider: "ngrok" })).toBeNull();
|
|
194
|
+
expect(parseTunnelRecord({ publicBaseUrl: TUNNEL_URL })).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("rejects a provider outside the registry", () => {
|
|
198
|
+
// Readers render the provider into a `vellum tunnel --provider <name>`
|
|
199
|
+
// command, so a hand-edited config must not reach a terminal.
|
|
200
|
+
for (const provider of ["wireguard", "; rm -rf /", "", 42]) {
|
|
201
|
+
expect(
|
|
202
|
+
parseTunnelRecord({ provider, publicBaseUrl: TUNNEL_URL }),
|
|
203
|
+
).toBeNull();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("rejects a URL that is not absolute HTTP(S)", () => {
|
|
208
|
+
for (const publicBaseUrl of [
|
|
209
|
+
"",
|
|
210
|
+
" ",
|
|
211
|
+
"not-a-url",
|
|
212
|
+
"example.ts.net",
|
|
213
|
+
"ftp://x.test",
|
|
214
|
+
"https://one.ngrok.app?token=x",
|
|
215
|
+
42,
|
|
216
|
+
]) {
|
|
217
|
+
expect(
|
|
218
|
+
parseTunnelRecord({ provider: "ngrok", publicBaseUrl }),
|
|
219
|
+
).toBeNull();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe("trimmedNonEmptyString", () => {
|
|
225
|
+
test("trims and rejects blank or non-string values", () => {
|
|
226
|
+
expect(trimmedNonEmptyString(" assistant-1 ")).toBe("assistant-1");
|
|
227
|
+
for (const value of ["", " ", undefined, null, 42]) {
|
|
228
|
+
expect(trimmedNonEmptyString(value)).toBeUndefined();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe("parseRecordedAssistantId", () => {
|
|
234
|
+
test("trims a recorded id", () => {
|
|
235
|
+
expect(parseRecordedAssistantId(" assistant-1 ")).toBe("assistant-1");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("rejects blank and non-string values", () => {
|
|
239
|
+
for (const value of ["", " ", undefined, null, 42]) {
|
|
240
|
+
expect(parseRecordedAssistantId(value)).toBeNull();
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -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
|
+
});
|
package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts
CHANGED
|
@@ -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
|
+
}
|
package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"./remote-web-pairing": "./src/remote-web-pairing.ts",
|
|
15
15
|
"./twilio-ingress": "./src/twilio-ingress.ts",
|
|
16
16
|
"./trust-rules": "./src/trust-rules.ts",
|
|
17
|
+
"./url-normalization": "./src/url-normalization.ts",
|
|
17
18
|
"./handles": "./src/handles.ts",
|
|
18
19
|
"./rpc": "./src/rpc.ts",
|
|
19
20
|
"./attachment-naming": "./src/attachment-naming.ts",
|
|
@@ -2,7 +2,12 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
normalizeHttpPublicBaseUrl,
|
|
5
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash,
|
|
5
6
|
normalizePublicBaseUrl,
|
|
7
|
+
parseRecordedAssistantId,
|
|
8
|
+
parseTunnelRecord,
|
|
9
|
+
trimmedNonEmptyString,
|
|
10
|
+
TUNNEL_PROVIDERS,
|
|
6
11
|
velayHostForPlatformHost,
|
|
7
12
|
} from "../ingress.js";
|
|
8
13
|
import {
|
|
@@ -85,6 +90,35 @@ describe("normalizeHttpPublicBaseUrl", () => {
|
|
|
85
90
|
});
|
|
86
91
|
});
|
|
87
92
|
|
|
93
|
+
describe("normalizeHttpPublicBaseUrlWithoutTrailingSlash", () => {
|
|
94
|
+
test("drops the root path its sibling always emits", () => {
|
|
95
|
+
expect(
|
|
96
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash("https://x.test"),
|
|
97
|
+
).toBe("https://x.test");
|
|
98
|
+
expect(
|
|
99
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash("https://x.test/"),
|
|
100
|
+
).toBe("https://x.test");
|
|
101
|
+
expect(
|
|
102
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash(" https://x.test/api/// "),
|
|
103
|
+
).toBe("https://x.test/api");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("rejects everything its sibling rejects", () => {
|
|
107
|
+
for (const value of [
|
|
108
|
+
"",
|
|
109
|
+
" ",
|
|
110
|
+
"notaurl",
|
|
111
|
+
"ftp://x.test",
|
|
112
|
+
"https://x.test?a=b",
|
|
113
|
+
42,
|
|
114
|
+
]) {
|
|
115
|
+
expect(
|
|
116
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash(value),
|
|
117
|
+
).toBeUndefined();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
88
122
|
describe("Twilio ingress helpers", () => {
|
|
89
123
|
test("resolves public base URL with fallback", () => {
|
|
90
124
|
expect(
|
|
@@ -123,3 +157,87 @@ describe("Twilio ingress helpers", () => {
|
|
|
123
157
|
});
|
|
124
158
|
});
|
|
125
159
|
});
|
|
160
|
+
|
|
161
|
+
describe("parseTunnelRecord", () => {
|
|
162
|
+
const TUNNEL_URL = "https://assistant-1.example.ts.net";
|
|
163
|
+
|
|
164
|
+
test("accepts every provider in the registry", () => {
|
|
165
|
+
for (const provider of TUNNEL_PROVIDERS) {
|
|
166
|
+
expect(
|
|
167
|
+
parseTunnelRecord({ provider, publicBaseUrl: TUNNEL_URL }),
|
|
168
|
+
).toEqual({ provider, publicBaseUrl: TUNNEL_URL });
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("returns the URL in the shape its own validator produces", () => {
|
|
173
|
+
// Padding and trailing slashes are normalized away rather than handed
|
|
174
|
+
// back, so readers never re-normalize what they were given.
|
|
175
|
+
for (const publicBaseUrl of [
|
|
176
|
+
` ${TUNNEL_URL} `,
|
|
177
|
+
`${TUNNEL_URL}/`,
|
|
178
|
+
`${TUNNEL_URL}///`,
|
|
179
|
+
]) {
|
|
180
|
+
expect(
|
|
181
|
+
parseTunnelRecord({ provider: "ngrok", publicBaseUrl }),
|
|
182
|
+
).toEqual({ provider: "ngrok", publicBaseUrl: TUNNEL_URL });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("rejects values that are not a record", () => {
|
|
187
|
+
for (const value of [undefined, null, "nonsense", 42, []]) {
|
|
188
|
+
expect(parseTunnelRecord(value)).toBeNull();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("rejects a record missing either field", () => {
|
|
193
|
+
expect(parseTunnelRecord({ provider: "ngrok" })).toBeNull();
|
|
194
|
+
expect(parseTunnelRecord({ publicBaseUrl: TUNNEL_URL })).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("rejects a provider outside the registry", () => {
|
|
198
|
+
// Readers render the provider into a `vellum tunnel --provider <name>`
|
|
199
|
+
// command, so a hand-edited config must not reach a terminal.
|
|
200
|
+
for (const provider of ["wireguard", "; rm -rf /", "", 42]) {
|
|
201
|
+
expect(
|
|
202
|
+
parseTunnelRecord({ provider, publicBaseUrl: TUNNEL_URL }),
|
|
203
|
+
).toBeNull();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("rejects a URL that is not absolute HTTP(S)", () => {
|
|
208
|
+
for (const publicBaseUrl of [
|
|
209
|
+
"",
|
|
210
|
+
" ",
|
|
211
|
+
"not-a-url",
|
|
212
|
+
"example.ts.net",
|
|
213
|
+
"ftp://x.test",
|
|
214
|
+
"https://one.ngrok.app?token=x",
|
|
215
|
+
42,
|
|
216
|
+
]) {
|
|
217
|
+
expect(
|
|
218
|
+
parseTunnelRecord({ provider: "ngrok", publicBaseUrl }),
|
|
219
|
+
).toBeNull();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe("trimmedNonEmptyString", () => {
|
|
225
|
+
test("trims and rejects blank or non-string values", () => {
|
|
226
|
+
expect(trimmedNonEmptyString(" assistant-1 ")).toBe("assistant-1");
|
|
227
|
+
for (const value of ["", " ", undefined, null, 42]) {
|
|
228
|
+
expect(trimmedNonEmptyString(value)).toBeUndefined();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe("parseRecordedAssistantId", () => {
|
|
234
|
+
test("trims a recorded id", () => {
|
|
235
|
+
expect(parseRecordedAssistantId(" assistant-1 ")).toBe("assistant-1");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("rejects blank and non-string values", () => {
|
|
239
|
+
for (const value of ["", " ", undefined, null, 42]) {
|
|
240
|
+
expect(parseRecordedAssistantId(value)).toBeNull();
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|