@vellumai/assistant 0.11.4 → 0.11.5-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/ARCHITECTURE.md +29 -4
- package/README.md +1 -1
- package/docs/architecture/memory.md +17 -5
- package/docs/architecture/security.md +96 -152
- package/docs/guardian-request-flow.md +13 -2
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +29 -31
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +193 -3
- package/package.json +1 -1
- package/src/__tests__/anthropic-provider.test.ts +10 -3
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +4 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
- package/src/__tests__/channel-reply-delivery.test.ts +26 -13
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
- package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
- package/src/__tests__/config-loader-backfill.test.ts +19 -10
- package/src/__tests__/container-cpu-sampler.test.ts +153 -0
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +17 -0
- package/src/__tests__/conversation-lifecycle.test.ts +20 -26
- package/src/__tests__/conversation-pairing.test.ts +186 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
- package/src/__tests__/credential-broker-server-use.test.ts +24 -5
- package/src/__tests__/credential-routes.test.ts +22 -3
- package/src/__tests__/credential-security-invariants.test.ts +2 -1
- package/src/__tests__/daemon-credential-client.test.ts +88 -0
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/dm-backfill.test.ts +63 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -4
- package/src/__tests__/events-client-registration.test.ts +28 -0
- package/src/__tests__/file-write-tool.test.ts +4 -2
- package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/host-proxy-interface.test.ts +11 -1
- package/src/__tests__/host-shell-tool.test.ts +23 -4
- package/src/__tests__/image-conversion.test.ts +143 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/input-repairs.test.ts +207 -0
- package/src/__tests__/live-workspace-guard.test.ts +75 -0
- package/src/__tests__/managed-profile-guard.test.ts +4 -2
- package/src/__tests__/mcp-abort-signal.test.ts +1 -1
- package/src/__tests__/mcp-client-auth.test.ts +1 -1
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
- package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
- package/src/__tests__/mtime-cache.test.ts +2 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
- package/src/__tests__/openai-provider.test.ts +22 -0
- package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/platform.test.ts +16 -1
- package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
- package/src/__tests__/plugin-api-shim.test.ts +5 -0
- package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
- package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/resource-pressure-guard.test.ts +428 -0
- package/src/__tests__/resource-pressure-routes.test.ts +113 -0
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
- package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
- package/src/__tests__/skill-tool-factory.test.ts +161 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +63 -38
- package/src/__tests__/tool-policy.test.ts +97 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
- package/src/__tests__/user-plugin-loader.test.ts +2 -0
- package/src/__tests__/validate-input.test.ts +243 -7
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -0
- package/src/__tests__/voice-session-bridge.test.ts +42 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
- package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
- package/src/acp/acp-claude-oauth.ts +38 -21
- package/src/acp/prepare-agent-env.ts +146 -55
- package/src/agent/loop-tool-dedup.test.ts +161 -0
- package/src/agent/loop.ts +68 -22
- package/src/api/constants/app-tools.ts +34 -0
- package/src/api/events/resource-pressure-status-changed.ts +53 -0
- package/src/api/index.ts +19 -0
- package/src/api/responses/resource-pressure-status.ts +25 -0
- package/src/approvals/guardian-channel-delivery.ts +70 -6
- package/src/approvals/guardian-request-resolvers.ts +32 -66
- package/src/calls/__tests__/voice-session-bridge.test.ts +114 -0
- package/src/calls/voice-session-bridge.ts +60 -6
- package/src/channels/__tests__/message-audience.test.ts +49 -0
- package/src/channels/__tests__/types.test.ts +17 -3
- package/src/channels/message-audience.ts +40 -0
- package/src/channels/types.ts +16 -12
- package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
- package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
- package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/__tests__/inference-providers.test.ts +68 -0
- package/src/cli/commands/__tests__/keys.test.ts +89 -8
- package/src/cli/commands/__tests__/plugins.test.ts +57 -1
- package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
- package/src/cli/commands/channel-verification-sessions.ts +11 -21
- package/src/cli/commands/conversations.help.ts +34 -0
- package/src/cli/commands/conversations.ts +125 -0
- package/src/cli/commands/inference-providers.ts +9 -4
- package/src/cli/commands/inference.help.ts +9 -4
- package/src/cli/commands/keys.help.ts +13 -1
- package/src/cli/commands/keys.ts +26 -15
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/index.help.ts +41 -27
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/monitoring.ts +1 -0
- package/src/cli/commands/plugins.help.ts +28 -31
- package/src/cli/commands/plugins.ts +10 -0
- package/src/cli/commands/skills.help.ts +13 -16
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
- package/src/cli/lib/bundled-marketplace.json +13 -0
- package/src/cli/lib/daemon-credential-client.ts +25 -3
- package/src/cli/lib/install-from-github.ts +35 -24
- package/src/cli/lib/merge-plugin-tree.ts +22 -4
- package/src/cli/lib/plugin-surfaces.ts +27 -0
- package/src/config/__tests__/balanced-model-experiment.test.ts +7 -7
- package/src/config/__tests__/default-profile-catalog.test.ts +3 -3
- package/src/config/default-profile-catalog.ts +5 -6
- package/src/config/feature-flag-registry.json +32 -32
- package/src/config/webhook-routing.ts +8 -0
- package/src/context/compactor.ts +31 -2
- package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
- package/src/daemon/conversation-error.ts +13 -0
- package/src/daemon/conversation-messaging.ts +22 -2
- package/src/daemon/conversation-process.ts +11 -0
- package/src/daemon/conversation-store.ts +10 -0
- package/src/daemon/conversation-surfaces.ts +21 -8
- package/src/daemon/conversation.ts +12 -10
- package/src/daemon/lifecycle.ts +6 -2
- package/src/daemon/message-types/conversations.ts +5 -7
- package/src/daemon/provider-rejection-log-fields.ts +124 -0
- package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
- package/src/daemon/resource-pressure-guard.ts +387 -0
- package/src/daemon/shutdown-handlers.ts +2 -0
- package/src/daemon/startup-error.ts +16 -0
- package/src/daemon/trust-context.ts +14 -10
- package/src/daemon/unsendable-image-notice.ts +76 -0
- package/src/hooks/registry.ts +65 -51
- package/src/ipc/__tests__/socket-path.test.ts +15 -7
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/socket-cleanup.ts +2 -11
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
- package/src/messaging/provider-message-metadata.ts +128 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +116 -68
- package/src/messaging/providers/channel-transport.ts +90 -13
- package/src/messaging/providers/discord/send.ts +16 -0
- package/src/messaging/providers/discord/transport.ts +11 -1
- package/src/messaging/providers/index.ts +109 -16
- package/src/messaging/providers/slack/message-metadata.ts +45 -0
- package/src/messaging/providers/slack/render-transcript.ts +1 -4
- package/src/messaging/providers/slack/send.test.ts +6 -9
- package/src/messaging/providers/slack/send.ts +40 -28
- package/src/messaging/providers/slack/transport.ts +22 -26
- package/src/messaging/providers/telegram-bot/send.test.ts +2 -8
- package/src/messaging/providers/telegram-bot/transport.ts +3 -6
- package/src/messaging/read-provider-metadata.test.ts +157 -0
- package/src/messaging/read-provider-metadata.ts +51 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
- package/src/notifications/__tests__/edit-notification.test.ts +174 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
- package/src/notifications/adapters/macos.ts +55 -0
- package/src/notifications/adapters/slack.ts +10 -5
- package/src/notifications/assistant-reply-producer.ts +37 -4
- package/src/notifications/conversation-pairing.ts +111 -21
- package/src/notifications/edit-notification.ts +38 -8
- package/src/notifications/emit-signal.ts +12 -14
- package/src/notifications/home-feed-side-effect.ts +218 -15
- package/src/notifications/types.ts +5 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +28 -3
- package/src/permissions/confirmation-guardian-request.ts +5 -1
- package/src/persistence/conversation-crud.ts +23 -20
- package/src/persistence/conversation-queries.ts +14 -1
- package/src/persistence/conversation-types.ts +22 -0
- package/src/persistence/delivery-crud.ts +122 -15
- package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
- package/src/plugin-api/conversation-turn.ts +27 -0
- package/src/plugin-api/credential-scope.test.ts +15 -0
- package/src/plugin-api/credential-scope.ts +14 -0
- package/src/plugin-api/index.ts +19 -1
- package/src/plugin-api/resolve-credential.ts +15 -10
- package/src/plugin-api/store-credential.ts +148 -0
- package/src/plugin-api/system-card.ts +36 -0
- package/src/plugin-api/vision-support.test.ts +82 -0
- package/src/plugin-api/vision-support.ts +14 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
- package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
- package/src/plugins/defaults/image-recovery/detect.ts +24 -1
- package/src/plugins/defaults/main.ts +15 -0
- package/src/plugins/defaults/memory/AGENTS.md +11 -3
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/mtime-cache.ts +1 -1
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/plugin-tree-walk.ts +63 -4
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/connection-resolution.ts +2 -1
- package/src/providers/content-blocks.ts +22 -0
- package/src/providers/gemini/client.ts +5 -2
- package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
- package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
- package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
- package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
- package/src/providers/inference/adapter-factory.ts +33 -2
- package/src/providers/inference/auth.ts +13 -2
- package/src/providers/inference/credential-usage.ts +37 -0
- package/src/providers/inference/missing-credential-guard.ts +110 -0
- package/src/providers/inference/resolve-auth.ts +7 -7
- package/src/providers/media-resolve.ts +176 -15
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/chat-completions-provider.ts +322 -42
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +31 -10
- package/src/runtime/AGENTS.md +35 -0
- package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
- package/src/runtime/__tests__/web-presence.test.ts +234 -0
- package/src/runtime/assistant-event-hub.ts +38 -0
- package/src/runtime/channel-reply-delivery.ts +54 -31
- package/src/runtime/channel-retry-sweep.ts +6 -4
- package/src/runtime/effective-capabilities.test.ts +19 -0
- package/src/runtime/effective-capabilities.ts +25 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/http-errors.ts +1 -0
- package/src/runtime/http-server.ts +20 -5
- package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
- package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +52 -1
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +88 -35
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
- package/src/runtime/routes/approval-routes.ts +37 -4
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
- package/src/runtime/routes/canned-message-complete.ts +68 -29
- package/src/runtime/routes/client-routes.ts +113 -0
- package/src/runtime/routes/conversation-list-routes.ts +22 -7
- package/src/runtime/routes/conversation-management-routes.ts +15 -9
- package/src/runtime/routes/conversation-routes.ts +36 -22
- package/src/runtime/routes/credential-in-use.ts +85 -0
- package/src/runtime/routes/credential-routes.ts +55 -85
- package/src/runtime/routes/guardian-approval-interception.ts +22 -22
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
- package/src/runtime/routes/identity-routes.ts +5 -121
- package/src/runtime/routes/inbound-message-handler.ts +71 -27
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +77 -50
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -97
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +9 -7
- package/src/runtime/routes/monitoring-routes.ts +9 -1
- package/src/runtime/routes/plugins-routes.ts +17 -5
- package/src/runtime/routes/resource-pressure-routes.ts +22 -0
- package/src/runtime/routes/secret-routes.ts +39 -8
- package/src/runtime/routes/settings-routes.ts +6 -6
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/user-route-dispatcher.ts +31 -2
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/slack-reply-session.test.ts +23 -13
- package/src/runtime/slack-reply-session.ts +38 -55
- package/src/runtime/web-presence.ts +90 -0
- package/src/skills/validate-input.ts +177 -40
- package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
- package/src/subagent/consult-context.ts +7 -23
- package/src/tools/credentials/broker.ts +24 -79
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/credentials/tool-policy.ts +67 -0
- package/src/tools/executor.ts +53 -15
- package/src/tools/network/__tests__/web-search.test.ts +41 -1
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/network/web-search.ts +21 -3
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/schema-transforms.ts +40 -5
- package/src/tools/shared/input-repairs.ts +160 -0
- package/src/tools/skills/skill-tool-factory.ts +18 -4
- package/src/tools/subagent/spawn.ts +0 -1
- package/src/tools/tool-approval-handler.ts +16 -10
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/util/__tests__/cgroup-memory.test.ts +3 -0
- package/src/util/cgroup-memory.ts +3 -0
- package/src/util/container-cpu-sampler.ts +250 -0
- package/src/util/image-conversion.ts +178 -14
- package/src/util/platform.ts +93 -10
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
getContainerMemoryLimitBytes,
|
|
20
20
|
getContainerMemoryUsageBytes,
|
|
21
21
|
} from "../../util/cgroup-memory.js";
|
|
22
|
+
import { getCachedContainerCpuPercent } from "../../util/container-cpu-sampler.js";
|
|
22
23
|
import { getDiskUsageInfo } from "../../util/disk-usage.js";
|
|
23
24
|
import { getWorkspacePromptPath } from "../../util/platform.js";
|
|
24
25
|
import { APP_VERSION } from "../../version.js";
|
|
@@ -93,128 +94,11 @@ interface CpuInfo {
|
|
|
93
94
|
maxCores: number;
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
/**
|
|
97
|
-
* Read the container's CPU usage from cgroup accounting files.
|
|
98
|
-
*
|
|
99
|
-
* Returns total CPU microseconds consumed by the container since boot.
|
|
100
|
-
* We use the delta between two samples to compute percentage.
|
|
101
|
-
*/
|
|
102
|
-
function getContainerCpuUsageUs(): number | null {
|
|
103
|
-
// cgroups v2: cpu.stat has a "usage_usec" line.
|
|
104
|
-
try {
|
|
105
|
-
const stat = readFileSync("/sys/fs/cgroup/cpu.stat", "utf-8");
|
|
106
|
-
for (const line of stat.split("\n")) {
|
|
107
|
-
if (line.startsWith("usage_usec")) {
|
|
108
|
-
const val = parseInt(line.split(/\s+/)[1], 10);
|
|
109
|
-
if (!isNaN(val) && val > 0) {
|
|
110
|
-
return val;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
} catch {
|
|
115
|
-
/* not available */
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// cgroups v1: cpuacct.usage is in nanoseconds.
|
|
119
|
-
try {
|
|
120
|
-
const ns = parseInt(
|
|
121
|
-
readFileSync("/sys/fs/cgroup/cpuacct/cpuacct.usage", "utf-8").trim(),
|
|
122
|
-
10,
|
|
123
|
-
);
|
|
124
|
-
if (!isNaN(ns) && ns > 0) {
|
|
125
|
-
return ns / 1000;
|
|
126
|
-
} // convert ns → µs
|
|
127
|
-
} catch {
|
|
128
|
-
/* not available */
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Track CPU usage over a rolling window so /v1/health reports near-real-time
|
|
135
|
-
// utilization instead of a lifetime average (total CPU time / total uptime).
|
|
136
|
-
const CPU_SAMPLE_INTERVAL_MS = 5_000;
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Sample this process's cumulative CPU time, returning null when the
|
|
140
|
-
* underlying syscall fails (mirroring {@link sampleProcessRssBytes}).
|
|
141
|
-
*/
|
|
142
|
-
function sampleProcessCpuUsage(): NodeJS.CpuUsage | null {
|
|
143
|
-
try {
|
|
144
|
-
return process.cpuUsage();
|
|
145
|
-
} catch {
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
let _lastProcessCpuUsage: NodeJS.CpuUsage | null = sampleProcessCpuUsage();
|
|
151
|
-
let _lastCgroupCpuUs: number | null = getContainerCpuUsageUs();
|
|
152
|
-
let _lastCpuTime: number = Date.now();
|
|
153
|
-
let _cachedCpuPercent = 0;
|
|
154
|
-
|
|
155
|
-
// Kick off the background sampler. unref() so it never prevents process exit.
|
|
156
|
-
setInterval(() => {
|
|
157
|
-
const now = Date.now();
|
|
158
|
-
const elapsedMs = now - _lastCpuTime;
|
|
159
|
-
if (elapsedMs <= 0) {
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const numCores = getContainerCpuCores();
|
|
164
|
-
if (numCores <= 0) {
|
|
165
|
-
_lastCpuTime = now;
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Always sample process-level CPU so the baseline stays fresh. This
|
|
170
|
-
// prevents a spike if the platform cgroup path later falls back to
|
|
171
|
-
// process.cpuUsage() after cgroup stats were previously available.
|
|
172
|
-
const newProcessUsage = sampleProcessCpuUsage();
|
|
173
|
-
const processDeltaUs =
|
|
174
|
-
newProcessUsage !== null && _lastProcessCpuUsage !== null
|
|
175
|
-
? newProcessUsage.user -
|
|
176
|
-
_lastProcessCpuUsage.user +
|
|
177
|
-
(newProcessUsage.system - _lastProcessCpuUsage.system)
|
|
178
|
-
: null;
|
|
179
|
-
if (newProcessUsage !== null) {
|
|
180
|
-
_lastProcessCpuUsage = newProcessUsage;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (getIsPlatform()) {
|
|
184
|
-
// In platform mode, prefer cgroup-level CPU usage so we see the full
|
|
185
|
-
// container footprint, not just this process.
|
|
186
|
-
const cgroupUs = getContainerCpuUsageUs();
|
|
187
|
-
if (cgroupUs !== null && _lastCgroupCpuUs !== null) {
|
|
188
|
-
const deltaCpuUs = cgroupUs - _lastCgroupCpuUs;
|
|
189
|
-
const deltaCpuMs = deltaCpuUs / 1000;
|
|
190
|
-
_cachedCpuPercent =
|
|
191
|
-
Math.round((deltaCpuMs / (elapsedMs * numCores)) * 10000) / 100;
|
|
192
|
-
} else if (processDeltaUs !== null) {
|
|
193
|
-
// cgroup CPU stats unavailable (e.g. gVisor) – fall back to process-level.
|
|
194
|
-
const deltaCpuMs = processDeltaUs / 1000;
|
|
195
|
-
_cachedCpuPercent =
|
|
196
|
-
Math.round((deltaCpuMs / (elapsedMs * numCores)) * 10000) / 100;
|
|
197
|
-
}
|
|
198
|
-
_lastCgroupCpuUs = cgroupUs;
|
|
199
|
-
} else if (processDeltaUs !== null) {
|
|
200
|
-
// Non-platform: use process.cpuUsage() (accurate for single-process mode).
|
|
201
|
-
const deltaCpuMs = processDeltaUs / 1000;
|
|
202
|
-
_cachedCpuPercent =
|
|
203
|
-
Math.round((deltaCpuMs / (elapsedMs * numCores)) * 10000) / 100;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
_lastCpuTime = now;
|
|
207
|
-
}, CPU_SAMPLE_INTERVAL_MS).unref();
|
|
208
|
-
|
|
209
97
|
function getCpuInfo(): CpuInfo {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
};
|
|
215
|
-
} catch {
|
|
216
|
-
return { currentPercent: 0, maxCores: 0 };
|
|
217
|
-
}
|
|
98
|
+
return {
|
|
99
|
+
currentPercent: getCachedContainerCpuPercent(),
|
|
100
|
+
maxCores: Math.ceil(getContainerCpuCores()),
|
|
101
|
+
};
|
|
218
102
|
}
|
|
219
103
|
|
|
220
104
|
/**
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
attachmentsToContentBlocks,
|
|
18
18
|
type MessageAttachmentInput,
|
|
19
19
|
} from "../../agent/attachments.js";
|
|
20
|
+
import { audienceForReader } from "../../channels/message-audience.js";
|
|
20
21
|
import {
|
|
21
22
|
CHANNEL_IDS,
|
|
22
23
|
INTERFACE_IDS,
|
|
@@ -42,6 +43,7 @@ import { mapChatTypeToConversationType } from "../../daemon/trust-context.js";
|
|
|
42
43
|
import type { TrustContext } from "../../daemon/trust-context-types.js";
|
|
43
44
|
import { HeartbeatService } from "../../heartbeat/heartbeat-service.js";
|
|
44
45
|
import type { Message as ProviderMessage } from "../../messaging/provider-types.js";
|
|
46
|
+
import { editChannelMessage } from "../../messaging/providers/index.js";
|
|
45
47
|
import {
|
|
46
48
|
resolveSlackBotUserId,
|
|
47
49
|
withSlackBotToken,
|
|
@@ -77,7 +79,7 @@ import {
|
|
|
77
79
|
addMessage,
|
|
78
80
|
getMessageById,
|
|
79
81
|
getMessages,
|
|
80
|
-
|
|
82
|
+
selectProviderMetaCandidateMetadata,
|
|
81
83
|
updateMessageContent,
|
|
82
84
|
updateMessageMetadata,
|
|
83
85
|
} from "../../persistence/conversation-crud.js";
|
|
@@ -90,6 +92,7 @@ import {
|
|
|
90
92
|
import { markProcessed } from "../../persistence/delivery-status.js";
|
|
91
93
|
import { upsertBinding } from "../../persistence/external-conversation-store.js";
|
|
92
94
|
import type { ContentBlock } from "../../providers/types.js";
|
|
95
|
+
import { checkIngressForSecrets } from "../../security/secret-ingress.js";
|
|
93
96
|
import { canonicalizeInboundIdentity } from "../../util/canonicalize-identity.js";
|
|
94
97
|
import { getLogger } from "../../util/logger.js";
|
|
95
98
|
import { truncate } from "../../util/truncate.js";
|
|
@@ -417,9 +420,10 @@ export async function handleChannelInbound({
|
|
|
417
420
|
// 👍 never triggers a verification handshake or an access-request
|
|
418
421
|
// notification, and a stranger's reaction creates no conversation/binding.
|
|
419
422
|
// The interceptor drops strangers, records known contacts' reactions as
|
|
420
|
-
// transcript signals
|
|
421
|
-
//
|
|
422
|
-
// agent
|
|
423
|
+
// transcript signals in the conversation of the reacted message, and routes
|
|
424
|
+
// a guardian's reaction on an approval card through the guardian decision
|
|
425
|
+
// pipeline. Reactions never mint a conversation and never drive an agent
|
|
426
|
+
// turn.
|
|
423
427
|
if (isSlackReactionEvent(body)) {
|
|
424
428
|
return handleSlackReactionIntercept({
|
|
425
429
|
callbackData: body.callbackData!,
|
|
@@ -433,7 +437,6 @@ export async function handleChannelInbound({
|
|
|
433
437
|
actorUsername: body.actorUsername,
|
|
434
438
|
replyCallbackUrl: body.replyCallbackUrl,
|
|
435
439
|
sourceMetadata: body.sourceMetadata,
|
|
436
|
-
slackChannelName,
|
|
437
440
|
approvalConversationGenerator,
|
|
438
441
|
});
|
|
439
442
|
}
|
|
@@ -907,10 +910,11 @@ export async function handleChannelInbound({
|
|
|
907
910
|
text: replyText,
|
|
908
911
|
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
909
912
|
};
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
913
|
+
replyPayload.audience = audienceForReader(
|
|
914
|
+
sourceChannel,
|
|
915
|
+
conversationExternalId,
|
|
916
|
+
canonicalSenderId ?? rawSenderId,
|
|
917
|
+
);
|
|
914
918
|
try {
|
|
915
919
|
await deliverChannelReply(replyCallbackUrl, replyPayload);
|
|
916
920
|
replyDelivered = true;
|
|
@@ -969,10 +973,11 @@ export async function handleChannelInbound({
|
|
|
969
973
|
text: DISK_PRESSURE_REMOTE_BLOCK_REPLY,
|
|
970
974
|
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
971
975
|
};
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
+
replyPayload.audience = audienceForReader(
|
|
977
|
+
sourceChannel,
|
|
978
|
+
conversationExternalId,
|
|
979
|
+
canonicalSenderId ?? rawSenderId,
|
|
980
|
+
);
|
|
976
981
|
try {
|
|
977
982
|
await deliverChannelReply(replyCallbackUrl, replyPayload);
|
|
978
983
|
} catch (err) {
|
|
@@ -1189,15 +1194,16 @@ export async function handleChannelInbound({
|
|
|
1189
1194
|
}
|
|
1190
1195
|
}
|
|
1191
1196
|
|
|
1192
|
-
//
|
|
1193
|
-
// and
|
|
1197
|
+
// Edit the original approval message to remove stale buttons
|
|
1198
|
+
// and reply so the user gets visible feedback
|
|
1194
1199
|
// instead of a silent no-op (JARVIS-299).
|
|
1195
|
-
|
|
1196
|
-
|
|
1200
|
+
// No channel check: a transport without `edit` declines the call, so a
|
|
1201
|
+
// channel gains this the moment it can revise a sent message.
|
|
1202
|
+
if (replyCallbackUrl && approvalMessageTs) {
|
|
1203
|
+
editChannelMessage(replyCallbackUrl, {
|
|
1197
1204
|
chatId: conversationExternalId,
|
|
1205
|
+
messageId: approvalMessageTs,
|
|
1198
1206
|
text: "This approval request has been resolved.",
|
|
1199
|
-
messageTs: approvalMessageTs,
|
|
1200
|
-
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
1201
1207
|
}).catch((err) => {
|
|
1202
1208
|
log.error(
|
|
1203
1209
|
{ err, conversationId: result.conversationId },
|
|
@@ -1537,7 +1543,7 @@ function countSlackMetaMessages(conversationId: string): number {
|
|
|
1537
1543
|
while (offset < SLACK_DM_CANDIDATE_MAX_SCAN) {
|
|
1538
1544
|
const remaining = SLACK_DM_CANDIDATE_MAX_SCAN - offset;
|
|
1539
1545
|
const batchLimit = Math.min(SLACK_DM_CANDIDATE_BATCH_SIZE, remaining);
|
|
1540
|
-
const candidates =
|
|
1546
|
+
const candidates = selectProviderMetaCandidateMetadata(
|
|
1541
1547
|
conversationId,
|
|
1542
1548
|
batchLimit,
|
|
1543
1549
|
offset,
|
|
@@ -1676,6 +1682,10 @@ function readStoredSlackThreadState(
|
|
|
1676
1682
|
* content.
|
|
1677
1683
|
* Caller is responsible for dedup checks before invoking; this helper
|
|
1678
1684
|
* performs no idempotency check itself.
|
|
1685
|
+
*
|
|
1686
|
+
* Returns false when the row was refused rather than written. Callers mark
|
|
1687
|
+
* the channel ts seen either way, so a refused body is not reconsidered on
|
|
1688
|
+
* the next pass.
|
|
1679
1689
|
*/
|
|
1680
1690
|
async function persistBackfilledSlackMessage(params: {
|
|
1681
1691
|
conversationId: string;
|
|
@@ -1683,8 +1693,27 @@ async function persistBackfilledSlackMessage(params: {
|
|
|
1683
1693
|
message: ProviderMessage;
|
|
1684
1694
|
account?: string;
|
|
1685
1695
|
guardianExternalUserId?: string;
|
|
1686
|
-
}): Promise<
|
|
1696
|
+
}): Promise<boolean> {
|
|
1687
1697
|
const { message } = params;
|
|
1698
|
+
|
|
1699
|
+
// Backfill writes externally-authored bodies into a conversation, so it
|
|
1700
|
+
// carries the same secret gate as live channel ingress
|
|
1701
|
+
// (`runSecretIngressCheck`). Runs before file hydration, so a refused row
|
|
1702
|
+
// costs no downloads.
|
|
1703
|
+
const secretScan = checkIngressForSecrets(message.text ?? "");
|
|
1704
|
+
if (secretScan.blocked) {
|
|
1705
|
+
log.warn(
|
|
1706
|
+
{
|
|
1707
|
+
conversationId: params.conversationId,
|
|
1708
|
+
channelId: params.channelId,
|
|
1709
|
+
channelTs: message.id,
|
|
1710
|
+
detectedTypes: secretScan.detectedTypes,
|
|
1711
|
+
},
|
|
1712
|
+
"Skipping backfilled Slack message: secret detected",
|
|
1713
|
+
);
|
|
1714
|
+
return false;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1688
1717
|
const slackFilesWithUrls = readSlackFilesWithUrlsFromProviderMetadata(
|
|
1689
1718
|
message.metadata,
|
|
1690
1719
|
);
|
|
@@ -1736,9 +1765,19 @@ async function persistBackfilledSlackMessage(params: {
|
|
|
1736
1765
|
|
|
1737
1766
|
const rawText = message.text ?? "";
|
|
1738
1767
|
|
|
1768
|
+
// `sentAt` is when the message was sent; the row's `createdAt` is when the
|
|
1769
|
+
// import wrote it. History serialization prefers `sentAt` for the display
|
|
1770
|
+
// timestamp, so setting it is what keeps weeks-old history from reading as
|
|
1771
|
+
// having just arrived. The Slack adapter already derives this from the
|
|
1772
|
+
// message ts, so no parsing happens here.
|
|
1773
|
+
const sentAt = Number.isFinite(message.timestamp)
|
|
1774
|
+
? message.timestamp
|
|
1775
|
+
: undefined;
|
|
1776
|
+
|
|
1739
1777
|
const persisted = await addMessage(params.conversationId, role, rawText, {
|
|
1740
1778
|
metadata: {
|
|
1741
1779
|
slackMeta: writeSlackMetadata(slackMeta),
|
|
1780
|
+
...(sentAt !== undefined ? { sentAt } : {}),
|
|
1742
1781
|
provenanceTrustClass: isGuardian ? "guardian" : "unknown",
|
|
1743
1782
|
provenanceSourceChannel: "slack",
|
|
1744
1783
|
...(params.guardianExternalUserId
|
|
@@ -1761,7 +1800,7 @@ async function persistBackfilledSlackMessage(params: {
|
|
|
1761
1800
|
f.mimetype.startsWith("image/"),
|
|
1762
1801
|
);
|
|
1763
1802
|
if (imageFiles.length === 0) {
|
|
1764
|
-
return;
|
|
1803
|
+
return true;
|
|
1765
1804
|
}
|
|
1766
1805
|
|
|
1767
1806
|
const hydratedAttachments = await withSlackBotToken(
|
|
@@ -1832,7 +1871,7 @@ async function persistBackfilledSlackMessage(params: {
|
|
|
1832
1871
|
{ conversationId: params.conversationId, channelTs: message.id },
|
|
1833
1872
|
"No Slack token available for backfill image hydration; skipping",
|
|
1834
1873
|
);
|
|
1835
|
-
return;
|
|
1874
|
+
return true;
|
|
1836
1875
|
}
|
|
1837
1876
|
|
|
1838
1877
|
if (hydratedAttachments.length > 0) {
|
|
@@ -1843,6 +1882,7 @@ async function persistBackfilledSlackMessage(params: {
|
|
|
1843
1882
|
),
|
|
1844
1883
|
);
|
|
1845
1884
|
}
|
|
1885
|
+
return true;
|
|
1846
1886
|
}
|
|
1847
1887
|
|
|
1848
1888
|
async function buildBackfilledSlackContentBlocks(
|
|
@@ -2079,7 +2119,7 @@ async function runBackfillSlackDmIfCold(params: {
|
|
|
2079
2119
|
continue;
|
|
2080
2120
|
}
|
|
2081
2121
|
try {
|
|
2082
|
-
await persistBackfilledSlackMessage({
|
|
2122
|
+
const stored = await persistBackfilledSlackMessage({
|
|
2083
2123
|
conversationId: params.conversationId,
|
|
2084
2124
|
channelId: params.channelId,
|
|
2085
2125
|
message,
|
|
@@ -2089,7 +2129,9 @@ async function runBackfillSlackDmIfCold(params: {
|
|
|
2089
2129
|
: {}),
|
|
2090
2130
|
});
|
|
2091
2131
|
seen.add(message.id);
|
|
2092
|
-
|
|
2132
|
+
if (stored) {
|
|
2133
|
+
written++;
|
|
2134
|
+
}
|
|
2093
2135
|
} catch (perRowErr) {
|
|
2094
2136
|
log.warn(
|
|
2095
2137
|
{
|
|
@@ -2677,7 +2719,7 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
|
|
|
2677
2719
|
continue;
|
|
2678
2720
|
}
|
|
2679
2721
|
try {
|
|
2680
|
-
await persistBackfilledSlackMessage({
|
|
2722
|
+
const stored = await persistBackfilledSlackMessage({
|
|
2681
2723
|
conversationId,
|
|
2682
2724
|
channelId,
|
|
2683
2725
|
message,
|
|
@@ -2685,7 +2727,9 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
|
|
|
2685
2727
|
...(guardianExternalUserId ? { guardianExternalUserId } : {}),
|
|
2686
2728
|
});
|
|
2687
2729
|
threadState.storedChannelTs.add(message.id);
|
|
2688
|
-
|
|
2730
|
+
if (stored) {
|
|
2731
|
+
persisted++;
|
|
2732
|
+
}
|
|
2689
2733
|
} catch (err) {
|
|
2690
2734
|
log.warn(
|
|
2691
2735
|
{ err, conversationId, channelId, threadTs, channelTs: message.id },
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
getPendingSession,
|
|
20
20
|
resolveBootstrapToken,
|
|
21
21
|
} from "../../../channels/gateway-verification-sessions.js";
|
|
22
|
+
import { audienceForReader } from "../../../channels/message-audience.js";
|
|
22
23
|
import type { ChannelId } from "../../../channels/types.js";
|
|
23
24
|
import {
|
|
24
25
|
channelStatusToMemberStatus,
|
|
@@ -595,11 +596,11 @@ export async function enforceIngressAcl(
|
|
|
595
596
|
text: replyText,
|
|
596
597
|
assistantId,
|
|
597
598
|
};
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
599
|
+
replyPayload.audience = audienceForReader(
|
|
600
|
+
sourceChannel,
|
|
601
|
+
conversationExternalId,
|
|
602
|
+
canonicalSenderId ?? rawSenderId,
|
|
603
|
+
);
|
|
603
604
|
try {
|
|
604
605
|
await deliverChannelReply(replyCallbackUrl, replyPayload);
|
|
605
606
|
replyDelivered = true;
|
|
@@ -847,14 +848,11 @@ export async function enforceIngressAcl(
|
|
|
847
848
|
text: inactiveReplyText,
|
|
848
849
|
assistantId,
|
|
849
850
|
};
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
)
|
|
855
|
-
inactiveReplyPayload.ephemeral = true;
|
|
856
|
-
inactiveReplyPayload.user = (canonicalSenderId ?? rawSenderId)!;
|
|
857
|
-
}
|
|
851
|
+
inactiveReplyPayload.audience = audienceForReader(
|
|
852
|
+
sourceChannel,
|
|
853
|
+
conversationExternalId,
|
|
854
|
+
canonicalSenderId ?? rawSenderId,
|
|
855
|
+
);
|
|
858
856
|
try {
|
|
859
857
|
await deliverChannelReply(replyCallbackUrl, inactiveReplyPayload);
|
|
860
858
|
inactiveReplyDelivered = true;
|
|
@@ -891,10 +889,11 @@ export async function enforceIngressAcl(
|
|
|
891
889
|
text: denyReplyText,
|
|
892
890
|
assistantId,
|
|
893
891
|
};
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
892
|
+
denyPayload.audience = audienceForReader(
|
|
893
|
+
sourceChannel,
|
|
894
|
+
conversationExternalId,
|
|
895
|
+
canonicalSenderId ?? rawSenderId,
|
|
896
|
+
);
|
|
898
897
|
try {
|
|
899
898
|
await deliverChannelReply(replyCallbackUrl, denyPayload);
|
|
900
899
|
denyReplyDelivered = true;
|
|
@@ -88,6 +88,42 @@ mock.module("../../gateway-client.js", () => ({
|
|
|
88
88
|
},
|
|
89
89
|
}));
|
|
90
90
|
|
|
91
|
+
const sentReactions: Array<{
|
|
92
|
+
callbackUrl: string;
|
|
93
|
+
target: Record<string, unknown>;
|
|
94
|
+
}> = [];
|
|
95
|
+
const sentStreamOps: Array<Record<string, unknown>> = [];
|
|
96
|
+
let sendChannelStreamOpImpl: (
|
|
97
|
+
op: Record<string, unknown>,
|
|
98
|
+
) => Promise<{ ok: boolean; ts?: string }> = async () => ({ ok: true });
|
|
99
|
+
const sentThreadStatuses: Array<Record<string, unknown>> = [];
|
|
100
|
+
mock.module("../../../messaging/providers/index.js", () => ({
|
|
101
|
+
sendChannelTyping: async () => ({ ok: true }),
|
|
102
|
+
supportsChannelTyping: () => false,
|
|
103
|
+
sendChannelStreamOp: async (
|
|
104
|
+
_callbackUrl: string,
|
|
105
|
+
_chatId: string,
|
|
106
|
+
op: Record<string, unknown>,
|
|
107
|
+
) => {
|
|
108
|
+
sentStreamOps.push(op);
|
|
109
|
+
return sendChannelStreamOpImpl(op);
|
|
110
|
+
},
|
|
111
|
+
setChannelThreadStatus: async (
|
|
112
|
+
_callbackUrl: string,
|
|
113
|
+
status: Record<string, unknown>,
|
|
114
|
+
) => {
|
|
115
|
+
sentThreadStatuses.push(status);
|
|
116
|
+
return { ok: true };
|
|
117
|
+
},
|
|
118
|
+
sendChannelReaction: async (
|
|
119
|
+
callbackUrl: string,
|
|
120
|
+
target: Record<string, unknown>,
|
|
121
|
+
) => {
|
|
122
|
+
sentReactions.push({ callbackUrl, target });
|
|
123
|
+
return { ok: true };
|
|
124
|
+
},
|
|
125
|
+
}));
|
|
126
|
+
|
|
91
127
|
mock.module("../../channel-reply-delivery.js", () => ({
|
|
92
128
|
deliverReplyViaCallback: async (...args: unknown[]) => {
|
|
93
129
|
const options = args[4] as
|
|
@@ -127,6 +163,10 @@ beforeEach(() => {
|
|
|
127
163
|
__resetChannelTurnAdmissionForTests();
|
|
128
164
|
clearConversations();
|
|
129
165
|
deliveredChannelReplies.length = 0;
|
|
166
|
+
sentReactions.length = 0;
|
|
167
|
+
sentStreamOps.length = 0;
|
|
168
|
+
sendChannelStreamOpImpl = async () => ({ ok: true });
|
|
169
|
+
sentThreadStatuses.length = 0;
|
|
130
170
|
markedProcessedEvents.length = 0;
|
|
131
171
|
processingFailureEvents.length = 0;
|
|
132
172
|
retryableFailureEvents.length = 0;
|
|
@@ -144,10 +184,7 @@ beforeEach(() => {
|
|
|
144
184
|
deliverReplyViaCallbackImpl = async () => {};
|
|
145
185
|
});
|
|
146
186
|
|
|
147
|
-
const slackStreamOps = (): Array<Record<string, unknown>> =>
|
|
148
|
-
deliveredChannelReplies
|
|
149
|
-
.map((entry) => entry.payload.slackStream as Record<string, unknown>)
|
|
150
|
-
.filter(Boolean);
|
|
187
|
+
const slackStreamOps = (): Array<Record<string, unknown>> => sentStreamOps;
|
|
151
188
|
|
|
152
189
|
describe("isBoundGuardianActor", () => {
|
|
153
190
|
test("returns true only when requester matches bound guardian", () => {
|
|
@@ -492,7 +529,7 @@ describe("processChannelMessageInBackground — reply delivery", () => {
|
|
|
492
529
|
const channelId = "D-STREAMED";
|
|
493
530
|
const threadTs = "1700000000.000044";
|
|
494
531
|
const streamTs = "1700000000.000033";
|
|
495
|
-
|
|
532
|
+
sendChannelStreamOpImpl = async () => ({ ok: true, ts: streamTs });
|
|
496
533
|
|
|
497
534
|
const processMessage: MessageProcessor = async (
|
|
498
535
|
_conversationId,
|
|
@@ -671,7 +708,7 @@ describe("processChannelMessageInBackground — reply delivery", () => {
|
|
|
671
708
|
const channelId = "D-STREAM-PROCESSING-FAILURE";
|
|
672
709
|
const threadTs = "1700000000.000066";
|
|
673
710
|
const streamTs = "1700000000.000077";
|
|
674
|
-
|
|
711
|
+
sendChannelStreamOpImpl = async () => ({ ok: true, ts: streamTs });
|
|
675
712
|
|
|
676
713
|
const processMessage: MessageProcessor = async (
|
|
677
714
|
_conversationId,
|
|
@@ -925,11 +962,12 @@ describe("Slack thinking status timing", () => {
|
|
|
925
962
|
const messageTs = "1700000000.000010";
|
|
926
963
|
|
|
927
964
|
const processMessage: MessageProcessor = async () => {
|
|
928
|
-
expect(
|
|
929
|
-
expect(
|
|
965
|
+
expect(sentReactions).toHaveLength(1);
|
|
966
|
+
expect(sentReactions[0]!.target).toEqual({
|
|
967
|
+
chatId: channelId,
|
|
968
|
+
messageId: messageTs,
|
|
969
|
+
emoji: "eyes",
|
|
930
970
|
action: "add",
|
|
931
|
-
name: "eyes",
|
|
932
|
-
messageTs,
|
|
933
971
|
});
|
|
934
972
|
return { messageId: "user-msg-dm-immediate" };
|
|
935
973
|
};
|
|
@@ -950,12 +988,14 @@ describe("Slack thinking status timing", () => {
|
|
|
950
988
|
|
|
951
989
|
await flush();
|
|
952
990
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
991
|
+
expect(sentReactions.map((entry) => entry.target)).toEqual([
|
|
992
|
+
{ chatId: channelId, messageId: messageTs, emoji: "eyes", action: "add" },
|
|
993
|
+
{
|
|
994
|
+
chatId: channelId,
|
|
995
|
+
messageId: messageTs,
|
|
996
|
+
emoji: "eyes",
|
|
997
|
+
action: "remove",
|
|
998
|
+
},
|
|
959
999
|
]);
|
|
960
1000
|
});
|
|
961
1001
|
|
|
@@ -965,17 +1005,14 @@ describe("Slack thinking status timing", () => {
|
|
|
965
1005
|
const threadTs = "1700000000.000011";
|
|
966
1006
|
|
|
967
1007
|
const processMessage: MessageProcessor = async () => {
|
|
968
|
-
expect(
|
|
969
|
-
expect(
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
);
|
|
977
|
-
const threadStatus = deliveredChannelReplies[0]!.payload
|
|
978
|
-
.assistantThreadStatus as { status: string };
|
|
1008
|
+
expect(sentThreadStatuses).toHaveLength(1);
|
|
1009
|
+
expect(sentThreadStatuses[0]).toEqual({
|
|
1010
|
+
chatId: channelId,
|
|
1011
|
+
threadTs,
|
|
1012
|
+
status: expect.any(String),
|
|
1013
|
+
loadingMessages: ["Thinking\u2026"],
|
|
1014
|
+
});
|
|
1015
|
+
const threadStatus = sentThreadStatuses[0] as { status: string };
|
|
979
1016
|
expect(slackStatusLabels).toContain(threadStatus.status);
|
|
980
1017
|
return { messageId: "user-msg-mention-immediate" };
|
|
981
1018
|
};
|
|
@@ -996,12 +1033,9 @@ describe("Slack thinking status timing", () => {
|
|
|
996
1033
|
|
|
997
1034
|
await flush();
|
|
998
1035
|
|
|
999
|
-
const statuses =
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
| undefined;
|
|
1003
|
-
return status?.status;
|
|
1004
|
-
});
|
|
1036
|
+
const statuses = sentThreadStatuses.map(
|
|
1037
|
+
(entry) => (entry as { status?: string }).status,
|
|
1038
|
+
);
|
|
1005
1039
|
expect(slackStatusLabels).toContain(statuses[0]!);
|
|
1006
1040
|
expect(statuses[1]).toBe("");
|
|
1007
1041
|
});
|
|
@@ -1082,12 +1116,9 @@ describe("Slack thinking status timing", () => {
|
|
|
1082
1116
|
|
|
1083
1117
|
await flush();
|
|
1084
1118
|
|
|
1085
|
-
const statuses =
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
| undefined;
|
|
1089
|
-
return status?.status;
|
|
1090
|
-
});
|
|
1119
|
+
const statuses = sentThreadStatuses.map(
|
|
1120
|
+
(entry) => (entry as { status?: string }).status,
|
|
1121
|
+
);
|
|
1091
1122
|
expect(slackStatusLabels).toContain(statuses[0]!);
|
|
1092
1123
|
expect(statuses[1]).toBe("");
|
|
1093
1124
|
});
|
|
@@ -1144,18 +1175,16 @@ describe("Slack thinking status timing", () => {
|
|
|
1144
1175
|
|
|
1145
1176
|
await flush();
|
|
1146
1177
|
|
|
1147
|
-
const statuses =
|
|
1148
|
-
(entry) => entry.payload.assistantThreadStatus,
|
|
1149
|
-
);
|
|
1178
|
+
const statuses = sentThreadStatuses;
|
|
1150
1179
|
expect(statuses).toEqual([
|
|
1151
1180
|
{
|
|
1152
|
-
|
|
1181
|
+
chatId: channelId,
|
|
1153
1182
|
threadTs,
|
|
1154
1183
|
status: expect.any(String),
|
|
1155
1184
|
loadingMessages: ["In progress (1/2): Search docs"],
|
|
1156
1185
|
},
|
|
1157
1186
|
{
|
|
1158
|
-
|
|
1187
|
+
chatId: channelId,
|
|
1159
1188
|
threadTs,
|
|
1160
1189
|
status: "",
|
|
1161
1190
|
},
|
|
@@ -1278,24 +1307,22 @@ describe("Slack thinking status timing", () => {
|
|
|
1278
1307
|
|
|
1279
1308
|
await flush();
|
|
1280
1309
|
|
|
1281
|
-
const statuses =
|
|
1282
|
-
(entry) => entry.payload.assistantThreadStatus,
|
|
1283
|
-
);
|
|
1310
|
+
const statuses = sentThreadStatuses;
|
|
1284
1311
|
expect(statuses).toEqual([
|
|
1285
1312
|
{
|
|
1286
|
-
|
|
1313
|
+
chatId: channelId,
|
|
1287
1314
|
threadTs,
|
|
1288
1315
|
status: expect.any(String),
|
|
1289
1316
|
loadingMessages: ["In progress (1/2): Read request"],
|
|
1290
1317
|
},
|
|
1291
1318
|
{
|
|
1292
|
-
|
|
1319
|
+
chatId: channelId,
|
|
1293
1320
|
threadTs,
|
|
1294
1321
|
status: expect.any(String),
|
|
1295
1322
|
loadingMessages: ["In progress (2/2): Write answer"],
|
|
1296
1323
|
},
|
|
1297
1324
|
{
|
|
1298
|
-
|
|
1325
|
+
chatId: channelId,
|
|
1299
1326
|
threadTs,
|
|
1300
1327
|
status: "",
|
|
1301
1328
|
},
|