@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
|
@@ -1496,6 +1496,17 @@ describe("stripInjectionsForCompaction memory/info wrapper matching", () => {
|
|
|
1496
1496
|
// applyRuntimeInjections with nowScratchpad
|
|
1497
1497
|
// ---------------------------------------------------------------------------
|
|
1498
1498
|
|
|
1499
|
+
/**
|
|
1500
|
+
* Personal-memory content (NOW.md, PKB, v3 cards) is gated on the actor's
|
|
1501
|
+
* trust class, so suites asserting that content have to name the actor they
|
|
1502
|
+
* are about. Left unset, the injector substitutes the low-trust fallback and
|
|
1503
|
+
* the assertion silently becomes a test of the gate instead of the content.
|
|
1504
|
+
*/
|
|
1505
|
+
const GUARDIAN_TRUST_FIXTURE = {
|
|
1506
|
+
trustClass: "guardian",
|
|
1507
|
+
sourceChannel: "vellum",
|
|
1508
|
+
} as const;
|
|
1509
|
+
|
|
1499
1510
|
describe("applyRuntimeInjections with nowScratchpad", () => {
|
|
1500
1511
|
const baseMessages: Message[] = [
|
|
1501
1512
|
{
|
|
@@ -1513,6 +1524,7 @@ describe("applyRuntimeInjections with nowScratchpad", () => {
|
|
|
1513
1524
|
seedNowScratchpad("Current focus: fix the bug");
|
|
1514
1525
|
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1515
1526
|
conversationId: FALLBACK_CONVERSATION_ID,
|
|
1527
|
+
trust: GUARDIAN_TRUST_FIXTURE,
|
|
1516
1528
|
});
|
|
1517
1529
|
|
|
1518
1530
|
expect(result.length).toBe(1);
|
|
@@ -1527,6 +1539,7 @@ describe("applyRuntimeInjections with nowScratchpad", () => {
|
|
|
1527
1539
|
seedNowScratchpad("scratchpad notes");
|
|
1528
1540
|
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1529
1541
|
conversationId: FALLBACK_CONVERSATION_ID,
|
|
1542
|
+
trust: GUARDIAN_TRUST_FIXTURE,
|
|
1530
1543
|
});
|
|
1531
1544
|
|
|
1532
1545
|
// Scratchpad comes first (before user content)
|
|
@@ -1542,6 +1555,7 @@ describe("applyRuntimeInjections with nowScratchpad", () => {
|
|
|
1542
1555
|
test("does not inject when the NOW.md file is absent", async () => {
|
|
1543
1556
|
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1544
1557
|
conversationId: FALLBACK_CONVERSATION_ID,
|
|
1558
|
+
trust: GUARDIAN_TRUST_FIXTURE,
|
|
1545
1559
|
});
|
|
1546
1560
|
|
|
1547
1561
|
expect(result.length).toBe(1);
|
|
@@ -1552,6 +1566,7 @@ describe("applyRuntimeInjections with nowScratchpad", () => {
|
|
|
1552
1566
|
seedNowScratchpad("Current focus: fix the bug");
|
|
1553
1567
|
const { messages: result } = await applyRuntimeInjections(baseMessages, {
|
|
1554
1568
|
conversationId: FALLBACK_CONVERSATION_ID,
|
|
1569
|
+
trust: GUARDIAN_TRUST_FIXTURE,
|
|
1555
1570
|
mode: "minimal",
|
|
1556
1571
|
});
|
|
1557
1572
|
|
|
@@ -2873,6 +2888,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
|
|
|
2873
2888
|
function makePkbOptions(overrides: Record<string, unknown> = {}) {
|
|
2874
2889
|
return {
|
|
2875
2890
|
conversationId: FALLBACK_CONVERSATION_ID,
|
|
2891
|
+
trust: GUARDIAN_TRUST_FIXTURE,
|
|
2876
2892
|
...overrides,
|
|
2877
2893
|
};
|
|
2878
2894
|
}
|
|
@@ -3159,7 +3175,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
|
|
|
3159
3175
|
// filtered out.
|
|
3160
3176
|
const { messages: initialResult } = await applyRuntimeInjections(
|
|
3161
3177
|
preCompactionMessages,
|
|
3162
|
-
|
|
3178
|
+
makePkbOptions(),
|
|
3163
3179
|
);
|
|
3164
3180
|
// Unwrap the injected reminder from the last user message.
|
|
3165
3181
|
const initialTexts = extractTexts(initialResult);
|
|
@@ -3193,7 +3209,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
|
|
|
3193
3209
|
// should be filtered, and beta (no longer "in context") should appear.
|
|
3194
3210
|
const { messages: rebuiltResult } = await applyRuntimeInjections(
|
|
3195
3211
|
postCompactionMessages,
|
|
3196
|
-
|
|
3212
|
+
makePkbOptions(),
|
|
3197
3213
|
);
|
|
3198
3214
|
const rebuiltTexts = extractTexts(rebuiltResult);
|
|
3199
3215
|
const rebuiltReminder = rebuiltTexts.find(
|
|
@@ -6652,6 +6668,7 @@ describe("applyRuntimeInjections blocks.pkbSystemReminder", () => {
|
|
|
6652
6668
|
pkbSearchThrows = null;
|
|
6653
6669
|
const { blocks } = await applyRuntimeInjections(baseMessages, {
|
|
6654
6670
|
conversationId: FALLBACK_CONVERSATION_ID,
|
|
6671
|
+
trust: GUARDIAN_TRUST_FIXTURE,
|
|
6655
6672
|
mode: "full",
|
|
6656
6673
|
});
|
|
6657
6674
|
|
|
@@ -6663,6 +6680,7 @@ describe("applyRuntimeInjections blocks.pkbSystemReminder", () => {
|
|
|
6663
6680
|
seedPkbContent();
|
|
6664
6681
|
const { blocks } = await applyRuntimeInjections(baseMessages, {
|
|
6665
6682
|
conversationId: FALLBACK_CONVERSATION_ID,
|
|
6683
|
+
trust: GUARDIAN_TRUST_FIXTURE,
|
|
6666
6684
|
mode: "minimal",
|
|
6667
6685
|
});
|
|
6668
6686
|
|
|
@@ -6672,6 +6690,7 @@ describe("applyRuntimeInjections blocks.pkbSystemReminder", () => {
|
|
|
6672
6690
|
test("not captured when PKB inactive", async () => {
|
|
6673
6691
|
const { blocks } = await applyRuntimeInjections(baseMessages, {
|
|
6674
6692
|
conversationId: FALLBACK_CONVERSATION_ID,
|
|
6693
|
+
trust: GUARDIAN_TRUST_FIXTURE,
|
|
6675
6694
|
mode: "full",
|
|
6676
6695
|
});
|
|
6677
6696
|
|
|
@@ -44,8 +44,24 @@ import { setSecureKeyAsync } from "../security/secure-keys.js";
|
|
|
44
44
|
import { CredentialBroker } from "../tools/credentials/broker.js";
|
|
45
45
|
import {
|
|
46
46
|
_setMetadataPath,
|
|
47
|
+
type CredentialMetadata,
|
|
47
48
|
upsertCredentialMetadata,
|
|
48
49
|
} from "../tools/credentials/metadata-store.js";
|
|
50
|
+
import { serverUseDenialReason } from "../tools/credentials/tool-policy.js";
|
|
51
|
+
|
|
52
|
+
/** The shared policy's verdict for a state the broker must also deny. */
|
|
53
|
+
function sharedDenialReason(
|
|
54
|
+
metadata: CredentialMetadata,
|
|
55
|
+
toolName: string,
|
|
56
|
+
service: string,
|
|
57
|
+
field: string,
|
|
58
|
+
): string {
|
|
59
|
+
const reason = serverUseDenialReason(metadata, toolName, service, field);
|
|
60
|
+
if (!reason) {
|
|
61
|
+
throw new Error("expected a denial reason from the shared policy");
|
|
62
|
+
}
|
|
63
|
+
return reason;
|
|
64
|
+
}
|
|
49
65
|
|
|
50
66
|
// ---------------------------------------------------------------------------
|
|
51
67
|
// Tests — serverUse (publish_page / unpublish_page regression)
|
|
@@ -518,9 +534,10 @@ describe("CredentialBroker.serverUseById", () => {
|
|
|
518
534
|
if (result.success) {
|
|
519
535
|
throw new Error("expected denial");
|
|
520
536
|
}
|
|
521
|
-
|
|
522
|
-
expect(result.reason).
|
|
523
|
-
|
|
537
|
+
// The broker passes the shared server-use policy's verdict through verbatim.
|
|
538
|
+
expect(result.reason).toBe(
|
|
539
|
+
sharedDenialReason(meta, "unauthorized_tool", "fal", "api_key"),
|
|
540
|
+
);
|
|
524
541
|
});
|
|
525
542
|
|
|
526
543
|
test("returns not found for unknown credential ID", async () => {
|
|
@@ -553,8 +570,10 @@ describe("CredentialBroker.serverUseById", () => {
|
|
|
553
570
|
if (result.success) {
|
|
554
571
|
throw new Error("expected denial");
|
|
555
572
|
}
|
|
556
|
-
|
|
557
|
-
expect(result.reason).
|
|
573
|
+
// The broker passes the shared server-use policy's verdict through verbatim.
|
|
574
|
+
expect(result.reason).toBe(
|
|
575
|
+
sharedDenialReason(meta, "media_proxy", "github", "oauth_token"),
|
|
576
|
+
);
|
|
558
577
|
});
|
|
559
578
|
|
|
560
579
|
test("returns empty injection templates when credential has none", async () => {
|
|
@@ -146,6 +146,7 @@ mock.module("../tools/credentials/broker.js", () => ({
|
|
|
146
146
|
}));
|
|
147
147
|
|
|
148
148
|
import { persistPromptedCredential } from "../credential-execution/prompted-credential.js";
|
|
149
|
+
import { initializeDb } from "../persistence/db-init.js";
|
|
149
150
|
import {
|
|
150
151
|
forChatMintsSince,
|
|
151
152
|
resetForChatMintRegistryForTest,
|
|
@@ -158,6 +159,12 @@ import {
|
|
|
158
159
|
} from "../runtime/reveal-success-registry.js";
|
|
159
160
|
import { ROUTES } from "../runtime/routes/credential-routes.js";
|
|
160
161
|
|
|
162
|
+
// A delete looks up the provider connections that resolve their auth through
|
|
163
|
+
// the credential, so the inference schema has to exist. No connection is
|
|
164
|
+
// created here: the refusal is covered by
|
|
165
|
+
// runtime/routes/__tests__/credential-delete-in-use.test.ts.
|
|
166
|
+
await initializeDb();
|
|
167
|
+
|
|
161
168
|
const setRoute = ROUTES.find((r) => r.operationId === "credentials_set");
|
|
162
169
|
const listRoute = ROUTES.find((r) => r.operationId === "credentials_list");
|
|
163
170
|
const deleteRoute = ROUTES.find((r) => r.operationId === "credentials_delete");
|
|
@@ -173,7 +180,11 @@ type ListResponse = {
|
|
|
173
180
|
}>;
|
|
174
181
|
managedCredentials: unknown[];
|
|
175
182
|
};
|
|
176
|
-
type DeleteResponse = {
|
|
183
|
+
type DeleteResponse = {
|
|
184
|
+
service: string;
|
|
185
|
+
field: string;
|
|
186
|
+
affectedConnections: string[];
|
|
187
|
+
};
|
|
177
188
|
|
|
178
189
|
const SECRET_VALUE = "super-secret-token-value";
|
|
179
190
|
|
|
@@ -786,7 +797,11 @@ describe("credentials routes", () => {
|
|
|
786
797
|
})) as DeleteResponse;
|
|
787
798
|
|
|
788
799
|
// THEN the response echoes the identifiers
|
|
789
|
-
expect(result).toEqual({
|
|
800
|
+
expect(result).toEqual({
|
|
801
|
+
service: "vercel",
|
|
802
|
+
field: "api_token",
|
|
803
|
+
affectedConnections: [],
|
|
804
|
+
});
|
|
790
805
|
|
|
791
806
|
// AND the secret and metadata are gone
|
|
792
807
|
expect(secureStore.has("vercel:api_token")).toBe(false);
|
|
@@ -811,7 +826,11 @@ describe("credentials routes", () => {
|
|
|
811
826
|
})) as DeleteResponse;
|
|
812
827
|
|
|
813
828
|
// THEN the response echoes the identifiers
|
|
814
|
-
expect(result).toEqual({
|
|
829
|
+
expect(result).toEqual({
|
|
830
|
+
service: "slack_channel",
|
|
831
|
+
field: "user_token",
|
|
832
|
+
affectedConnections: [],
|
|
833
|
+
});
|
|
815
834
|
|
|
816
835
|
// AND only the user_token is removed; bot + app tokens remain
|
|
817
836
|
expect(secureStore.has("slack_channel:user_token")).toBe(false);
|
|
@@ -237,7 +237,8 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
237
237
|
"tools/network/web-fetch.ts", // Firecrawl /scrape BYOK fetch provider API key lookup (firecrawl provider key)
|
|
238
238
|
"workspace/default-provider-ensure.ts", // legacy anthropic echo disambiguation (vault key presence check)
|
|
239
239
|
"providers/inference/connection-availability.ts", // shared (provider, connection) availability status (credential presence check only; value never leaves the helper)
|
|
240
|
-
"plugin-api/resolve-credential.ts", // plugin-facing resolveCredential
|
|
240
|
+
"plugin-api/resolve-credential.ts", // plugin-facing resolveCredential: reveal-equivalent plaintext read, scoped to the in-context plugin's own service
|
|
241
|
+
"tools/credentials/store.ts", // shared credential write path (setSecureKeyAsync only; no reads) behind credentials/set and plugin-facing storeCredential
|
|
241
242
|
]);
|
|
242
243
|
|
|
243
244
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -104,6 +104,60 @@ describe("daemon credential client", () => {
|
|
|
104
104
|
expect(result.error).toBe("Credential store is unreachable");
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
+
test("refuses to delete around the in-use guard when the daemon is down", async () => {
|
|
108
|
+
/**
|
|
109
|
+
* The guard runs daemon-side, so a stopped daemon must not turn into an
|
|
110
|
+
* unchecked direct delete.
|
|
111
|
+
*/
|
|
112
|
+
|
|
113
|
+
// GIVEN a stored credential and no reachable daemon
|
|
114
|
+
_ipcResponse = {
|
|
115
|
+
ok: false,
|
|
116
|
+
error: "Could not connect to assistant daemon. Is it running?",
|
|
117
|
+
};
|
|
118
|
+
await setSecureKeyViaDaemon("api_key", "guarded-provider", "keep-me");
|
|
119
|
+
|
|
120
|
+
// WHEN the delete is attempted without force
|
|
121
|
+
const result = await deleteSecureKeyViaDaemon(
|
|
122
|
+
"api_key",
|
|
123
|
+
"guarded-provider",
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// THEN it is refused and the credential survives
|
|
127
|
+
expect(result.result).toBe("error");
|
|
128
|
+
expect(result.code).toBe("IN_USE_CHECK_UNAVAILABLE");
|
|
129
|
+
expect(result.error).toContain("--force");
|
|
130
|
+
expect(
|
|
131
|
+
await getSecureKeyAsync(credentialKey("guarded-provider", "api_key")),
|
|
132
|
+
).toBe("keep-me");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("deletes directly when the caller forces past the unavailable guard", async () => {
|
|
136
|
+
/**
|
|
137
|
+
* An explicit --force still works with the daemon down.
|
|
138
|
+
*/
|
|
139
|
+
|
|
140
|
+
// GIVEN a stored credential and no reachable daemon
|
|
141
|
+
_ipcResponse = {
|
|
142
|
+
ok: false,
|
|
143
|
+
error: "Could not connect to assistant daemon. Is it running?",
|
|
144
|
+
};
|
|
145
|
+
await setSecureKeyViaDaemon("api_key", "forced-provider", "drop-me");
|
|
146
|
+
|
|
147
|
+
// WHEN the delete is forced
|
|
148
|
+
const result = await deleteSecureKeyViaDaemon(
|
|
149
|
+
"api_key",
|
|
150
|
+
"forced-provider",
|
|
151
|
+
true,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// THEN the credential is gone from secure storage
|
|
155
|
+
expect(result.result).toBe("deleted");
|
|
156
|
+
expect(
|
|
157
|
+
await getSecureKeyAsync(credentialKey("forced-provider", "api_key")),
|
|
158
|
+
).toBeUndefined();
|
|
159
|
+
});
|
|
160
|
+
|
|
107
161
|
test("returns not-found for 404 errors", async () => {
|
|
108
162
|
_ipcResponse = {
|
|
109
163
|
ok: false,
|
|
@@ -167,5 +221,39 @@ describe("daemon credential client", () => {
|
|
|
167
221
|
body: { type: "credential", name: "github-app:pem" },
|
|
168
222
|
});
|
|
169
223
|
});
|
|
224
|
+
|
|
225
|
+
test("delete carries the caller's force choice past the in-use guard", async () => {
|
|
226
|
+
// GIVEN a daemon that accepts the delete
|
|
227
|
+
_ipcResponse = { ok: true, result: { success: true } };
|
|
228
|
+
_lastIpcCall = null;
|
|
229
|
+
|
|
230
|
+
// WHEN the caller forces the delete
|
|
231
|
+
await deleteSecureKeyViaDaemon("api_key", "agentrouter", true);
|
|
232
|
+
|
|
233
|
+
// THEN `force` rides along in the request body
|
|
234
|
+
const captured = _lastIpcCall as {
|
|
235
|
+
method: string;
|
|
236
|
+
params: unknown;
|
|
237
|
+
} | null;
|
|
238
|
+
expect(captured?.params).toEqual({
|
|
239
|
+
body: { type: "api_key", name: "agentrouter", force: true },
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("delete surfaces the in-use refusal from the daemon", async () => {
|
|
244
|
+
// GIVEN a credential an LLM provider connection resolves its auth through
|
|
245
|
+
_ipcResponse = {
|
|
246
|
+
ok: false,
|
|
247
|
+
error:
|
|
248
|
+
'Credential credential/agentrouter/api_key is in use by connection "agentrouter". It will stop working without it. Delete it anyway to continue.',
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// WHEN the delete is attempted without force
|
|
252
|
+
const result = await deleteSecureKeyViaDaemon("api_key", "agentrouter");
|
|
253
|
+
|
|
254
|
+
// THEN the refusal reaches the caller naming the dependent connection
|
|
255
|
+
expect(result.result).toBe("error");
|
|
256
|
+
expect(result.error).toContain('in use by connection "agentrouter"');
|
|
257
|
+
});
|
|
170
258
|
});
|
|
171
259
|
});
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import { getAllDefaultPlugins } from "../plugins/defaults/index.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getAllDefaultPluginNames,
|
|
6
|
+
isFirstPartyDefaultPlugin,
|
|
7
|
+
} from "../plugins/defaults/main.js";
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Guard: the filesystem-backed default-plugin registry (`defaults/main.ts`)
|
|
@@ -25,4 +28,12 @@ describe("default plugin names", () => {
|
|
|
25
28
|
expect(name).toMatch(/^default-[a-z0-9-]+$/);
|
|
26
29
|
}
|
|
27
30
|
});
|
|
31
|
+
|
|
32
|
+
test("isFirstPartyDefaultPlugin matches the filesystem registry and rejects other names", () => {
|
|
33
|
+
for (const name of getAllDefaultPluginNames()) {
|
|
34
|
+
expect(isFirstPartyDefaultPlugin(name)).toBe(true);
|
|
35
|
+
}
|
|
36
|
+
expect(isFirstPartyDefaultPlugin("default-a")).toBe(false);
|
|
37
|
+
expect(isFirstPartyDefaultPlugin("uplug-a")).toBe(false);
|
|
38
|
+
});
|
|
28
39
|
});
|
|
@@ -180,6 +180,8 @@ function readPersistedSlackRows(): Array<{
|
|
|
180
180
|
provenanceSourceChannel: string | undefined;
|
|
181
181
|
provenanceGuardianExternalUserId: string | undefined;
|
|
182
182
|
provenanceRequesterIdentifier: string | undefined;
|
|
183
|
+
sentAt: number | undefined;
|
|
184
|
+
createdAt: number;
|
|
183
185
|
}> {
|
|
184
186
|
const db = getDb();
|
|
185
187
|
return db
|
|
@@ -187,6 +189,7 @@ function readPersistedSlackRows(): Array<{
|
|
|
187
189
|
role: messages.role,
|
|
188
190
|
content: messages.content,
|
|
189
191
|
metadata: messages.metadata,
|
|
192
|
+
createdAt: messages.createdAt,
|
|
190
193
|
})
|
|
191
194
|
.from(messages)
|
|
192
195
|
.all()
|
|
@@ -231,6 +234,9 @@ function readPersistedSlackRows(): Array<{
|
|
|
231
234
|
typeof envelope.provenanceRequesterIdentifier === "string"
|
|
232
235
|
? envelope.provenanceRequesterIdentifier
|
|
233
236
|
: undefined,
|
|
237
|
+
sentAt:
|
|
238
|
+
typeof envelope.sentAt === "number" ? envelope.sentAt : undefined,
|
|
239
|
+
createdAt: row.createdAt,
|
|
234
240
|
};
|
|
235
241
|
});
|
|
236
242
|
}
|
|
@@ -587,4 +593,61 @@ describe("PR 23 — Slack DM cold-start backfill", () => {
|
|
|
587
593
|
const texts = rows.map((r) => r.content).sort();
|
|
588
594
|
expect(texts).toEqual(["older A", "older B", "older D"]);
|
|
589
595
|
});
|
|
596
|
+
|
|
597
|
+
test("backfilled rows carry the original send time as sentAt", async () => {
|
|
598
|
+
// History serialization prefers `sentAt` for the display timestamp, so a
|
|
599
|
+
// row without it reads as having arrived when the import ran.
|
|
600
|
+
const sentAt = Date.UTC(2026, 6, 8, 9, 15);
|
|
601
|
+
backfillDmMock.mockImplementation(async () => [
|
|
602
|
+
makeBackfilledMessage({
|
|
603
|
+
id: String(sentAt / 1000),
|
|
604
|
+
text: "six weeks old",
|
|
605
|
+
timestamp: sentAt,
|
|
606
|
+
}),
|
|
607
|
+
]);
|
|
608
|
+
|
|
609
|
+
await handleChannelInbound(
|
|
610
|
+
buildDmRequest("live new DM"),
|
|
611
|
+
noopProcessMessage,
|
|
612
|
+
TEST_BEARER_TOKEN,
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
const [row] = readPersistedSlackRows();
|
|
616
|
+
expect(row).toBeDefined();
|
|
617
|
+
expect(row.sentAt).toBe(sentAt);
|
|
618
|
+
// The row is still written now; only the reported send time is historical.
|
|
619
|
+
// Asserting the gap is what proves `sentAt` is not just echoing createdAt.
|
|
620
|
+
expect(row.createdAt).toBeGreaterThan(sentAt);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test("backfill refuses history rows carrying a secret, keeping the rest", async () => {
|
|
624
|
+
const leakedKey = "AKIA3H7QWERTY9MNBVC2";
|
|
625
|
+
backfillDmMock.mockImplementation(async () => [
|
|
626
|
+
makeBackfilledMessage({
|
|
627
|
+
id: "1700000000.000001",
|
|
628
|
+
text: "ordinary older chatter",
|
|
629
|
+
}),
|
|
630
|
+
makeBackfilledMessage({
|
|
631
|
+
id: "1700000000.000002",
|
|
632
|
+
text: `here is the access key ${leakedKey} for the deploy`,
|
|
633
|
+
}),
|
|
634
|
+
]);
|
|
635
|
+
|
|
636
|
+
await handleChannelInbound(
|
|
637
|
+
buildDmRequest("live new DM"),
|
|
638
|
+
noopProcessMessage,
|
|
639
|
+
TEST_BEARER_TOKEN,
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
const rows = readPersistedSlackRows();
|
|
643
|
+
expect(rows.map((r) => r.content)).toEqual(["ordinary older chatter"]);
|
|
644
|
+
// `rawContent`, not `content`: the latter is unwrapped by the test
|
|
645
|
+
// helper, so only the raw column proves what reached storage.
|
|
646
|
+
for (const row of rows) {
|
|
647
|
+
expect(row.rawContent).not.toContain(leakedKey);
|
|
648
|
+
}
|
|
649
|
+
expect(
|
|
650
|
+
rows.some((r) => r.slackMeta?.channelTs === "1700000000.000002"),
|
|
651
|
+
).toBe(false);
|
|
652
|
+
});
|
|
590
653
|
});
|
|
@@ -65,9 +65,7 @@ mock.module("../permissions/checker.js", () => ({
|
|
|
65
65
|
isDynamicSkillLoadInvocation: () => dynamicSkillLoad,
|
|
66
66
|
classifyRisk: async () => ({ level: "medium" }),
|
|
67
67
|
check: async () => ({ decision: checkDecision, reason: "medium risk" }),
|
|
68
|
-
generateAllowlistOptions: () => [],
|
|
69
68
|
generateScopeOptions: () => [],
|
|
70
|
-
getCachedAssessment: () => undefined,
|
|
71
69
|
}));
|
|
72
70
|
|
|
73
71
|
mock.module("../telemetry/tool-usage-store.js", () => ({
|
|
@@ -171,7 +171,100 @@ describe("Slack edit propagation", () => {
|
|
|
171
171
|
expect(slackMeta!.editedAt!).toBeGreaterThanOrEqual(t0);
|
|
172
172
|
});
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
// Times out on the default budget: an unresolvable target pays the full
|
|
175
|
+
// `EDIT_LOOKUP_RETRIES` window before it is dropped.
|
|
176
|
+
test("an edit of a message the assistant never stored creates no conversation", async () => {
|
|
177
|
+
// Keying a conversation off the edit's own address made every edit of an
|
|
178
|
+
// unseen message mint one, permanently titled with the loading placeholder.
|
|
179
|
+
const db = getDb();
|
|
180
|
+
const countRows = (table: string): number =>
|
|
181
|
+
(
|
|
182
|
+
db.$client.prepare(`SELECT COUNT(*) AS n FROM ${table}`).get() as {
|
|
183
|
+
n: number;
|
|
184
|
+
}
|
|
185
|
+
).n;
|
|
186
|
+
|
|
187
|
+
const resp = (await handleEditIntercept({
|
|
188
|
+
sourceChannel: "slack",
|
|
189
|
+
conversationExternalId: "C0NEVERSEEN",
|
|
190
|
+
externalMessageId: nextEditEventId(),
|
|
191
|
+
sourceMessageId: "1999.0001",
|
|
192
|
+
assistantId: "self",
|
|
193
|
+
content: "edited text",
|
|
194
|
+
})) as Record<string, unknown>;
|
|
195
|
+
|
|
196
|
+
expect(resp.accepted).toBe(true);
|
|
197
|
+
expect(resp.edited).toBe(false);
|
|
198
|
+
expect(countRows("conversations")).toBe(0);
|
|
199
|
+
expect(countRows("channel_inbound_events")).toBe(0);
|
|
200
|
+
expect(countRows("messages")).toBe(0);
|
|
201
|
+
}, 30_000);
|
|
202
|
+
|
|
203
|
+
test("an edit records against the edited message's conversation, not a new one", async () => {
|
|
204
|
+
const seeded = await seedSlackMessage({
|
|
205
|
+
conversationExternalId: "C0123CHANNEL",
|
|
206
|
+
channelTs: "1234.5678",
|
|
207
|
+
initialContent: "original text",
|
|
208
|
+
});
|
|
209
|
+
const db = getDb();
|
|
210
|
+
const conversationsBefore = (
|
|
211
|
+
db.$client.prepare("SELECT COUNT(*) AS n FROM conversations").get() as {
|
|
212
|
+
n: number;
|
|
213
|
+
}
|
|
214
|
+
).n;
|
|
215
|
+
|
|
216
|
+
await handleEditIntercept({
|
|
217
|
+
sourceChannel: "slack",
|
|
218
|
+
conversationExternalId: seeded.conversationExternalId,
|
|
219
|
+
externalMessageId: nextEditEventId(),
|
|
220
|
+
sourceMessageId: seeded.channelTs,
|
|
221
|
+
assistantId: "self",
|
|
222
|
+
content: "new text",
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const conversationsAfter = (
|
|
226
|
+
db.$client.prepare("SELECT COUNT(*) AS n FROM conversations").get() as {
|
|
227
|
+
n: number;
|
|
228
|
+
}
|
|
229
|
+
).n;
|
|
230
|
+
expect(conversationsAfter).toBe(conversationsBefore);
|
|
231
|
+
|
|
232
|
+
const editEvents = db.$client
|
|
233
|
+
.prepare(
|
|
234
|
+
`SELECT conversation_id AS conversationId FROM channel_inbound_events
|
|
235
|
+
WHERE external_message_id LIKE 'edit-event-%'`,
|
|
236
|
+
)
|
|
237
|
+
.all() as Array<{ conversationId: string }>;
|
|
238
|
+
expect(editEvents).toHaveLength(1);
|
|
239
|
+
expect(editEvents[0].conversationId).toBe(seeded.conversationId);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("a redelivered edit is answered without repeating the lookup window", async () => {
|
|
243
|
+
const seeded = await seedSlackMessage({
|
|
244
|
+
conversationExternalId: "C0123CHANNEL",
|
|
245
|
+
channelTs: "1234.5678",
|
|
246
|
+
initialContent: "original text",
|
|
247
|
+
});
|
|
248
|
+
const editEventId = nextEditEventId();
|
|
249
|
+
const send = () =>
|
|
250
|
+
handleEditIntercept({
|
|
251
|
+
sourceChannel: "slack",
|
|
252
|
+
conversationExternalId: seeded.conversationExternalId,
|
|
253
|
+
externalMessageId: editEventId,
|
|
254
|
+
sourceMessageId: seeded.channelTs,
|
|
255
|
+
assistantId: "self",
|
|
256
|
+
content: "new text",
|
|
257
|
+
}) as Promise<Record<string, unknown>>;
|
|
258
|
+
|
|
259
|
+
const first = await send();
|
|
260
|
+
const second = await send();
|
|
261
|
+
|
|
262
|
+
expect(first.duplicate).toBe(false);
|
|
263
|
+
expect(second.duplicate).toBe(true);
|
|
264
|
+
expect(second.eventId).toBe(first.eventId);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("a threaded edit stays in the edited message's conversation and preserves thread metadata", async () => {
|
|
175
268
|
const conversationExternalId = "C0123CHANNEL";
|
|
176
269
|
const threadTs = "1234.0000";
|
|
177
270
|
const seeded = await seedSlackMessage({
|
|
@@ -179,6 +272,14 @@ describe("Slack edit propagation", () => {
|
|
|
179
272
|
channelTs: "1234.5678",
|
|
180
273
|
initialContent: "original text",
|
|
181
274
|
});
|
|
275
|
+
const db = getDb();
|
|
276
|
+
const conversationCount = (): number =>
|
|
277
|
+
(
|
|
278
|
+
db.$client.prepare("SELECT COUNT(*) AS n FROM conversations").get() as {
|
|
279
|
+
n: number;
|
|
280
|
+
}
|
|
281
|
+
).n;
|
|
282
|
+
const before = conversationCount();
|
|
182
283
|
|
|
183
284
|
const resp = await handleEditIntercept({
|
|
184
285
|
sourceChannel: "slack",
|
|
@@ -191,10 +292,12 @@ describe("Slack edit propagation", () => {
|
|
|
191
292
|
});
|
|
192
293
|
|
|
193
294
|
expect((resp as Record<string, unknown>).accepted).toBe(true);
|
|
295
|
+
|
|
296
|
+
// The thread id names where the reply goes, not a conversation of the
|
|
297
|
+
// edit's own. Keying one off it is what minted a conversation per edit.
|
|
194
298
|
const threadedKey = `asst:self:slack:${conversationExternalId}:thread:${threadTs}`;
|
|
195
|
-
|
|
196
|
-
expect(
|
|
197
|
-
expect(editConversation!.conversationId).not.toBe(seeded.conversationId);
|
|
299
|
+
expect(getConversationByKey(threadedKey)).toBeNull();
|
|
300
|
+
expect(conversationCount()).toBe(before);
|
|
198
301
|
|
|
199
302
|
const after = readMessageRow(seeded.messageId);
|
|
200
303
|
const outer = JSON.parse(after.metadata!) as Record<string, unknown>;
|
|
@@ -54,6 +54,34 @@ describe("events client registration", () => {
|
|
|
54
54
|
ac.abort();
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
+
test("registers Windows with only its implemented host capabilities", () => {
|
|
58
|
+
const ac = new AbortController();
|
|
59
|
+
const hub = new AssistantEventHub();
|
|
60
|
+
|
|
61
|
+
handleSubscribeAssistantEvents(
|
|
62
|
+
{
|
|
63
|
+
headers: {
|
|
64
|
+
"x-vellum-client-id": "test-windows-001",
|
|
65
|
+
"x-vellum-interface-id": "windows",
|
|
66
|
+
},
|
|
67
|
+
abortSignal: ac.signal,
|
|
68
|
+
},
|
|
69
|
+
{ hub },
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const entry = hub.getClientById("test-windows-001");
|
|
73
|
+
expect(entry?.interfaceId).toBe("windows");
|
|
74
|
+
expect(entry?.capabilities).toEqual([
|
|
75
|
+
"host_bash",
|
|
76
|
+
"host_file",
|
|
77
|
+
"host_cu",
|
|
78
|
+
"host_browser",
|
|
79
|
+
"host_ui_snapshot",
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
ac.abort();
|
|
83
|
+
});
|
|
84
|
+
|
|
57
85
|
test("skips registration when no headers are provided (backwards compat)", () => {
|
|
58
86
|
const ac = new AbortController();
|
|
59
87
|
const hub = new AssistantEventHub();
|
|
@@ -76,8 +76,10 @@ beforeEach(() => {
|
|
|
76
76
|
enqueueCalls.length = 0;
|
|
77
77
|
enqueueThrows = false;
|
|
78
78
|
// Reset to a stable tmp path so the sandbox tests (which don't use pkb/)
|
|
79
|
-
// deterministically land outside any configured PKB root.
|
|
80
|
-
|
|
79
|
+
// deterministically land outside any configured PKB root. A subdirectory
|
|
80
|
+
// rather than tmpdir() itself, so vellumRoot() (the parent) also stays
|
|
81
|
+
// inside the temp directory for the live-workspace guard.
|
|
82
|
+
process.env.VELLUM_WORKSPACE_DIR = join(tmpdir(), "file-write-tool-ws");
|
|
81
83
|
});
|
|
82
84
|
|
|
83
85
|
afterEach(() => {
|