@vellumai/assistant 0.11.4 → 0.11.5-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/ARCHITECTURE.md +29 -4
- package/README.md +1 -1
- package/docs/architecture/memory.md +17 -5
- package/docs/architecture/security.md +96 -152
- package/docs/guardian-request-flow.md +13 -2
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +29 -31
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +193 -3
- package/package.json +1 -1
- package/src/__tests__/anthropic-provider.test.ts +10 -3
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +4 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
- package/src/__tests__/channel-reply-delivery.test.ts +26 -13
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
- package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
- package/src/__tests__/container-cpu-sampler.test.ts +153 -0
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +17 -0
- package/src/__tests__/conversation-lifecycle.test.ts +20 -26
- package/src/__tests__/conversation-pairing.test.ts +186 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
- package/src/__tests__/credential-broker-server-use.test.ts +24 -5
- package/src/__tests__/credential-routes.test.ts +22 -3
- package/src/__tests__/credential-security-invariants.test.ts +2 -1
- package/src/__tests__/daemon-credential-client.test.ts +88 -0
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/dm-backfill.test.ts +63 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -4
- package/src/__tests__/events-client-registration.test.ts +28 -0
- package/src/__tests__/file-write-tool.test.ts +4 -2
- package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/host-proxy-interface.test.ts +11 -1
- package/src/__tests__/host-shell-tool.test.ts +23 -4
- package/src/__tests__/image-conversion.test.ts +143 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/input-repairs.test.ts +207 -0
- package/src/__tests__/live-workspace-guard.test.ts +75 -0
- package/src/__tests__/mcp-abort-signal.test.ts +1 -1
- package/src/__tests__/mcp-client-auth.test.ts +1 -1
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
- package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
- package/src/__tests__/mtime-cache.test.ts +2 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
- package/src/__tests__/openai-provider.test.ts +22 -0
- package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/platform.test.ts +16 -1
- package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
- package/src/__tests__/plugin-api-shim.test.ts +5 -0
- package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
- package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/resource-pressure-guard.test.ts +428 -0
- package/src/__tests__/resource-pressure-routes.test.ts +113 -0
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
- package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
- package/src/__tests__/skill-tool-factory.test.ts +161 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +63 -38
- package/src/__tests__/tool-policy.test.ts +97 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
- package/src/__tests__/user-plugin-loader.test.ts +2 -0
- package/src/__tests__/validate-input.test.ts +243 -7
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
- package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
- package/src/acp/acp-claude-oauth.ts +38 -21
- package/src/acp/prepare-agent-env.ts +146 -55
- package/src/agent/loop-tool-dedup.test.ts +161 -0
- package/src/agent/loop.ts +68 -22
- package/src/api/constants/app-tools.ts +34 -0
- package/src/api/events/resource-pressure-status-changed.ts +53 -0
- package/src/api/index.ts +19 -0
- package/src/api/responses/resource-pressure-status.ts +25 -0
- package/src/approvals/guardian-channel-delivery.ts +70 -6
- package/src/approvals/guardian-request-resolvers.ts +32 -66
- package/src/channels/__tests__/message-audience.test.ts +49 -0
- package/src/channels/__tests__/types.test.ts +17 -3
- package/src/channels/message-audience.ts +40 -0
- package/src/channels/types.ts +16 -12
- package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
- package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
- package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/__tests__/inference-providers.test.ts +68 -0
- package/src/cli/commands/__tests__/keys.test.ts +89 -8
- package/src/cli/commands/__tests__/plugins.test.ts +57 -1
- package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
- package/src/cli/commands/channel-verification-sessions.ts +11 -21
- package/src/cli/commands/conversations.help.ts +34 -0
- package/src/cli/commands/conversations.ts +125 -0
- package/src/cli/commands/inference-providers.ts +9 -4
- package/src/cli/commands/inference.help.ts +9 -4
- package/src/cli/commands/keys.help.ts +13 -1
- package/src/cli/commands/keys.ts +26 -15
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/index.help.ts +41 -27
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/monitoring.ts +1 -0
- package/src/cli/commands/plugins.help.ts +28 -31
- package/src/cli/commands/plugins.ts +10 -0
- package/src/cli/commands/skills.help.ts +13 -16
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
- package/src/cli/lib/bundled-marketplace.json +13 -0
- package/src/cli/lib/daemon-credential-client.ts +25 -3
- package/src/cli/lib/install-from-github.ts +35 -24
- package/src/cli/lib/merge-plugin-tree.ts +22 -4
- package/src/cli/lib/plugin-surfaces.ts +27 -0
- package/src/config/feature-flag-registry.json +35 -19
- package/src/config/webhook-routing.ts +8 -0
- package/src/context/compactor.ts +31 -2
- package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
- package/src/daemon/conversation-error.ts +13 -0
- package/src/daemon/conversation-messaging.ts +22 -2
- package/src/daemon/conversation-process.ts +11 -0
- package/src/daemon/conversation-store.ts +10 -0
- package/src/daemon/conversation-surfaces.ts +21 -8
- package/src/daemon/conversation.ts +12 -10
- package/src/daemon/lifecycle.ts +6 -2
- package/src/daemon/message-types/conversations.ts +5 -7
- package/src/daemon/provider-rejection-log-fields.ts +124 -0
- package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
- package/src/daemon/resource-pressure-guard.ts +387 -0
- package/src/daemon/shutdown-handlers.ts +2 -0
- package/src/daemon/startup-error.ts +16 -0
- package/src/daemon/trust-context.ts +14 -10
- package/src/daemon/unsendable-image-notice.ts +76 -0
- package/src/hooks/registry.ts +65 -51
- package/src/ipc/__tests__/socket-path.test.ts +15 -7
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/socket-cleanup.ts +2 -11
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
- package/src/messaging/provider-message-metadata.ts +128 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +116 -68
- package/src/messaging/providers/channel-transport.ts +90 -13
- package/src/messaging/providers/discord/send.ts +16 -0
- package/src/messaging/providers/discord/transport.ts +11 -1
- package/src/messaging/providers/index.ts +109 -16
- package/src/messaging/providers/slack/message-metadata.ts +45 -0
- package/src/messaging/providers/slack/render-transcript.ts +1 -4
- package/src/messaging/providers/slack/send.test.ts +6 -9
- package/src/messaging/providers/slack/send.ts +40 -28
- package/src/messaging/providers/slack/transport.ts +22 -26
- package/src/messaging/providers/telegram-bot/send.test.ts +2 -8
- package/src/messaging/providers/telegram-bot/transport.ts +3 -6
- package/src/messaging/read-provider-metadata.test.ts +157 -0
- package/src/messaging/read-provider-metadata.ts +51 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
- package/src/notifications/__tests__/edit-notification.test.ts +174 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
- package/src/notifications/adapters/macos.ts +55 -0
- package/src/notifications/adapters/slack.ts +10 -5
- package/src/notifications/assistant-reply-producer.ts +37 -4
- package/src/notifications/conversation-pairing.ts +111 -21
- package/src/notifications/edit-notification.ts +38 -8
- package/src/notifications/emit-signal.ts +12 -14
- package/src/notifications/home-feed-side-effect.ts +218 -15
- package/src/notifications/types.ts +5 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +28 -3
- package/src/permissions/confirmation-guardian-request.ts +5 -1
- package/src/persistence/conversation-crud.ts +23 -20
- package/src/persistence/conversation-queries.ts +14 -1
- package/src/persistence/conversation-types.ts +22 -0
- package/src/persistence/delivery-crud.ts +122 -15
- package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
- package/src/plugin-api/conversation-turn.ts +27 -0
- package/src/plugin-api/credential-scope.test.ts +15 -0
- package/src/plugin-api/credential-scope.ts +14 -0
- package/src/plugin-api/index.ts +19 -1
- package/src/plugin-api/resolve-credential.ts +15 -10
- package/src/plugin-api/store-credential.ts +148 -0
- package/src/plugin-api/system-card.ts +36 -0
- package/src/plugin-api/vision-support.test.ts +82 -0
- package/src/plugin-api/vision-support.ts +14 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
- package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
- package/src/plugins/defaults/image-recovery/detect.ts +24 -1
- package/src/plugins/defaults/main.ts +15 -0
- package/src/plugins/defaults/memory/AGENTS.md +11 -3
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/mtime-cache.ts +1 -1
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/plugin-tree-walk.ts +63 -4
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/connection-resolution.ts +2 -1
- package/src/providers/content-blocks.ts +22 -0
- package/src/providers/gemini/client.ts +5 -2
- package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
- package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
- package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
- package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
- package/src/providers/inference/adapter-factory.ts +33 -2
- package/src/providers/inference/auth.ts +13 -2
- package/src/providers/inference/credential-usage.ts +37 -0
- package/src/providers/inference/missing-credential-guard.ts +110 -0
- package/src/providers/inference/resolve-auth.ts +7 -7
- package/src/providers/media-resolve.ts +176 -15
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/chat-completions-provider.ts +322 -42
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +31 -10
- package/src/runtime/AGENTS.md +35 -0
- package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
- package/src/runtime/__tests__/web-presence.test.ts +234 -0
- package/src/runtime/assistant-event-hub.ts +38 -0
- package/src/runtime/channel-reply-delivery.ts +54 -31
- package/src/runtime/channel-retry-sweep.ts +6 -4
- package/src/runtime/effective-capabilities.test.ts +19 -0
- package/src/runtime/effective-capabilities.ts +25 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/http-errors.ts +1 -0
- package/src/runtime/http-server.ts +20 -5
- package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
- package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +52 -1
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +88 -35
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
- package/src/runtime/routes/approval-routes.ts +37 -4
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
- package/src/runtime/routes/canned-message-complete.ts +68 -29
- package/src/runtime/routes/client-routes.ts +113 -0
- package/src/runtime/routes/conversation-list-routes.ts +22 -7
- package/src/runtime/routes/conversation-management-routes.ts +15 -9
- package/src/runtime/routes/conversation-routes.ts +36 -22
- package/src/runtime/routes/credential-in-use.ts +85 -0
- package/src/runtime/routes/credential-routes.ts +55 -85
- package/src/runtime/routes/guardian-approval-interception.ts +22 -22
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
- package/src/runtime/routes/identity-routes.ts +5 -121
- package/src/runtime/routes/inbound-message-handler.ts +71 -27
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +77 -50
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -97
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +9 -7
- package/src/runtime/routes/monitoring-routes.ts +9 -1
- package/src/runtime/routes/plugins-routes.ts +17 -5
- package/src/runtime/routes/resource-pressure-routes.ts +22 -0
- package/src/runtime/routes/secret-routes.ts +39 -8
- package/src/runtime/routes/settings-routes.ts +6 -6
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/user-route-dispatcher.ts +31 -2
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/slack-reply-session.test.ts +23 -13
- package/src/runtime/slack-reply-session.ts +38 -55
- package/src/runtime/web-presence.ts +90 -0
- package/src/skills/validate-input.ts +177 -40
- package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
- package/src/subagent/consult-context.ts +7 -23
- package/src/tools/credentials/broker.ts +24 -79
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/credentials/tool-policy.ts +67 -0
- package/src/tools/executor.ts +53 -15
- package/src/tools/network/__tests__/web-search.test.ts +41 -1
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/network/web-search.ts +21 -3
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/schema-transforms.ts +40 -5
- package/src/tools/shared/input-repairs.ts +160 -0
- package/src/tools/skills/skill-tool-factory.ts +18 -4
- package/src/tools/subagent/spawn.ts +0 -1
- package/src/tools/tool-approval-handler.ts +16 -10
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/util/__tests__/cgroup-memory.test.ts +3 -0
- package/src/util/cgroup-memory.ts +3 -0
- package/src/util/container-cpu-sampler.ts +250 -0
- package/src/util/image-conversion.ts +178 -14
- package/src/util/platform.ts +93 -10
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the confirmation
|
|
3
|
-
* in-flight race
|
|
4
|
-
* create is still pending must not strand a pending tool_approval row
|
|
2
|
+
* Tests for the confirmation to guardian-request promotion, including the
|
|
3
|
+
* in-flight race (a confirmation resolved while the fire-and-forget gateway
|
|
4
|
+
* create is still pending must not strand a pending tool_approval row) and
|
|
5
|
+
* the deadline, which is derived from the approval-window resolver rather
|
|
6
|
+
* than restated as a constant.
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
9
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -52,6 +54,15 @@ mock.module("../runtime/confirmation-request-guardian-bridge.js", () => ({
|
|
|
52
54
|
},
|
|
53
55
|
}));
|
|
54
56
|
|
|
57
|
+
// Deliberately not 300_000, the resolver's default. A fixture on the default
|
|
58
|
+
// passes whether the deadline is read from the resolver or restated as a
|
|
59
|
+
// constant beside it; this value only matches if it is read.
|
|
60
|
+
const APPROVAL_WINDOW_MS = 900_000;
|
|
61
|
+
|
|
62
|
+
mock.module("../tools/tool-approval-handler.js", () => ({
|
|
63
|
+
resolveInlineGrantWaitMs: () => APPROVAL_WINDOW_MS,
|
|
64
|
+
}));
|
|
65
|
+
|
|
55
66
|
import { asConversation } from "../__tests__/helpers/mock-conversation.js";
|
|
56
67
|
import type { AssistantEvent } from "../api/index.js";
|
|
57
68
|
import type { TrustContext } from "../daemon/trust-context-types.js";
|
|
@@ -86,6 +97,20 @@ describe("createGuardianRequestForConfirmation", () => {
|
|
|
86
97
|
expect(bridgeCalls).toHaveLength(1);
|
|
87
98
|
});
|
|
88
99
|
|
|
100
|
+
test("takes the row deadline from the approval-window resolver", async () => {
|
|
101
|
+
const before = Date.now();
|
|
102
|
+
await createGuardianRequestForConfirmation(MSG, "conv-1");
|
|
103
|
+
const after = Date.now();
|
|
104
|
+
|
|
105
|
+
expect(createCalls).toHaveLength(1);
|
|
106
|
+
const expiresAt = createCalls[0].expiresAt as number;
|
|
107
|
+
|
|
108
|
+
// Bracketed against the clock either side of the call rather than compared
|
|
109
|
+
// to a single Date.now(), so the assertion cannot flake on a slow tick.
|
|
110
|
+
expect(expiresAt).toBeGreaterThanOrEqual(before + APPROVAL_WINDOW_MS);
|
|
111
|
+
expect(expiresAt).toBeLessThanOrEqual(after + APPROVAL_WINDOW_MS);
|
|
112
|
+
});
|
|
113
|
+
|
|
89
114
|
test("expires the row and skips the bridge when the confirmation resolved mid-create", async () => {
|
|
90
115
|
confirmationPending = false;
|
|
91
116
|
|
|
@@ -55,6 +55,7 @@ export async function createGuardianRequestForConfirmation(
|
|
|
55
55
|
{ summarizeToolInput },
|
|
56
56
|
{ DAEMON_INTERNAL_ASSISTANT_ID },
|
|
57
57
|
{ bridgeConfirmationRequestToGuardian },
|
|
58
|
+
{ resolveInlineGrantWaitMs },
|
|
58
59
|
] = await Promise.all([
|
|
59
60
|
import("../daemon/conversation-registry.js"),
|
|
60
61
|
import("../channels/gateway-guardian-requests.js"),
|
|
@@ -62,6 +63,7 @@ export async function createGuardianRequestForConfirmation(
|
|
|
62
63
|
import("../tools/tool-input-summary.js"),
|
|
63
64
|
import("../runtime/assistant-scope.js"),
|
|
64
65
|
import("../runtime/confirmation-request-guardian-bridge.js"),
|
|
66
|
+
import("../tools/tool-approval-handler.js"),
|
|
65
67
|
]);
|
|
66
68
|
|
|
67
69
|
const conversation = findConversation(conversationId);
|
|
@@ -100,7 +102,9 @@ export async function createGuardianRequestForConfirmation(
|
|
|
100
102
|
activityText: activityRaw ? redactSecrets(activityRaw) : undefined,
|
|
101
103
|
executionTarget: msg.executionTarget,
|
|
102
104
|
status: "pending",
|
|
103
|
-
|
|
105
|
+
// The row stays decidable exactly as long as the prompt stays parked:
|
|
106
|
+
// one budget, so raising `timeouts.permissionTimeoutSec` moves both.
|
|
107
|
+
expiresAt: Date.now() + resolveInlineGrantWaitMs(),
|
|
104
108
|
});
|
|
105
109
|
|
|
106
110
|
// The prompt is actionable before this fire-and-forget create lands, so
|
|
@@ -87,6 +87,7 @@ import {
|
|
|
87
87
|
type ConversationCreateType,
|
|
88
88
|
type ConversationOrigin,
|
|
89
89
|
isHiddenMessageMetadata,
|
|
90
|
+
isSystemCardMetadata,
|
|
90
91
|
PINNED_GROUP_ID,
|
|
91
92
|
UNGROUPED_GROUP_ID,
|
|
92
93
|
} from "./conversation-types.js";
|
|
@@ -259,6 +260,14 @@ const backgroundToolCompletionMetadataSchema = z.object({
|
|
|
259
260
|
|
|
260
261
|
export const messageMetadataSchema = z
|
|
261
262
|
.object({
|
|
263
|
+
/**
|
|
264
|
+
* Epoch ms the content actually happened, when that differs from when
|
|
265
|
+
* the row was written. Set wherever persistence lags the event: a queued
|
|
266
|
+
* turn draining, or channel history imported long after the fact.
|
|
267
|
+
* History serialization prefers it over `createdAt` for the display
|
|
268
|
+
* timestamp.
|
|
269
|
+
*/
|
|
270
|
+
sentAt: z.number().optional(),
|
|
262
271
|
userMessageChannel: channelIdSchema.optional(),
|
|
263
272
|
assistantMessageChannel: channelIdSchema.optional(),
|
|
264
273
|
userMessageInterface: interfaceIdSchema.optional(),
|
|
@@ -413,13 +422,15 @@ export {
|
|
|
413
422
|
} from "./conversation-types.js";
|
|
414
423
|
|
|
415
424
|
/**
|
|
416
|
-
* `messageKind`
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
* display turns.
|
|
425
|
+
* The system-card `messageKind` marker and its predicate live in the
|
|
426
|
+
* `conversation-types` leaf so a caller that only stamps or classifies the
|
|
427
|
+
* marker does not pull in this module's DB graph, and are re-exported here
|
|
428
|
+
* alongside the schema that carries them.
|
|
421
429
|
*/
|
|
422
|
-
export
|
|
430
|
+
export {
|
|
431
|
+
isSystemCardMetadata,
|
|
432
|
+
SYSTEM_CARD_MESSAGE_KIND,
|
|
433
|
+
} from "./conversation-types.js";
|
|
423
434
|
|
|
424
435
|
/**
|
|
425
436
|
* `messageKind` value marking the synthetic assistant row the agent loop
|
|
@@ -431,18 +442,6 @@ export const SYSTEM_CARD_MESSAGE_KIND = "system_card";
|
|
|
431
442
|
*/
|
|
432
443
|
export const PROVIDER_ERROR_MESSAGE_KIND = "provider_error";
|
|
433
444
|
|
|
434
|
-
/**
|
|
435
|
-
* Shared predicate for the system-card marker on assistant-message metadata
|
|
436
|
-
* (see the `messageKind` field on {@link messageMetadataSchema}). One
|
|
437
|
-
* definition so display merging, transcript rendering, and turn grouping
|
|
438
|
-
* cannot drift.
|
|
439
|
-
*/
|
|
440
|
-
export function isSystemCardMetadata(
|
|
441
|
-
metadata: Record<string, unknown> | null | undefined,
|
|
442
|
-
): boolean {
|
|
443
|
-
return metadata?.messageKind === SYSTEM_CARD_MESSAGE_KIND;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
445
|
/**
|
|
447
446
|
* Shared predicate for the provider-error marker on assistant-message
|
|
448
447
|
* metadata (see the `messageKind` field on {@link messageMetadataSchema}).
|
|
@@ -2467,7 +2466,7 @@ export function getMessages(conversationId: string): MessageRow[] {
|
|
|
2467
2466
|
* offsets until the target is reached (capped at a reasonable maximum to bound
|
|
2468
2467
|
* scan cost).
|
|
2469
2468
|
*/
|
|
2470
|
-
export function
|
|
2469
|
+
export function selectProviderMetaCandidateMetadata(
|
|
2471
2470
|
conversationId: string,
|
|
2472
2471
|
limit: number,
|
|
2473
2472
|
offset = 0,
|
|
@@ -2482,10 +2481,14 @@ export function selectSlackMetaCandidateMetadata(
|
|
|
2482
2481
|
lineageFilter(conversationId),
|
|
2483
2482
|
opts?.includeFlatLegacy
|
|
2484
2483
|
? or(
|
|
2484
|
+
like(messages.metadata, '%"providerMeta"%'),
|
|
2485
2485
|
like(messages.metadata, '%"slackMeta"%'),
|
|
2486
2486
|
like(messages.metadata, '%"source":"slack"%'),
|
|
2487
2487
|
)
|
|
2488
|
-
:
|
|
2488
|
+
: or(
|
|
2489
|
+
like(messages.metadata, '%"providerMeta"%'),
|
|
2490
|
+
like(messages.metadata, '%"slackMeta"%'),
|
|
2491
|
+
),
|
|
2489
2492
|
),
|
|
2490
2493
|
)
|
|
2491
2494
|
.orderBy(asc(messages.createdAt))
|
|
@@ -199,7 +199,8 @@ function surfacedVisibilitySql(alias = "conversations"): string {
|
|
|
199
199
|
* explicitly surfaced. {@link standardListingVisibilitySql} already admits
|
|
200
200
|
* such rows through its surfaced and custom-group arms, so a caller that
|
|
201
201
|
* must distinguish "visible in the listing" from "user-facing foreground"
|
|
202
|
-
* (unread counting
|
|
202
|
+
* (unread counting, the section index, a `foregroundOnly` list read) applies
|
|
203
|
+
* this on top.
|
|
203
204
|
*
|
|
204
205
|
* The SQL twin of `isBackgroundConversation` in the web client's
|
|
205
206
|
* `utils/conversation-predicates.ts`.
|
|
@@ -282,6 +283,16 @@ export interface ConversationListFilter {
|
|
|
282
283
|
* the rows its attention surfaces need instead of scanning for them.
|
|
283
284
|
*/
|
|
284
285
|
needsAttention?: true;
|
|
286
|
+
/**
|
|
287
|
+
* Restrict to user-facing foreground conversations: drop the automated
|
|
288
|
+
* background/scheduled rows the standard listing admits through its
|
|
289
|
+
* custom-group arm ({@link notBackgroundVisibilitySql}, the same predicate
|
|
290
|
+
* the unread count and the section index apply on top of visibility).
|
|
291
|
+
* Only `true` narrows; omit to keep every visible row. A client that
|
|
292
|
+
* needs the newest conversation a user can open can ask for it in one
|
|
293
|
+
* row instead of paging past runs it would skip.
|
|
294
|
+
*/
|
|
295
|
+
foregroundOnly?: true;
|
|
285
296
|
}
|
|
286
297
|
|
|
287
298
|
export interface ConversationListQuery extends ConversationListFilter {
|
|
@@ -364,6 +375,7 @@ function conversationListWhere(filter: ConversationListFilter) {
|
|
|
364
375
|
originChannel,
|
|
365
376
|
groupId,
|
|
366
377
|
needsAttention,
|
|
378
|
+
foregroundOnly,
|
|
367
379
|
} = filter;
|
|
368
380
|
return and(
|
|
369
381
|
conversationTypeClause(conversationType),
|
|
@@ -371,6 +383,7 @@ function conversationListWhere(filter: ConversationListFilter) {
|
|
|
371
383
|
originChannel ? originChannelClause(originChannel) : undefined,
|
|
372
384
|
groupId ? groupIdClause(groupId) : undefined,
|
|
373
385
|
...(needsAttention ? unseenAttentionStateConditions() : []),
|
|
386
|
+
foregroundOnly ? sql.raw(notBackgroundVisibilitySql()) : undefined,
|
|
374
387
|
);
|
|
375
388
|
}
|
|
376
389
|
|
|
@@ -135,6 +135,28 @@ export function isBackgroundEventMetadata(
|
|
|
135
135
|
return typeof metadata?.backgroundEventSource === "string";
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* `messageKind` value marking a daemon-authored system card: a notice that
|
|
140
|
+
* bypasses the agent loop (the /compact, /clean, and summarize-up-to result
|
|
141
|
+
* cards, and plugin notices about what a turn did to the user's input). Cards
|
|
142
|
+
* render as standalone system notices, never as the assistant persona
|
|
143
|
+
* speaking, and never merge into adjacent assistant display turns.
|
|
144
|
+
*
|
|
145
|
+
* Lives in this leaf so a caller that only stamps or classifies the marker
|
|
146
|
+
* does not pull in `conversation-crud`'s DB graph.
|
|
147
|
+
*/
|
|
148
|
+
export const SYSTEM_CARD_MESSAGE_KIND = "system_card";
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Shared predicate for the system-card marker on assistant-message metadata,
|
|
152
|
+
* so display merging, transcript rendering, and turn grouping cannot drift.
|
|
153
|
+
*/
|
|
154
|
+
export function isSystemCardMetadata(
|
|
155
|
+
metadata: Record<string, unknown> | null | undefined,
|
|
156
|
+
): boolean {
|
|
157
|
+
return metadata?.messageKind === SYSTEM_CARD_MESSAGE_KIND;
|
|
158
|
+
}
|
|
159
|
+
|
|
138
160
|
/**
|
|
139
161
|
* True when a role-`"user"` row is internal scaffolding rather than a person's
|
|
140
162
|
* prompt: a daemon-injected run lifecycle notification (subagent
|
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
* finding messages by source identifiers, and managing raw payload storage.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { and, desc, eq, isNotNull, ne, or, sql } from "drizzle-orm";
|
|
8
|
+
import { and, desc, eq, isNotNull, like, ne, or, sql } from "drizzle-orm";
|
|
9
9
|
import { v4 as uuid } from "uuid";
|
|
10
10
|
|
|
11
11
|
import type { ChannelId } from "../channels/types.js";
|
|
12
|
-
import {
|
|
12
|
+
import { readProviderMetadata } from "../messaging/read-provider-metadata.js";
|
|
13
13
|
import type { SlackInboundMessageMetadata } from "../runtime/http-types.js";
|
|
14
14
|
import { parseJsonSafe } from "../util/json.js";
|
|
15
15
|
import { isPlainObject } from "../util/object.js";
|
|
16
|
-
import {
|
|
16
|
+
import { selectProviderMetaCandidateMetadata } from "./conversation-crud.js";
|
|
17
17
|
import {
|
|
18
18
|
getConversationByKey,
|
|
19
19
|
getOrCreateConversation,
|
|
@@ -21,7 +21,12 @@ import {
|
|
|
21
21
|
} from "./conversation-key-store.js";
|
|
22
22
|
import type { NonScheduledConversationType } from "./conversation-types.js";
|
|
23
23
|
import { getDb } from "./db-connection.js";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
channelInboundEvents,
|
|
26
|
+
conversationKeys,
|
|
27
|
+
conversations,
|
|
28
|
+
messages,
|
|
29
|
+
} from "./schema.js";
|
|
25
30
|
|
|
26
31
|
export interface InboundResult {
|
|
27
32
|
accepted: boolean;
|
|
@@ -33,11 +38,26 @@ export interface InboundResult {
|
|
|
33
38
|
export interface RecordInboundOptions {
|
|
34
39
|
sourceMessageId?: string;
|
|
35
40
|
sourceThreadId?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Record the event against this conversation instead of resolving one from
|
|
43
|
+
* the address. For events that belong to a message rather than to a chat: a
|
|
44
|
+
* reaction lives in the conversation of the message it was attached to, and
|
|
45
|
+
* resolving from its own address would mint a second one.
|
|
46
|
+
*/
|
|
47
|
+
conversationId?: string;
|
|
36
48
|
}
|
|
37
49
|
|
|
38
50
|
const SLACK_LEGACY_THREAD_EVIDENCE_BATCH_SIZE = 50;
|
|
39
51
|
const SLACK_LEGACY_THREAD_EVIDENCE_MAX_SCAN = 500;
|
|
40
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Rows examined when locating a Slack message the assistant itself posted.
|
|
55
|
+
* Bounded on purpose: the scan runs on the inbound path while the gateway
|
|
56
|
+
* waits for its ack, and reactions land on recent messages, so a cap costs
|
|
57
|
+
* almost no recall and keeps the cost flat as the database grows.
|
|
58
|
+
*/
|
|
59
|
+
const SLACK_OUTBOUND_TS_MAX_SCAN = 400;
|
|
60
|
+
|
|
41
61
|
/**
|
|
42
62
|
* Channels where an inbound thread id scopes the conversation: a Slack thread
|
|
43
63
|
* or a Telegram private-chat topic each maps to its own conversation. A
|
|
@@ -99,7 +119,7 @@ function legacySlackConversationHasThreadEvidence(
|
|
|
99
119
|
SLACK_LEGACY_THREAD_EVIDENCE_BATCH_SIZE,
|
|
100
120
|
remaining,
|
|
101
121
|
);
|
|
102
|
-
const metadataRows =
|
|
122
|
+
const metadataRows = selectProviderMetaCandidateMetadata(
|
|
103
123
|
conversationId,
|
|
104
124
|
batchLimit,
|
|
105
125
|
offset,
|
|
@@ -110,12 +130,10 @@ function legacySlackConversationHasThreadEvidence(
|
|
|
110
130
|
return false;
|
|
111
131
|
}
|
|
112
132
|
for (const metadata of metadataRows) {
|
|
113
|
-
const
|
|
114
|
-
allowFlatLegacy: true,
|
|
115
|
-
});
|
|
133
|
+
const meta = readProviderMetadata(metadata, { allowFlatLegacy: true });
|
|
116
134
|
if (
|
|
117
|
-
|
|
118
|
-
|
|
135
|
+
meta?.conversationExternalId === externalChatId &&
|
|
136
|
+
meta.threadId === sourceThreadId
|
|
119
137
|
) {
|
|
120
138
|
return true;
|
|
121
139
|
}
|
|
@@ -248,6 +266,93 @@ export function findInboundConversationId(
|
|
|
248
266
|
* the conversation and tracks the reply. Both must key on the same three
|
|
249
267
|
* fields for either to mean anything.
|
|
250
268
|
*/
|
|
269
|
+
/**
|
|
270
|
+
* The inbound event already recorded for this address, if any. Read-only twin
|
|
271
|
+
* of the dedup check inside {@link recordInbound}, for callers that must know
|
|
272
|
+
* an event is a redelivery before doing work that recording would otherwise
|
|
273
|
+
* gate (routing a guardian decision, for one).
|
|
274
|
+
*/
|
|
275
|
+
export function findInboundEvent(
|
|
276
|
+
sourceChannel: string,
|
|
277
|
+
externalChatId: string,
|
|
278
|
+
externalMessageId: string,
|
|
279
|
+
): { eventId: string; conversationId: string } | null {
|
|
280
|
+
const row = getDb()
|
|
281
|
+
.select({
|
|
282
|
+
id: channelInboundEvents.id,
|
|
283
|
+
conversationId: channelInboundEvents.conversationId,
|
|
284
|
+
})
|
|
285
|
+
.from(channelInboundEvents)
|
|
286
|
+
.where(
|
|
287
|
+
and(
|
|
288
|
+
eq(channelInboundEvents.sourceChannel, sourceChannel),
|
|
289
|
+
eq(channelInboundEvents.externalChatId, externalChatId),
|
|
290
|
+
eq(channelInboundEvents.externalMessageId, externalMessageId),
|
|
291
|
+
),
|
|
292
|
+
)
|
|
293
|
+
.get();
|
|
294
|
+
return row ? { eventId: row.id, conversationId: row.conversationId } : null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* The conversation holding the message with this provider id, found by reading
|
|
299
|
+
* the metadata the assistant's own posts carry.
|
|
300
|
+
*
|
|
301
|
+
* Reads through `readProviderMetadata`, so it matches any channel that
|
|
302
|
+
* describes its rows in the neutral shape as well as Slack's own envelope.
|
|
303
|
+
*
|
|
304
|
+
* `findMessageBySourceId` covers every message that arrived as an inbound
|
|
305
|
+
* event. It cannot see what the assistant posted, because an outbound reply
|
|
306
|
+
* opens no inbound event, so a reaction on the assistant's own message needs
|
|
307
|
+
* this. The search is confined to conversations already bound to the same
|
|
308
|
+
* Slack channel and to the most recent {@link SLACK_OUTBOUND_TS_MAX_SCAN}
|
|
309
|
+
* rows among them; beyond that it gives up and the caller drops the
|
|
310
|
+
* annotation, which is the same outcome as never finding it at all.
|
|
311
|
+
*/
|
|
312
|
+
export function findSlackConversationByMessageTs(
|
|
313
|
+
externalChatId: string,
|
|
314
|
+
channelTs: string,
|
|
315
|
+
): string | null {
|
|
316
|
+
const db = getDb();
|
|
317
|
+
const keyPrefix = `${CONVERSATION_KEY_SCOPE}:slack:${externalChatId}`;
|
|
318
|
+
const rows = db
|
|
319
|
+
.select({
|
|
320
|
+
conversationId: messages.conversationId,
|
|
321
|
+
metadata: messages.metadata,
|
|
322
|
+
})
|
|
323
|
+
.from(messages)
|
|
324
|
+
.innerJoin(
|
|
325
|
+
conversationKeys,
|
|
326
|
+
eq(conversationKeys.conversationId, messages.conversationId),
|
|
327
|
+
)
|
|
328
|
+
.where(
|
|
329
|
+
and(
|
|
330
|
+
or(
|
|
331
|
+
eq(conversationKeys.conversationKey, keyPrefix),
|
|
332
|
+
like(conversationKeys.conversationKey, `${keyPrefix}:thread:%`),
|
|
333
|
+
),
|
|
334
|
+
or(
|
|
335
|
+
like(messages.metadata, '%"providerMeta"%'),
|
|
336
|
+
like(messages.metadata, '%"slackMeta"%'),
|
|
337
|
+
),
|
|
338
|
+
),
|
|
339
|
+
)
|
|
340
|
+
.orderBy(desc(messages.createdAt))
|
|
341
|
+
.limit(SLACK_OUTBOUND_TS_MAX_SCAN)
|
|
342
|
+
.all();
|
|
343
|
+
|
|
344
|
+
for (const row of rows) {
|
|
345
|
+
const meta = readProviderMetadata(row.metadata, { allowFlatLegacy: true });
|
|
346
|
+
if (
|
|
347
|
+
meta?.conversationExternalId === externalChatId &&
|
|
348
|
+
meta.messageId === channelTs
|
|
349
|
+
) {
|
|
350
|
+
return row.conversationId;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
|
|
251
356
|
export function recordInbound(
|
|
252
357
|
sourceChannel: string,
|
|
253
358
|
externalChatId: string,
|
|
@@ -280,11 +385,13 @@ export function recordInbound(
|
|
|
280
385
|
};
|
|
281
386
|
}
|
|
282
387
|
|
|
283
|
-
const mapping =
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
388
|
+
const mapping = options?.conversationId
|
|
389
|
+
? { conversationId: options.conversationId }
|
|
390
|
+
: resolveInboundConversation(
|
|
391
|
+
sourceChannel,
|
|
392
|
+
externalChatId,
|
|
393
|
+
options?.sourceThreadId,
|
|
394
|
+
);
|
|
288
395
|
const now = Date.now();
|
|
289
396
|
const eventId = uuid();
|
|
290
397
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guards that importing `@vellumai/plugin-api` does not force a DB-graph
|
|
3
|
+
* module's named exports to resolve.
|
|
4
|
+
*
|
|
5
|
+
* Plugin suites routinely partial-mock modules in the persistence/embeddings
|
|
6
|
+
* graph (a handful of functions, not the whole surface). `mock.module` replaces
|
|
7
|
+
* the module for every importer, so a static edge from plugin-api into that
|
|
8
|
+
* graph makes some unrelated host module import a name the partial mock does not
|
|
9
|
+
* define, and the entire suite fails to instantiate with
|
|
10
|
+
* `SyntaxError: Export named '...' not found in module`. Facades over DB-graph
|
|
11
|
+
* writers therefore load them through a dynamic `import()` inside the wrapper.
|
|
12
|
+
*
|
|
13
|
+
* The probe runs in a subprocess: `mock.module` is process-wide and permanent,
|
|
14
|
+
* so mocking the embedding backend in this process would leak into every other
|
|
15
|
+
* suite sharing the test process.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { spawnSync } from "node:child_process";
|
|
19
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
23
|
+
|
|
24
|
+
const PLUGIN_API_DIR = join(import.meta.dir, "..");
|
|
25
|
+
|
|
26
|
+
let probeDir: string | null = null;
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
if (probeDir !== null) {
|
|
30
|
+
rmSync(probeDir, { recursive: true, force: true });
|
|
31
|
+
probeDir = null;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("plugin-api import graph", () => {
|
|
36
|
+
test("instantiates against a partial-mocked DB-graph module", () => {
|
|
37
|
+
// GIVEN a probe suite that partial-mocks a DB-graph module the way plugin
|
|
38
|
+
// suites do (one function, not the module's full export surface) and then
|
|
39
|
+
// imports the public plugin API surface.
|
|
40
|
+
probeDir = mkdtempSync(join(tmpdir(), "plugin-api-import-graph-"));
|
|
41
|
+
const probe = join(probeDir, "probe.test.ts");
|
|
42
|
+
writeFileSync(
|
|
43
|
+
probe,
|
|
44
|
+
`import { expect, mock, test } from "bun:test";\n` +
|
|
45
|
+
`mock.module(${JSON.stringify(join(PLUGIN_API_DIR, "..", "persistence", "embeddings", "embedding-backend.ts"))}, () => ({\n` +
|
|
46
|
+
` embedWithBackend: async () => [],\n` +
|
|
47
|
+
`}));\n` +
|
|
48
|
+
`test("plugin-api instantiates", async () => {\n` +
|
|
49
|
+
` const api = await import(${JSON.stringify(join(PLUGIN_API_DIR, "index.ts"))});\n` +
|
|
50
|
+
` expect(typeof api.persistSystemCard).toEqual("function");\n` +
|
|
51
|
+
` expect(typeof api.addMessage).toEqual("function");\n` +
|
|
52
|
+
`});\n`,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// WHEN the probe suite runs in its own process.
|
|
56
|
+
const result = spawnSync(process.execPath, ["test", probe], {
|
|
57
|
+
cwd: join(PLUGIN_API_DIR, "..", ".."),
|
|
58
|
+
encoding: "utf8",
|
|
59
|
+
timeout: 60_000,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// THEN plugin-api instantiated, so nothing in its static import graph
|
|
63
|
+
// reached a name the partial mock left undefined.
|
|
64
|
+
expect(`${result.stdout}${result.stderr}`).not.toContain("SyntaxError");
|
|
65
|
+
expect(result.status).toBe(0);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -280,6 +280,9 @@ export async function runConversationTurn(
|
|
|
280
280
|
await import("../persistence/conversation-crud.js");
|
|
281
281
|
const { publishConversationListAndMetadataChanged } =
|
|
282
282
|
await import("../runtime/sync/resource-sync-events.js");
|
|
283
|
+
const { parseInterfaceId } = await import("../channels/types.js");
|
|
284
|
+
const { resolveChannelCapabilities } =
|
|
285
|
+
await import("../daemon/conversation-runtime-assembly.js");
|
|
283
286
|
|
|
284
287
|
// Plugin-driven turns run as the guardian: plugins are installed by the
|
|
285
288
|
// guardian, so their conversations inherit guardian trust. This lets the
|
|
@@ -351,6 +354,28 @@ export async function runConversationTurn(
|
|
|
351
354
|
assistantMessageChannel: options.channel.sourceChannel,
|
|
352
355
|
}
|
|
353
356
|
: null;
|
|
357
|
+
// The interface the message arrived on. `plugin` is in both the channel
|
|
358
|
+
// and interface unions, matching gateway inbound. Tool gating reads this
|
|
359
|
+
// as `transportInterface`; unset, the loop falls back to `web`.
|
|
360
|
+
const turnInterfaceContext = options.channel
|
|
361
|
+
? (() => {
|
|
362
|
+
const iface = parseInterfaceId(options.channel.sourceChannel);
|
|
363
|
+
return iface
|
|
364
|
+
? {
|
|
365
|
+
userMessageInterface: iface,
|
|
366
|
+
assistantMessageInterface: iface,
|
|
367
|
+
}
|
|
368
|
+
: null;
|
|
369
|
+
})()
|
|
370
|
+
: null;
|
|
371
|
+
if (options.channel) {
|
|
372
|
+
conversation.setChannelCapabilities(
|
|
373
|
+
resolveChannelCapabilities(
|
|
374
|
+
options.channel.sourceChannel,
|
|
375
|
+
turnInterfaceContext?.userMessageInterface,
|
|
376
|
+
),
|
|
377
|
+
);
|
|
378
|
+
}
|
|
354
379
|
// Carried on the metadata as well, because a queued turn is drained after
|
|
355
380
|
// this call returns and reads its channel from there. Without it the drain
|
|
356
381
|
// inherits whichever turn was in flight, which on a shared conversation is
|
|
@@ -358,6 +383,7 @@ export async function runConversationTurn(
|
|
|
358
383
|
const metadata = {
|
|
359
384
|
...PLUGIN_TURN_MESSAGE_METADATA,
|
|
360
385
|
...(turnChannelContext ?? {}),
|
|
386
|
+
...(turnInterfaceContext ?? {}),
|
|
361
387
|
};
|
|
362
388
|
|
|
363
389
|
// Build the event emitter: fan out to SSE/event-hub subscribers via
|
|
@@ -398,6 +424,7 @@ export async function runConversationTurn(
|
|
|
398
424
|
}
|
|
399
425
|
|
|
400
426
|
conversation.setTurnChannelContext(turnChannelContext);
|
|
427
|
+
conversation.setTurnInterfaceContext(turnInterfaceContext);
|
|
401
428
|
|
|
402
429
|
const userMessageId = await conversation.processMessage({
|
|
403
430
|
content: text,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { credentialInPluginScope } from "./credential-scope.js";
|
|
4
|
+
|
|
5
|
+
describe("credentialInPluginScope", () => {
|
|
6
|
+
test("allows every field under the plugin's own service", () => {
|
|
7
|
+
expect(credentialInPluginScope("imessage", "imessage")).toBe(true);
|
|
8
|
+
expect(credentialInPluginScope("acme", "acme")).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("blocks another service, even when the field matches the plugin name", () => {
|
|
12
|
+
expect(credentialInPluginScope("acme", "openai")).toBe(false);
|
|
13
|
+
expect(credentialInPluginScope("imessage", "openai")).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a plugin may read or write a credential named `service/field`.
|
|
3
|
+
*
|
|
4
|
+
* The plugin's manifest name is the service it owns. Plugin `imessage` may
|
|
5
|
+
* resolve `imessage/api_key` and `imessage/photon_project_id`, and nothing
|
|
6
|
+
* under another service (`openai/api_key`, `openai/imessage`). The check is
|
|
7
|
+
* name equality only; it does not look the credential up.
|
|
8
|
+
*/
|
|
9
|
+
export function credentialInPluginScope(
|
|
10
|
+
pluginName: string,
|
|
11
|
+
service: string,
|
|
12
|
+
): boolean {
|
|
13
|
+
return service === pluginName;
|
|
14
|
+
}
|
package/src/plugin-api/index.ts
CHANGED
|
@@ -170,13 +170,25 @@ export { doesSupportVision } from "./vision-support.js";
|
|
|
170
170
|
// Resolve a stored credential to its plaintext value — the same value
|
|
171
171
|
// `assistant credentials reveal` prints — from a UUID or a "service/field"
|
|
172
172
|
// reference. When a plugin is in context, resolution is scoped to credentials
|
|
173
|
-
// whose
|
|
173
|
+
// whose service matches the plugin's manifest name; outside any plugin it is
|
|
174
174
|
// unscoped. Throws CredentialResolutionError when the ref does not resolve, the
|
|
175
175
|
// store is unreachable, or the credential is out of the plugin's scope.
|
|
176
176
|
export {
|
|
177
177
|
CredentialResolutionError,
|
|
178
178
|
resolveCredential,
|
|
179
179
|
} from "./resolve-credential.js";
|
|
180
|
+
// Store a credential's plaintext value (the same write `assistant credentials
|
|
181
|
+
// set` performs), creating it or replacing an existing one, named by UUID or a
|
|
182
|
+
// "service/field" reference. A plugin may only write credentials whose service
|
|
183
|
+
// matches its manifest name, and the write fails closed with no plugin in
|
|
184
|
+
// context. Throws CredentialStoreError when there is no calling plugin, the ref
|
|
185
|
+
// is malformed, the value is invalid, the store rejects the write, or the
|
|
186
|
+
// credential is out of the plugin's scope.
|
|
187
|
+
export type {
|
|
188
|
+
StoreCredentialOptions,
|
|
189
|
+
StoredCredentialRef,
|
|
190
|
+
} from "./store-credential.js";
|
|
191
|
+
export { CredentialStoreError, storeCredential } from "./store-credential.js";
|
|
180
192
|
// Resolve the public URL a third party should deliver to for one of the
|
|
181
193
|
// plugin's own ingress routes. Which URL is correct depends on how the
|
|
182
194
|
// assistant is reachable (a managed platform callback route, or a configured
|
|
@@ -348,6 +360,12 @@ export {
|
|
|
348
360
|
syncMessageToDisk,
|
|
349
361
|
updateMessageMetadata,
|
|
350
362
|
} from "../persistence/conversation-plugin-facade.js";
|
|
363
|
+
// System cards: a transcript notice authored by the daemon rather than the
|
|
364
|
+
// assistant persona, for telling the user something a turn did to their input
|
|
365
|
+
// that the model's reply cannot explain (e.g. an attachment that could not be
|
|
366
|
+
// sent). Persisted and pushed to clients; not seated in the turn's working
|
|
367
|
+
// history.
|
|
368
|
+
export { persistSystemCard } from "./system-card.js";
|
|
351
369
|
// Synthesize text to speech through the assistant's globally configured TTS
|
|
352
370
|
// provider (ElevenLabs, Fish Audio, etc.). Plugins that need voice output —
|
|
353
371
|
// e.g. a meeting bot speaking into a live call — use this instead of managing
|