@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,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reachability probe for the public URL a tunnel exposes.
|
|
3
|
+
*
|
|
4
|
+
* A tunnel's public URL is recorded in the workspace config, but a recorded
|
|
5
|
+
* URL says nothing about whether anything still answers on it: the tunnel
|
|
6
|
+
* process can die, the nginx edge can stay up while the gateway behind it is
|
|
7
|
+
* down, and a URL can end up fronting a different assistant entirely. The
|
|
8
|
+
* probe asks the edge two questions at once, and the two answers carry very
|
|
9
|
+
* different weight:
|
|
10
|
+
*
|
|
11
|
+
* - `GET /healthz` is the liveness verdict. Every ingress shape answers it:
|
|
12
|
+
* the nginx edge proxies it to the gateway, and a tunnel pointed straight
|
|
13
|
+
* at the gateway port serves it from the gateway itself. Anything but a
|
|
14
|
+
* 2xx means the URL is not usable at all, which is `unreachable`.
|
|
15
|
+
* - `GET /assistant/__config` decides whether that live edge is one a device
|
|
16
|
+
* can pair against. Only the nginx remote-web SPA edge serves it
|
|
17
|
+
* (`buildRemoteWebIngressLocations` in `cli/src/lib/nginx-ingress.ts`),
|
|
18
|
+
* and that is the same edge serving `/assistant/pair`, so an edge that
|
|
19
|
+
* answers the config request with anything but that edge's own config is
|
|
20
|
+
* an edge whose pair URL would 404. That is `unpairable`: alive, and no
|
|
21
|
+
* use to a card whose whole job is pairing. A tunnel pointed straight at
|
|
22
|
+
* the gateway, a bring-your-own HTTPS front that only proxies the gateway,
|
|
23
|
+
* and a catch-all that answers every path with some JSON all land there.
|
|
24
|
+
* A config request that gets no answer at all, because it lost the shared
|
|
25
|
+
* deadline or the connection dropped under it, proves nothing either way,
|
|
26
|
+
* and the live `/healthz` stands.
|
|
27
|
+
*
|
|
28
|
+
* The served config also carries the assistant's id, which is what separates
|
|
29
|
+
* `healthy` from `foreign`.
|
|
30
|
+
*
|
|
31
|
+
* Neither path is denylisted, so both are publicly reachable.
|
|
32
|
+
*
|
|
33
|
+
* The module takes a URL and an expected id and returns a verdict: it reads no
|
|
34
|
+
* config and knows nothing about workspace paths, which is what lets callers
|
|
35
|
+
* probe an arbitrary URL and lets tests run without network access.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import {
|
|
39
|
+
normalizeHttpPublicBaseUrlWithoutTrailingSlash,
|
|
40
|
+
trimmedNonEmptyString,
|
|
41
|
+
} from "@vellumai/service-contracts/ingress";
|
|
42
|
+
|
|
43
|
+
import { isPlainObject } from "../util/object.js";
|
|
44
|
+
import { truncate } from "../util/truncate.js";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Verdict for one probe.
|
|
48
|
+
*
|
|
49
|
+
* `unpairable` means the edge answered the config request with something that
|
|
50
|
+
* is not the pairing edge's config, so it is alive without fronting the
|
|
51
|
+
* pairing app.
|
|
52
|
+
* `foreign` means the edge positively identified itself as a different
|
|
53
|
+
* assistant. Any case where either id is unknown is `healthy`: a config
|
|
54
|
+
* served by a CLI that predates the id, or a URL recorded before the id was
|
|
55
|
+
* saved, is version skew, not someone else's assistant.
|
|
56
|
+
*/
|
|
57
|
+
export type TunnelProbeResult =
|
|
58
|
+
| { kind: "healthy"; assistantId?: string; assistantName?: string }
|
|
59
|
+
| { kind: "unreachable"; detail: string }
|
|
60
|
+
| { kind: "unpairable"; detail?: string }
|
|
61
|
+
| { kind: "foreign"; assistantId?: string; assistantName?: string };
|
|
62
|
+
|
|
63
|
+
const DEFAULT_TIMEOUT_MS = 4_000;
|
|
64
|
+
|
|
65
|
+
/** Placeholder swapped in for the probed URL so `detail` stays URL-free. */
|
|
66
|
+
const REDACTED_URL = "<url>";
|
|
67
|
+
|
|
68
|
+
/** `detail` is rendered inline in a settings card, so it has to stay short. */
|
|
69
|
+
const MAX_DETAIL_LENGTH = 120;
|
|
70
|
+
|
|
71
|
+
/** Bound on the `cause` walk, so a self-referential chain cannot spin. */
|
|
72
|
+
const MAX_CAUSE_DEPTH = 5;
|
|
73
|
+
|
|
74
|
+
/** Wrappers fetch layers raise when the real reason sits one `cause` down. */
|
|
75
|
+
const GENERIC_FETCH_MESSAGES = new Set(["fetch failed", "failed to fetch"]);
|
|
76
|
+
|
|
77
|
+
/** Reported when the config path answers with a body that is not a config. */
|
|
78
|
+
const UNREADABLE_CONFIG_DETAIL = "no assistant config served";
|
|
79
|
+
|
|
80
|
+
/** Reported when a served body parses but is not the pairing edge's config. */
|
|
81
|
+
const UNMARKED_CONFIG_DETAIL = "not a pairing edge config";
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Stamped into the served config by the nginx pairing edge and nothing else
|
|
85
|
+
* (`remoteWebIngressConfig` in `cli/src/lib/nginx-ingress.ts`), which makes it
|
|
86
|
+
* the marker that separates that edge from any other JSON a live URL serves.
|
|
87
|
+
*/
|
|
88
|
+
const PAIRING_EDGE_MODE = "remote-gateway";
|
|
89
|
+
|
|
90
|
+
export async function probeTunnel(args: {
|
|
91
|
+
publicBaseUrl: string;
|
|
92
|
+
/** Id recorded when this URL was saved; omit to skip the identity check. */
|
|
93
|
+
expectedAssistantId?: string;
|
|
94
|
+
timeoutMs?: number;
|
|
95
|
+
fetchImpl?: typeof fetch;
|
|
96
|
+
}): Promise<TunnelProbeResult> {
|
|
97
|
+
if (args.publicBaseUrl.trim().length === 0) {
|
|
98
|
+
return { kind: "unreachable", detail: "no public base URL" };
|
|
99
|
+
}
|
|
100
|
+
// The same validation the config writers apply, so a value they would have
|
|
101
|
+
// rejected is named as malformed rather than handed to `fetch` and reported
|
|
102
|
+
// through whatever string that layer happens to produce.
|
|
103
|
+
const base = normalizeHttpPublicBaseUrlWithoutTrailingSlash(
|
|
104
|
+
args.publicBaseUrl,
|
|
105
|
+
);
|
|
106
|
+
if (base === undefined) {
|
|
107
|
+
return { kind: "unreachable", detail: "not an http(s) URL" };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const fetchImpl = args.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
111
|
+
// One deadline covers both requests: only an answered config request can
|
|
112
|
+
// demote a live edge, so a slow edge that answers `/healthz` and loses the
|
|
113
|
+
// config request to the same deadline keeps its liveness verdict.
|
|
114
|
+
const signal = AbortSignal.timeout(args.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
115
|
+
const [health, config] = await Promise.allSettled([
|
|
116
|
+
fetchImpl(`${base}/healthz`, { signal }),
|
|
117
|
+
fetchImpl(`${base}/assistant/__config`, { signal }),
|
|
118
|
+
]);
|
|
119
|
+
const configResponse =
|
|
120
|
+
config.status === "fulfilled" ? config.value : undefined;
|
|
121
|
+
|
|
122
|
+
if (health.status === "rejected") {
|
|
123
|
+
await discardBody(configResponse);
|
|
124
|
+
return unreachable(describeError(health.reason), base);
|
|
125
|
+
}
|
|
126
|
+
// Liveness is the status line; the health body is never read.
|
|
127
|
+
await discardBody(health.value);
|
|
128
|
+
if (!health.value.ok) {
|
|
129
|
+
await discardBody(configResponse);
|
|
130
|
+
return unreachable(`HTTP ${health.value.status}`, base);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const served = await readServedConfig(configResponse);
|
|
134
|
+
if (served.kind === "notServed") {
|
|
135
|
+
return { kind: "unpairable", detail: served.detail };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const identity = served.kind === "served" ? served.config : {};
|
|
139
|
+
const expected = trimmedNonEmptyString(args.expectedAssistantId);
|
|
140
|
+
const isForeign =
|
|
141
|
+
expected !== undefined &&
|
|
142
|
+
identity.assistantId !== undefined &&
|
|
143
|
+
identity.assistantId !== expected;
|
|
144
|
+
|
|
145
|
+
return { kind: isForeign ? "foreign" : "healthy", ...identity };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Fetch layers vary in how much of the request they echo into an error
|
|
150
|
+
* message, and the caller already knows which URL it asked about.
|
|
151
|
+
*/
|
|
152
|
+
function unreachable(detail: string, base: string): TunnelProbeResult {
|
|
153
|
+
return {
|
|
154
|
+
kind: "unreachable",
|
|
155
|
+
detail: truncate(detail.replaceAll(base, REDACTED_URL), MAX_DETAIL_LENGTH),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* An unread fetch body pins its socket until GC under Bun and undici, and the
|
|
161
|
+
* probe runs on every settings mount, resume, and manual refresh.
|
|
162
|
+
*/
|
|
163
|
+
async function discardBody(response: Response | undefined): Promise<void> {
|
|
164
|
+
try {
|
|
165
|
+
await response?.body?.cancel();
|
|
166
|
+
} catch {
|
|
167
|
+
/* best effort */
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function isTimeoutError(error: unknown): boolean {
|
|
172
|
+
return (
|
|
173
|
+
isPlainObject(error) &&
|
|
174
|
+
(error.name === "TimeoutError" || error.name === "AbortError")
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function isGenericFetchMessage(message: string): boolean {
|
|
179
|
+
return GENERIC_FETCH_MESSAGES.has(message.toLowerCase());
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A dead tunnel surfaces as a generic wrapper ("fetch failed") whose `cause`
|
|
184
|
+
* carries the reason worth reading (`ECONNREFUSED`, `ENOTFOUND`), so walk the
|
|
185
|
+
* chain for a code, then for the first message that says something.
|
|
186
|
+
*/
|
|
187
|
+
function describeError(error: unknown): string {
|
|
188
|
+
let message: string | undefined;
|
|
189
|
+
let current: unknown = error;
|
|
190
|
+
|
|
191
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH; depth += 1) {
|
|
192
|
+
if (!isPlainObject(current)) {
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
if (isTimeoutError(current)) {
|
|
196
|
+
return "timeout";
|
|
197
|
+
}
|
|
198
|
+
const code = trimmedNonEmptyString(current.code);
|
|
199
|
+
if (code !== undefined) {
|
|
200
|
+
return code;
|
|
201
|
+
}
|
|
202
|
+
// A wrapper only holds the slot until a deeper level fills it.
|
|
203
|
+
if (message === undefined || isGenericFetchMessage(message)) {
|
|
204
|
+
message = trimmedNonEmptyString(current.message) ?? message;
|
|
205
|
+
}
|
|
206
|
+
current = current.cause;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return message ?? String(error);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
interface ServedEdgeConfig {
|
|
213
|
+
assistantId?: string;
|
|
214
|
+
assistantName?: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* What the config request settled: the identity the pairing edge reports, a
|
|
219
|
+
* positive "whatever is here does not serve the pairing app", or nothing.
|
|
220
|
+
*/
|
|
221
|
+
type ServedConfigReading =
|
|
222
|
+
| { kind: "unanswered" }
|
|
223
|
+
| { kind: "notServed"; detail: string }
|
|
224
|
+
| { kind: "served"; config: ServedEdgeConfig };
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* A 2xx JSON object carrying the pairing edge's marker is that edge
|
|
228
|
+
* identifying itself, any other answer is an edge that does not serve the
|
|
229
|
+
* pairing app, and no answer is no evidence.
|
|
230
|
+
*/
|
|
231
|
+
async function readServedConfig(
|
|
232
|
+
response: Response | undefined,
|
|
233
|
+
): Promise<ServedConfigReading> {
|
|
234
|
+
if (response === undefined) {
|
|
235
|
+
return { kind: "unanswered" };
|
|
236
|
+
}
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
await discardBody(response);
|
|
239
|
+
return { kind: "notServed", detail: `HTTP ${response.status}` };
|
|
240
|
+
}
|
|
241
|
+
let detail = UNREADABLE_CONFIG_DETAIL;
|
|
242
|
+
try {
|
|
243
|
+
const body: unknown = await response.json();
|
|
244
|
+
if (isPlainObject(body)) {
|
|
245
|
+
if (body.mode === PAIRING_EDGE_MODE) {
|
|
246
|
+
return {
|
|
247
|
+
kind: "served",
|
|
248
|
+
config: {
|
|
249
|
+
assistantId: trimmedNonEmptyString(body.assistantId),
|
|
250
|
+
assistantName: trimmedNonEmptyString(body.assistantName),
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
detail = UNMARKED_CONFIG_DETAIL;
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
/* an unparseable body is an edge answering with something else */
|
|
258
|
+
}
|
|
259
|
+
await discardBody(response);
|
|
260
|
+
return { kind: "notServed", detail };
|
|
261
|
+
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { createServer } from "node:net";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
3
4
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
4
5
|
|
|
5
6
|
import { ensureSocketPathFree } from "../socket-cleanup.js";
|
|
6
7
|
import { resolveIpcSocketPath } from "../socket-path.js";
|
|
7
8
|
|
|
9
|
+
// Fake workspace under tmpdir so the live-workspace guard accepts it.
|
|
10
|
+
const WS_DIR = join(tmpdir(), "vellum-workspace-test");
|
|
11
|
+
|
|
8
12
|
let savedWorkspaceDir: string | undefined;
|
|
9
13
|
let savedGatewayIpcSocketDir: string | undefined;
|
|
10
14
|
|
|
@@ -29,7 +33,7 @@ afterEach(() => {
|
|
|
29
33
|
describe("resolveIpcSocketPath", () => {
|
|
30
34
|
test("uses env var override when set", () => {
|
|
31
35
|
process.env.GATEWAY_IPC_SOCKET_DIR = "/run/gateway-ipc";
|
|
32
|
-
process.env.VELLUM_WORKSPACE_DIR =
|
|
36
|
+
process.env.VELLUM_WORKSPACE_DIR = WS_DIR;
|
|
33
37
|
|
|
34
38
|
const resolved = resolveIpcSocketPath("gateway");
|
|
35
39
|
|
|
@@ -39,28 +43,32 @@ describe("resolveIpcSocketPath", () => {
|
|
|
39
43
|
|
|
40
44
|
test("ignores empty env var override", () => {
|
|
41
45
|
process.env.GATEWAY_IPC_SOCKET_DIR = " ";
|
|
42
|
-
process.env.VELLUM_WORKSPACE_DIR =
|
|
46
|
+
process.env.VELLUM_WORKSPACE_DIR = WS_DIR;
|
|
43
47
|
|
|
44
48
|
const resolved = resolveIpcSocketPath("gateway");
|
|
45
49
|
|
|
46
50
|
expect(resolved.source).toBe("workspace");
|
|
47
|
-
expect(resolved.path).toBe("
|
|
51
|
+
expect(resolved.path).toBe(join(WS_DIR, "gateway.sock"));
|
|
48
52
|
});
|
|
49
53
|
|
|
50
54
|
test("uses workspace path by default", () => {
|
|
51
55
|
delete process.env.GATEWAY_IPC_SOCKET_DIR;
|
|
52
|
-
process.env.VELLUM_WORKSPACE_DIR =
|
|
56
|
+
process.env.VELLUM_WORKSPACE_DIR = WS_DIR;
|
|
53
57
|
|
|
54
58
|
const resolved = resolveIpcSocketPath("gateway");
|
|
55
59
|
|
|
56
60
|
expect(resolved.source).toBe("workspace");
|
|
57
|
-
expect(resolved.path).toBe("
|
|
61
|
+
expect(resolved.path).toBe(join(WS_DIR, "gateway.sock"));
|
|
58
62
|
});
|
|
59
63
|
|
|
60
64
|
test("falls back to tmpdir when workspace path exceeds AF_UNIX limit", () => {
|
|
61
65
|
delete process.env.GATEWAY_IPC_SOCKET_DIR;
|
|
62
66
|
// 90-char workspace dir + /gateway.sock = well over 103 bytes
|
|
63
|
-
process.env.VELLUM_WORKSPACE_DIR =
|
|
67
|
+
process.env.VELLUM_WORKSPACE_DIR = join(
|
|
68
|
+
tmpdir(),
|
|
69
|
+
"a".repeat(90),
|
|
70
|
+
"workspace",
|
|
71
|
+
);
|
|
64
72
|
|
|
65
73
|
const resolved = resolveIpcSocketPath("gateway");
|
|
66
74
|
|
|
@@ -70,7 +78,7 @@ describe("resolveIpcSocketPath", () => {
|
|
|
70
78
|
|
|
71
79
|
test("derives a hyphenated socket name's env var and filename", () => {
|
|
72
80
|
process.env.EXAMPLE_SERVICE_IPC_SOCKET_DIR = "/run/example-ipc";
|
|
73
|
-
process.env.VELLUM_WORKSPACE_DIR =
|
|
81
|
+
process.env.VELLUM_WORKSPACE_DIR = WS_DIR;
|
|
74
82
|
|
|
75
83
|
const resolved = resolveIpcSocketPath("example-service");
|
|
76
84
|
|
|
@@ -143,7 +143,7 @@ describe("ipcClassifyRisk retry", () => {
|
|
|
143
143
|
const CLASSIFY_OK = {
|
|
144
144
|
risk: "low",
|
|
145
145
|
reason: "safe",
|
|
146
|
-
matchType: "
|
|
146
|
+
matchType: "registry",
|
|
147
147
|
scopeOptions: [],
|
|
148
148
|
};
|
|
149
149
|
const emptyParams = {} as unknown as Parameters<typeof ipcClassifyRisk>[0];
|
|
@@ -10,16 +10,17 @@
|
|
|
10
10
|
* deterministic fallback for long AF_UNIX paths.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import {
|
|
14
|
+
type ClassifyRiskIpcParams,
|
|
15
|
+
type ClassifyRiskIpcResponse,
|
|
16
|
+
ClassifyRiskIpcResponseSchema,
|
|
17
|
+
} from "@vellumai/gateway-client";
|
|
13
18
|
import {
|
|
14
19
|
ipcCall as packageIpcCall,
|
|
15
20
|
IpcCallError,
|
|
16
21
|
PersistentIpcClient as PackagePersistentIpcClient,
|
|
17
22
|
} from "@vellumai/gateway-client/ipc-client";
|
|
18
23
|
|
|
19
|
-
import type {
|
|
20
|
-
ClassificationResult,
|
|
21
|
-
ClassifyRiskParams,
|
|
22
|
-
} from "../permissions/ipc-risk-types.js";
|
|
23
24
|
import { getLogger } from "../util/logger.js";
|
|
24
25
|
import { abortableSleep, computeRetryDelay } from "../util/retry.js";
|
|
25
26
|
import { resolveIpcSocketPath } from "./socket-path.js";
|
|
@@ -173,37 +174,29 @@ const CLASSIFY_RISK_ATTEMPT_TIMEOUT_MS = 1_500; // per-attempt cap (< 5s default
|
|
|
173
174
|
* When a `signal` is supplied, retries stop as soon as it aborts.
|
|
174
175
|
*/
|
|
175
176
|
export async function ipcClassifyRisk(
|
|
176
|
-
params:
|
|
177
|
+
params: ClassifyRiskIpcParams,
|
|
177
178
|
signal?: AbortSignal,
|
|
178
|
-
): Promise<
|
|
179
|
+
): Promise<ClassifyRiskIpcResponse | undefined> {
|
|
179
180
|
for (let attempt = 0; ; attempt++) {
|
|
180
181
|
try {
|
|
181
182
|
const result = await ipcCallPersistent(
|
|
182
183
|
"classify_risk",
|
|
183
|
-
params
|
|
184
|
+
params,
|
|
184
185
|
CLASSIFY_RISK_ATTEMPT_TIMEOUT_MS,
|
|
185
186
|
);
|
|
186
187
|
|
|
187
|
-
//
|
|
188
|
+
// A returned-but-malformed response is deterministic, not transient:
|
|
188
189
|
// fail closed immediately (no retry).
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
{ result },
|
|
192
|
-
"ipcClassifyRisk: gateway returned non-object response",
|
|
193
|
-
);
|
|
194
|
-
return undefined;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const obj = result as Record<string, unknown>;
|
|
198
|
-
if (typeof obj.risk !== "string") {
|
|
190
|
+
const parsed = ClassifyRiskIpcResponseSchema.safeParse(result);
|
|
191
|
+
if (!parsed.success) {
|
|
199
192
|
log.warn(
|
|
200
|
-
{ result },
|
|
201
|
-
"ipcClassifyRisk: gateway response
|
|
193
|
+
{ result, issues: parsed.error.issues },
|
|
194
|
+
"ipcClassifyRisk: gateway response does not match the classify_risk contract",
|
|
202
195
|
);
|
|
203
196
|
return undefined;
|
|
204
197
|
}
|
|
205
198
|
|
|
206
|
-
return
|
|
199
|
+
return parsed.data;
|
|
207
200
|
} catch (err) {
|
|
208
201
|
// A structured gateway error means the gateway was reachable and
|
|
209
202
|
// deterministically rejected the request — not a transient blip.
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
removeIpcEndpointFile,
|
|
16
16
|
} from "@vellumai/ipc-server-utils";
|
|
17
17
|
|
|
18
|
+
import { makeAddrInUseError } from "../daemon/startup-error.js";
|
|
19
|
+
|
|
18
20
|
/**
|
|
19
21
|
* Maximum time to wait for the probe `connect()` to settle before declaring
|
|
20
22
|
* the path occupied. Without a bound, a hung process holding the socket would
|
|
@@ -25,17 +27,6 @@ import {
|
|
|
25
27
|
*/
|
|
26
28
|
const PROBE_CONNECT_TIMEOUT_MS = 2000;
|
|
27
29
|
|
|
28
|
-
/**
|
|
29
|
-
* Build an `EADDRINUSE`-coded error so callers (and `categorizeDaemonError`)
|
|
30
|
-
* can branch on `err.code` and surface the structured "already running"
|
|
31
|
-
* guidance instead of a generic UNKNOWN.
|
|
32
|
-
*/
|
|
33
|
-
function makeAddrInUseError(message: string): NodeJS.ErrnoException {
|
|
34
|
-
const err = new Error(message) as NodeJS.ErrnoException;
|
|
35
|
-
err.code = "EADDRINUSE";
|
|
36
|
-
return err;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
30
|
/**
|
|
40
31
|
* Probe-connect to `socketPath`. Behavior:
|
|
41
32
|
* - Path doesn't exist → return.
|
|
@@ -67,7 +67,7 @@ mock.module("../../daemon/mcp-reload-service.js", () => ({
|
|
|
67
67
|
|
|
68
68
|
mock.module("../../config/env-registry.js", () => ({
|
|
69
69
|
getIsContainerized: () => false,
|
|
70
|
-
getWorkspaceDirOverride: () =>
|
|
70
|
+
getWorkspaceDirOverride: () => process.env.VELLUM_WORKSPACE_DIR,
|
|
71
71
|
// Imported by the real util/logger.js; ESM named-import validation
|
|
72
72
|
// requires it even though the silent test logger never calls it.
|
|
73
73
|
getDebugStdoutLogs: () => false,
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { CHANNEL_IDS } from "../channels/types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What a stored channel message row knows about itself, in terms no single
|
|
7
|
+
* provider owns.
|
|
8
|
+
*
|
|
9
|
+
* "Provider" here is the channel sense, the one `messaging/providers/*` uses:
|
|
10
|
+
* Slack, Telegram, Discord. It is not the inference sense in `providers/*`,
|
|
11
|
+
* whose `providerMetadata` rides on a content block and describes a model.
|
|
12
|
+
*
|
|
13
|
+
* Named for the provider rather than the channel because `channel metadata`
|
|
14
|
+
* is already taken: `buildChannelMetadata` builds the whole envelope a
|
|
15
|
+
* message row carries (provenance, `userMessageChannel`, interfaces), and
|
|
16
|
+
* this is one key inside that envelope. This is the neutral counterpart of
|
|
17
|
+
* `slackMeta`, so it reads as the generalization of what `providers/slack`
|
|
18
|
+
* stores.
|
|
19
|
+
*
|
|
20
|
+
* Every channel needs the same facts to reconstruct a transcript: which chat
|
|
21
|
+
* the row belongs to, the provider's id for the row itself, the thread it sits
|
|
22
|
+
* in, whether it is a message or a reaction, and, when it is a reaction, which
|
|
23
|
+
* message it was attached to.
|
|
24
|
+
*
|
|
25
|
+
* History assembly is structural, not Slack logic: it groups a reaction with
|
|
26
|
+
* the message it targets, inside a thread. That rule holds for any
|
|
27
|
+
* thread-scoped channel.
|
|
28
|
+
*
|
|
29
|
+
* The schema passes through what it does not name, so a provider carries its
|
|
30
|
+
* own fields (Slack's file markers and timezone labels) on this same object
|
|
31
|
+
* and validates them with its own schema. This is how `SourceMetadataSchema`
|
|
32
|
+
* already carries `slackBotMentioned` and the email fields on the wire: one
|
|
33
|
+
* object per row, no per-provider sub-envelope, and no second copy of
|
|
34
|
+
* anything named here.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const providerReactionMetadataSchema = z.object({
|
|
38
|
+
/**
|
|
39
|
+
* Provider id of the message this reaction was attached to, in the same
|
|
40
|
+
* namespace as `messageId`. Resolution is keyed on it, so it is required.
|
|
41
|
+
*/
|
|
42
|
+
targetMessageId: z.string(),
|
|
43
|
+
emoji: z.string(),
|
|
44
|
+
op: z.enum(["added", "removed"]),
|
|
45
|
+
actorDisplayName: z.string().optional(),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export const providerMessageMetadataSchema = z
|
|
49
|
+
.object({
|
|
50
|
+
source: z.enum(CHANNEL_IDS),
|
|
51
|
+
/**
|
|
52
|
+
* Provider id of the chat, channel or room this row belongs to. Same name
|
|
53
|
+
* and meaning as on the inbound wire, where "conversation" is the canonical
|
|
54
|
+
* word for a delivery address and "channel" names the provider.
|
|
55
|
+
*/
|
|
56
|
+
conversationExternalId: z.string(),
|
|
57
|
+
/**
|
|
58
|
+
* Provider id of this row itself. Absent on a reaction, which no channel
|
|
59
|
+
* gives an id of its own: a reaction event names the chat, the message it
|
|
60
|
+
* was attached to, the actor and the emoji, and nothing identifies the
|
|
61
|
+
* reaction. The message it acts on is `reaction.targetMessageId`.
|
|
62
|
+
*/
|
|
63
|
+
messageId: z.string().optional(),
|
|
64
|
+
/**
|
|
65
|
+
* Provider id of the thread this row sits in, absent when it is not in one.
|
|
66
|
+
* Never synthesized from `messageId`: a value here asserts that a thread
|
|
67
|
+
* exists, and inventing one keys conversations on threads that never do.
|
|
68
|
+
*/
|
|
69
|
+
threadId: z.string().optional(),
|
|
70
|
+
/**
|
|
71
|
+
* Provider id of whoever authored the row or performed the reaction. Trust
|
|
72
|
+
* is keyed on this everywhere else, and it is what lets a channel say who
|
|
73
|
+
* acted; a display name is a label, not an identity. Optional because a
|
|
74
|
+
* provider envelope need not carry one.
|
|
75
|
+
*/
|
|
76
|
+
actorExternalId: z.string().optional(),
|
|
77
|
+
displayName: z.string().optional(),
|
|
78
|
+
/**
|
|
79
|
+
* Only reactions get a row of their own. An edit rewrites the row it
|
|
80
|
+
* targets and a delete stamps `deletedAt` on it, so neither appears here.
|
|
81
|
+
*/
|
|
82
|
+
eventKind: z.enum(["message", "reaction"]),
|
|
83
|
+
reaction: providerReactionMetadataSchema.optional(),
|
|
84
|
+
editedAt: z.number().optional(),
|
|
85
|
+
deletedAt: z.number().optional(),
|
|
86
|
+
})
|
|
87
|
+
.passthrough();
|
|
88
|
+
|
|
89
|
+
export type ProviderReactionMetadata = z.infer<
|
|
90
|
+
typeof providerReactionMetadataSchema
|
|
91
|
+
>;
|
|
92
|
+
export type ProviderMessageMetadata = z.infer<
|
|
93
|
+
typeof providerMessageMetadataSchema
|
|
94
|
+
>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Parse and validate a serialized `ProviderMessageMetadata`, the counterpart of
|
|
98
|
+
* `readSlackMetadata` for the neutral shape. Anything that does not parse or
|
|
99
|
+
* does not validate reads as null.
|
|
100
|
+
*/
|
|
101
|
+
export function readProviderMessageMetadata(
|
|
102
|
+
raw: unknown,
|
|
103
|
+
): ProviderMessageMetadata | null {
|
|
104
|
+
if (typeof raw !== "string") {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
let parsed: unknown;
|
|
108
|
+
try {
|
|
109
|
+
parsed = JSON.parse(raw);
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const result = providerMessageMetadataSchema.safeParse(parsed);
|
|
114
|
+
return result.success ? result.data : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The id a row is grouped by when assembling a thread: its own for a message,
|
|
119
|
+
* its target's for a reaction, so a reaction lands beside the message it was
|
|
120
|
+
* attached to rather than in a block of its own.
|
|
121
|
+
*
|
|
122
|
+
* Undefined for a row that identifies neither, which cannot be placed.
|
|
123
|
+
*/
|
|
124
|
+
export function groupingMessageId(
|
|
125
|
+
meta: ProviderMessageMetadata,
|
|
126
|
+
): string | undefined {
|
|
127
|
+
return meta.reaction?.targetMessageId ?? meta.messageId;
|
|
128
|
+
}
|