@vellumai/assistant 0.11.4 → 0.11.5-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/ARCHITECTURE.md +29 -4
- package/README.md +1 -1
- package/docs/architecture/memory.md +17 -5
- package/docs/architecture/security.md +96 -152
- package/docs/guardian-request-flow.md +13 -2
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +29 -31
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +193 -3
- package/package.json +1 -1
- package/src/__tests__/anthropic-provider.test.ts +10 -3
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +4 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
- package/src/__tests__/channel-reply-delivery.test.ts +26 -13
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
- package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
- package/src/__tests__/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-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 +22 -0
- package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/platform.test.ts +16 -1
- package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
- package/src/__tests__/plugin-api-shim.test.ts +5 -0
- package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
- package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/resource-pressure-guard.test.ts +428 -0
- package/src/__tests__/resource-pressure-routes.test.ts +113 -0
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
- package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
- package/src/__tests__/skill-tool-factory.test.ts +161 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +63 -38
- package/src/__tests__/tool-policy.test.ts +97 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
- package/src/__tests__/user-plugin-loader.test.ts +2 -0
- package/src/__tests__/validate-input.test.ts +243 -7
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -0
- package/src/__tests__/voice-session-bridge.test.ts +42 -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/events/resource-pressure-status-changed.ts +53 -0
- package/src/api/index.ts +19 -0
- package/src/api/responses/resource-pressure-status.ts +25 -0
- package/src/approvals/guardian-channel-delivery.ts +70 -6
- package/src/approvals/guardian-request-resolvers.ts +32 -66
- package/src/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 +1 -0
- package/src/cli/commands/__tests__/inference-providers.test.ts +68 -0
- package/src/cli/commands/__tests__/keys.test.ts +89 -8
- package/src/cli/commands/__tests__/plugins.test.ts +57 -1
- package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
- package/src/cli/commands/channel-verification-sessions.ts +11 -21
- package/src/cli/commands/conversations.help.ts +34 -0
- package/src/cli/commands/conversations.ts +125 -0
- package/src/cli/commands/inference-providers.ts +9 -4
- package/src/cli/commands/inference.help.ts +9 -4
- package/src/cli/commands/keys.help.ts +13 -1
- package/src/cli/commands/keys.ts +26 -15
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/index.help.ts +41 -27
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/monitoring.ts +1 -0
- package/src/cli/commands/plugins.help.ts +28 -31
- package/src/cli/commands/plugins.ts +10 -0
- package/src/cli/commands/skills.help.ts +13 -16
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
- package/src/cli/lib/bundled-marketplace.json +13 -0
- package/src/cli/lib/daemon-credential-client.ts +25 -3
- package/src/cli/lib/install-from-github.ts +35 -24
- package/src/cli/lib/merge-plugin-tree.ts +22 -4
- package/src/cli/lib/plugin-surfaces.ts +27 -0
- package/src/config/__tests__/balanced-model-experiment.test.ts +7 -7
- package/src/config/__tests__/default-profile-catalog.test.ts +3 -3
- package/src/config/default-profile-catalog.ts +5 -6
- package/src/config/feature-flag-registry.json +32 -32
- package/src/config/webhook-routing.ts +8 -0
- package/src/context/compactor.ts +31 -2
- package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
- package/src/daemon/conversation-error.ts +13 -0
- package/src/daemon/conversation-messaging.ts +22 -2
- package/src/daemon/conversation-process.ts +11 -0
- package/src/daemon/conversation-store.ts +10 -0
- package/src/daemon/conversation-surfaces.ts +21 -8
- package/src/daemon/conversation.ts +12 -10
- package/src/daemon/lifecycle.ts +6 -2
- package/src/daemon/message-types/conversations.ts +5 -7
- package/src/daemon/provider-rejection-log-fields.ts +124 -0
- package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
- package/src/daemon/resource-pressure-guard.ts +387 -0
- package/src/daemon/shutdown-handlers.ts +2 -0
- package/src/daemon/startup-error.ts +16 -0
- package/src/daemon/trust-context.ts +14 -10
- package/src/daemon/unsendable-image-notice.ts +76 -0
- package/src/hooks/registry.ts +65 -51
- package/src/ipc/__tests__/socket-path.test.ts +15 -7
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/socket-cleanup.ts +2 -11
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
- package/src/messaging/provider-message-metadata.ts +128 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +116 -68
- package/src/messaging/providers/channel-transport.ts +90 -13
- package/src/messaging/providers/discord/send.ts +16 -0
- package/src/messaging/providers/discord/transport.ts +11 -1
- package/src/messaging/providers/index.ts +109 -16
- package/src/messaging/providers/slack/message-metadata.ts +45 -0
- package/src/messaging/providers/slack/render-transcript.ts +1 -4
- package/src/messaging/providers/slack/send.test.ts +6 -9
- package/src/messaging/providers/slack/send.ts +40 -28
- package/src/messaging/providers/slack/transport.ts +22 -26
- package/src/messaging/providers/telegram-bot/send.test.ts +2 -8
- package/src/messaging/providers/telegram-bot/transport.ts +3 -6
- package/src/messaging/read-provider-metadata.test.ts +157 -0
- package/src/messaging/read-provider-metadata.ts +51 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
- package/src/notifications/__tests__/edit-notification.test.ts +174 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
- package/src/notifications/adapters/macos.ts +55 -0
- package/src/notifications/adapters/slack.ts +10 -5
- package/src/notifications/assistant-reply-producer.ts +37 -4
- package/src/notifications/conversation-pairing.ts +111 -21
- package/src/notifications/edit-notification.ts +38 -8
- package/src/notifications/emit-signal.ts +12 -14
- package/src/notifications/home-feed-side-effect.ts +218 -15
- package/src/notifications/types.ts +5 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +28 -3
- package/src/permissions/confirmation-guardian-request.ts +5 -1
- package/src/persistence/conversation-crud.ts +23 -20
- package/src/persistence/conversation-queries.ts +14 -1
- package/src/persistence/conversation-types.ts +22 -0
- package/src/persistence/delivery-crud.ts +122 -15
- package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
- package/src/plugin-api/conversation-turn.ts +27 -0
- package/src/plugin-api/credential-scope.test.ts +15 -0
- package/src/plugin-api/credential-scope.ts +14 -0
- package/src/plugin-api/index.ts +19 -1
- package/src/plugin-api/resolve-credential.ts +15 -10
- package/src/plugin-api/store-credential.ts +148 -0
- package/src/plugin-api/system-card.ts +36 -0
- package/src/plugin-api/vision-support.test.ts +82 -0
- package/src/plugin-api/vision-support.ts +14 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
- package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
- package/src/plugins/defaults/image-recovery/detect.ts +24 -1
- package/src/plugins/defaults/main.ts +15 -0
- package/src/plugins/defaults/memory/AGENTS.md +11 -3
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/mtime-cache.ts +1 -1
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/plugin-tree-walk.ts +63 -4
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/connection-resolution.ts +2 -1
- package/src/providers/content-blocks.ts +22 -0
- package/src/providers/gemini/client.ts +5 -2
- package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
- package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
- package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
- package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
- package/src/providers/inference/adapter-factory.ts +33 -2
- package/src/providers/inference/auth.ts +13 -2
- package/src/providers/inference/credential-usage.ts +37 -0
- package/src/providers/inference/missing-credential-guard.ts +110 -0
- package/src/providers/inference/resolve-auth.ts +7 -7
- package/src/providers/media-resolve.ts +176 -15
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/chat-completions-provider.ts +322 -42
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +31 -10
- package/src/runtime/AGENTS.md +35 -0
- package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
- package/src/runtime/__tests__/web-presence.test.ts +234 -0
- package/src/runtime/assistant-event-hub.ts +38 -0
- package/src/runtime/channel-reply-delivery.ts +54 -31
- package/src/runtime/channel-retry-sweep.ts +6 -4
- package/src/runtime/effective-capabilities.test.ts +19 -0
- package/src/runtime/effective-capabilities.ts +25 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/http-errors.ts +1 -0
- package/src/runtime/http-server.ts +20 -5
- package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
- package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +52 -1
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +88 -35
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
- package/src/runtime/routes/approval-routes.ts +37 -4
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
- package/src/runtime/routes/canned-message-complete.ts +68 -29
- package/src/runtime/routes/client-routes.ts +113 -0
- package/src/runtime/routes/conversation-list-routes.ts +22 -7
- package/src/runtime/routes/conversation-management-routes.ts +15 -9
- package/src/runtime/routes/conversation-routes.ts +36 -22
- package/src/runtime/routes/credential-in-use.ts +85 -0
- package/src/runtime/routes/credential-routes.ts +55 -85
- package/src/runtime/routes/guardian-approval-interception.ts +22 -22
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
- package/src/runtime/routes/identity-routes.ts +5 -121
- package/src/runtime/routes/inbound-message-handler.ts +71 -27
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +77 -50
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -97
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +9 -7
- package/src/runtime/routes/monitoring-routes.ts +9 -1
- package/src/runtime/routes/plugins-routes.ts +17 -5
- package/src/runtime/routes/resource-pressure-routes.ts +22 -0
- package/src/runtime/routes/secret-routes.ts +39 -8
- package/src/runtime/routes/settings-routes.ts +6 -6
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/user-route-dispatcher.ts +31 -2
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/slack-reply-session.test.ts +23 -13
- package/src/runtime/slack-reply-session.ts +38 -55
- package/src/runtime/web-presence.ts +90 -0
- package/src/skills/validate-input.ts +177 -40
- package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
- package/src/subagent/consult-context.ts +7 -23
- package/src/tools/credentials/broker.ts +24 -79
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/credentials/tool-policy.ts +67 -0
- package/src/tools/executor.ts +53 -15
- package/src/tools/network/__tests__/web-search.test.ts +41 -1
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/network/web-search.ts +21 -3
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/schema-transforms.ts +40 -5
- package/src/tools/shared/input-repairs.ts +160 -0
- package/src/tools/skills/skill-tool-factory.ts +18 -4
- package/src/tools/subagent/spawn.ts +0 -1
- package/src/tools/tool-approval-handler.ts +16 -10
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/util/__tests__/cgroup-memory.test.ts +3 -0
- package/src/util/cgroup-memory.ts +3 -0
- package/src/util/container-cpu-sampler.ts +250 -0
- package/src/util/image-conversion.ts +178 -14
- package/src/util/platform.ts +93 -10
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
package/src/hooks/registry.ts
CHANGED
|
@@ -11,13 +11,16 @@
|
|
|
11
11
|
* boot, plus the user-land lookup that walks discovered plugin names from
|
|
12
12
|
* the plugin cache.
|
|
13
13
|
*
|
|
14
|
-
* {@link getHooksFor}
|
|
15
|
-
*
|
|
16
|
-
* from
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* {@link getHooksFor} and {@link getUserHooksFor} both resolve through
|
|
15
|
+
* {@link getUserHookEntriesFor}, which prepends in-process default-plugin
|
|
16
|
+
* hooks from this registry (filtered by `isPluginDisabled` at read time)
|
|
17
|
+
* ahead of user-land hooks from the plugin cache. The read-time filtering
|
|
18
|
+
* is what makes `assistant plugins disable default-*` take effect immediately
|
|
19
|
+
* in a running assistant: the hooks stay registered but are filtered out on
|
|
20
|
+
* the next turn.
|
|
19
21
|
*/
|
|
20
22
|
|
|
23
|
+
import { isFirstPartyDefaultPlugin } from "../plugins/defaults/main.js";
|
|
21
24
|
import { isPluginDisabled } from "../plugins/disabled-state.js";
|
|
22
25
|
import { getDiscoveredUserPluginNames } from "../plugins/mtime-cache.js";
|
|
23
26
|
import type { HookEntry, HookFunction } from "../plugins/types.js";
|
|
@@ -78,35 +81,74 @@ export function unregisterPluginHooks(pluginName: string): void {
|
|
|
78
81
|
// ─── Queries ─────────────────────────────────────────────────────────────────
|
|
79
82
|
|
|
80
83
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
84
|
+
* In-process (default plugin) hook entries for `name`, filtered by the
|
|
85
|
+
* `.disabled` sentinel at read time. First-party default plugins always pass
|
|
86
|
+
* the per-chat allowlist; other in-process registrations (test fixtures) honor
|
|
87
|
+
* it. Used by {@link getUserHookEntriesFor} so `getUserHooksFor` returns the
|
|
88
|
+
* same default-plugin hooks {@link getHooksFor} does.
|
|
89
|
+
*/
|
|
90
|
+
function collectInProcessHookEntries<TCtx = unknown>(
|
|
91
|
+
name: string,
|
|
92
|
+
effectiveEnabledPlugins?: Set<string> | null,
|
|
93
|
+
): HookEntry<TCtx>[] {
|
|
94
|
+
const defaultEntries: HookEntry<TCtx>[] = [];
|
|
95
|
+
for (const entry of hookRegistry.get(name) ?? []) {
|
|
96
|
+
if (isPluginDisabled(entry.pluginName)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (
|
|
100
|
+
effectiveEnabledPlugins != null &&
|
|
101
|
+
!effectiveEnabledPlugins.has(entry.pluginName) &&
|
|
102
|
+
!isFirstPartyDefaultPlugin(entry.pluginName)
|
|
103
|
+
) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
defaultEntries.push({
|
|
107
|
+
fn: entry.fn as HookFunction<TCtx>,
|
|
108
|
+
owner: { kind: "plugin", id: entry.pluginName },
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return defaultEntries;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get all hooks for a given event name: in-process default-plugin hooks
|
|
116
|
+
* first, then user plugins and standalone workspace hooks. Plugin hooks run
|
|
83
117
|
* in install-date order, the workspace hook runs last.
|
|
84
118
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* (`
|
|
119
|
+
* Default-plugin hooks come from the in-process registry (registration at
|
|
120
|
+
* boot). User-land hooks are a pure cache read: this never scans disk,
|
|
121
|
+
* activates a plugin, or runs `init`. Activation happens only at boot
|
|
122
|
+
* (`populateCacheAtBoot`) and through the imperative install/uninstall
|
|
123
|
+
* poke (`reconcilePluginSourcesNow`), both main-daemon paths, so a
|
|
89
124
|
* sidecar process that dispatches hooks (a worker running conversation
|
|
90
125
|
* turns) can never bring a plugin up in its own process.
|
|
91
126
|
*
|
|
92
127
|
* `effectiveEnabledPlugins` carries the per-chat plugin scope: when non-null,
|
|
93
|
-
* user plugins outside the set are skipped
|
|
94
|
-
*
|
|
128
|
+
* user plugins outside the set are skipped. First-party default plugins
|
|
129
|
+
* always pass that allowlist (a workspace `.disabled` sentinel still
|
|
130
|
+
* excludes them). Standalone workspace hooks always run. `null`/omitted
|
|
131
|
+
* means no per-chat restriction.
|
|
95
132
|
*/
|
|
96
133
|
export async function getUserHookEntriesFor<TCtx = unknown>(
|
|
97
134
|
hookName: string,
|
|
98
135
|
effectiveEnabledPlugins?: Set<string> | null,
|
|
99
136
|
): Promise<HookEntry<TCtx>[]> {
|
|
100
|
-
|
|
137
|
+
const defaultEntries = collectInProcessHookEntries<TCtx>(
|
|
138
|
+
hookName,
|
|
139
|
+
effectiveEnabledPlugins,
|
|
140
|
+
);
|
|
141
|
+
const userEntries = await collectUserHookEntries<TCtx>(
|
|
101
142
|
hookName,
|
|
102
143
|
getDiscoveredUserPluginNames(),
|
|
103
144
|
effectiveEnabledPlugins,
|
|
104
145
|
);
|
|
146
|
+
return [...defaultEntries, ...userEntries];
|
|
105
147
|
}
|
|
106
148
|
|
|
107
149
|
/**
|
|
108
150
|
* {@link getUserHookEntriesFor} without owner attribution. Returns just the
|
|
109
|
-
* hook functions in the same order.
|
|
151
|
+
* hook functions in the same order, including in-process default-plugin hooks.
|
|
110
152
|
*/
|
|
111
153
|
export async function getUserHooksFor<TCtx = unknown>(
|
|
112
154
|
hookName: string,
|
|
@@ -131,17 +173,19 @@ export async function getUserHooksFor<TCtx = unknown>(
|
|
|
131
173
|
* (async, pure cache read). Default hooks are prepended so they compose
|
|
132
174
|
* innermost, ahead of any user plugins.
|
|
133
175
|
*
|
|
134
|
-
* The `TCtx` generic mirrors {@link HookFunction}'s
|
|
176
|
+
* The `TCtx` generic mirrors {@link HookFunction}'s: callers parameterize
|
|
135
177
|
* over the concrete context type their hook receives. Hooks that mutate the
|
|
136
178
|
* context in place return `void`; hooks that return a new context replace
|
|
137
179
|
* the threaded value for the next hook in the chain.
|
|
138
180
|
*
|
|
139
181
|
* When `conversationId` is given, the conversation's effective plugin scope is
|
|
140
182
|
* resolved from it (memory, then DB) and layered on top of the global disabled
|
|
141
|
-
* check: a
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
183
|
+
* check: a user plugin must also be a member of that set or its hook is
|
|
184
|
+
* excluded for this turn. First-party default plugins are not subject to the
|
|
185
|
+
* per-chat allowlist (they are core runtime infrastructure; the chat pills
|
|
186
|
+
* only list user-installed plugins), but a workspace `.disabled` sentinel
|
|
187
|
+
* still excludes them. Omit `conversationId` (or pass a conversation with no
|
|
188
|
+
* per-chat restriction) and every globally-enabled plugin's hooks run.
|
|
145
189
|
*/
|
|
146
190
|
export async function getHookEntriesFor<TCtx = unknown>(
|
|
147
191
|
name: string,
|
|
@@ -160,37 +204,7 @@ export async function getHookEntriesFor<TCtx = unknown>(
|
|
|
160
204
|
options.conversationId,
|
|
161
205
|
);
|
|
162
206
|
}
|
|
163
|
-
|
|
164
|
-
// sentinel at read time. This is what makes `assistant plugins disable
|
|
165
|
-
// default-*` take effect immediately in a running assistant: the hooks stay
|
|
166
|
-
// registered but are filtered out on the next turn.
|
|
167
|
-
const defaultEntries: HookEntry<TCtx>[] = [];
|
|
168
|
-
for (const entry of hookRegistry.get(name) ?? []) {
|
|
169
|
-
if (isPluginDisabled(entry.pluginName)) {
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
if (
|
|
173
|
-
effectiveEnabledPlugins != null &&
|
|
174
|
-
!effectiveEnabledPlugins.has(entry.pluginName)
|
|
175
|
-
) {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
defaultEntries.push({
|
|
179
|
-
fn: entry.fn as HookFunction<TCtx>,
|
|
180
|
-
owner: { kind: "plugin", id: entry.pluginName },
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// User-land hooks from the plugin cache (async, pure cache read; dispatch
|
|
185
|
-
// never activates plugins). The per-chat
|
|
186
|
-
// scope is threaded through so a deselected user plugin's hooks are excluded
|
|
187
|
-
// too — standalone workspace hooks (not owned by a plugin) always run.
|
|
188
|
-
const userEntries = await getUserHookEntriesFor<TCtx>(
|
|
189
|
-
name,
|
|
190
|
-
effectiveEnabledPlugins,
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
return [...defaultEntries, ...userEntries];
|
|
207
|
+
return getUserHookEntriesFor<TCtx>(name, effectiveEnabledPlugins);
|
|
194
208
|
}
|
|
195
209
|
|
|
196
210
|
/**
|
|
@@ -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
|
+
}
|