@vellumai/assistant 0.11.4 → 0.11.5-dev.202608220019.a778cf0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -2
- package/ARCHITECTURE.md +29 -4
- package/README.md +1 -1
- package/docs/architecture/memory.md +17 -5
- package/docs/architecture/security.md +96 -152
- package/docs/guardian-request-flow.md +13 -2
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +30 -32
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +487 -11
- package/package.json +1 -1
- package/scripts/sync-web-search-catalog.ts +6 -0
- package/src/__tests__/anthropic-provider.test.ts +10 -3
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +4 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
- package/src/__tests__/channel-readiness-service.test.ts +126 -0
- package/src/__tests__/channel-reply-delivery.test.ts +30 -17
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
- package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
- package/src/__tests__/config-loader-backfill.test.ts +19 -10
- package/src/__tests__/container-cpu-sampler.test.ts +153 -0
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-delete-watch-timeline.test.ts +231 -0
- package/src/__tests__/conversation-error.test.ts +17 -0
- package/src/__tests__/conversation-lifecycle.test.ts +20 -26
- package/src/__tests__/conversation-pairing.test.ts +186 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
- package/src/__tests__/credential-broker-server-use.test.ts +24 -5
- package/src/__tests__/credential-routes.test.ts +22 -3
- package/src/__tests__/credential-security-invariants.test.ts +2 -1
- package/src/__tests__/daemon-credential-client.test.ts +88 -0
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/dm-backfill.test.ts +63 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -4
- package/src/__tests__/events-client-registration.test.ts +28 -0
- package/src/__tests__/file-write-tool.test.ts +4 -2
- package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/host-proxy-interface.test.ts +11 -1
- package/src/__tests__/host-shell-tool.test.ts +23 -4
- package/src/__tests__/image-conversion.test.ts +143 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/input-repairs.test.ts +207 -0
- package/src/__tests__/live-workspace-guard.test.ts +75 -0
- package/src/__tests__/managed-profile-guard.test.ts +4 -2
- package/src/__tests__/mcp-abort-signal.test.ts +1 -1
- package/src/__tests__/mcp-client-auth.test.ts +1 -1
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
- package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
- package/src/__tests__/mtime-cache.test.ts +2 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
- package/src/__tests__/openai-provider.test.ts +40 -0
- package/src/__tests__/openai-responses-provider.test.ts +18 -0
- package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/platform.test.ts +16 -1
- package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
- package/src/__tests__/plugin-api-shim.test.ts +5 -0
- package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
- package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/resource-pressure-guard.test.ts +428 -0
- package/src/__tests__/resource-pressure-routes.test.ts +113 -0
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
- package/src/__tests__/sanitize-config-for-transfer.test.ts +28 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
- package/src/__tests__/settings-routes.test.ts +85 -3
- package/src/__tests__/skill-tool-factory.test.ts +161 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +63 -38
- package/src/__tests__/tool-policy.test.ts +97 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
- package/src/__tests__/user-plugin-loader.test.ts +2 -0
- package/src/__tests__/validate-input.test.ts +243 -7
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -0
- package/src/__tests__/voice-session-bridge.test.ts +42 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +8 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
- package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
- package/src/acp/acp-claude-oauth.ts +38 -21
- package/src/acp/prepare-agent-env.ts +146 -55
- package/src/agent/loop-tool-dedup.test.ts +161 -0
- package/src/agent/loop.ts +68 -22
- package/src/api/constants/app-tools.ts +34 -0
- package/src/api/constants/profile-config-validation.ts +60 -0
- package/src/api/events/resource-pressure-status-changed.ts +53 -0
- package/src/api/events/tool-result.ts +6 -1
- package/src/api/events/watch-retro-completed.ts +52 -0
- package/src/api/index.ts +30 -0
- package/src/api/responses/resource-pressure-status.ts +25 -0
- package/src/approvals/guardian-channel-delivery.ts +70 -6
- package/src/approvals/guardian-request-resolvers.ts +32 -66
- package/src/calls/__tests__/voice-session-bridge.test.ts +114 -0
- package/src/calls/voice-session-bridge.ts +60 -6
- package/src/channels/__tests__/message-audience.test.ts +49 -0
- package/src/channels/__tests__/types.test.ts +17 -3
- package/src/channels/message-audience.ts +40 -0
- package/src/channels/types.ts +16 -12
- package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
- package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
- package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +2 -1
- package/src/cli/commands/__tests__/inference-profiles.test.ts +16 -4
- package/src/cli/commands/__tests__/inference-providers.test.ts +135 -2
- package/src/cli/commands/__tests__/keys.test.ts +89 -8
- package/src/cli/commands/__tests__/plugins.test.ts +57 -1
- package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
- package/src/cli/commands/channel-verification-sessions.ts +11 -21
- package/src/cli/commands/conversations.help.ts +34 -0
- package/src/cli/commands/conversations.ts +125 -0
- package/src/cli/commands/inference-profiles.ts +56 -3
- package/src/cli/commands/inference-providers.ts +37 -6
- package/src/cli/commands/inference.help.ts +9 -4
- package/src/cli/commands/keys.help.ts +13 -1
- package/src/cli/commands/keys.ts +26 -15
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/index.help.ts +41 -27
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/monitoring.ts +1 -0
- package/src/cli/commands/oauth/request.test.ts +93 -0
- package/src/cli/commands/oauth/request.ts +23 -9
- package/src/cli/commands/plugins.help.ts +28 -31
- package/src/cli/commands/plugins.ts +10 -0
- package/src/cli/commands/skills.help.ts +13 -16
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
- package/src/cli/lib/bundled-marketplace.json +26 -0
- package/src/cli/lib/daemon-credential-client.ts +25 -3
- package/src/cli/lib/install-from-github.ts +35 -24
- package/src/cli/lib/merge-plugin-tree.ts +22 -4
- package/src/cli/lib/open-browser.test.ts +67 -0
- package/src/cli/lib/open-browser.ts +24 -5
- package/src/cli/lib/plugin-surfaces.ts +27 -0
- package/src/config/__tests__/balanced-model-experiment.test.ts +7 -7
- package/src/config/__tests__/default-profile-catalog.test.ts +3 -3
- package/src/config/__tests__/profile-materialization.test.ts +26 -0
- package/src/config/default-profile-catalog.ts +5 -6
- package/src/config/feature-flag-registry.json +36 -28
- package/src/config/profile-materialization.ts +29 -0
- package/src/config/sanitize-for-transfer.ts +16 -0
- package/src/config/schemas/llm.ts +7 -0
- package/src/config/schemas/services.ts +6 -0
- package/src/config/webhook-routing.ts +8 -0
- package/src/context/compactor.ts +31 -2
- package/src/context/outbound-sanitize.ts +6 -0
- package/src/daemon/__tests__/lifecycle-watch-timeline-sweep.test.ts +98 -0
- package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
- package/src/daemon/conversation-error.ts +19 -2
- package/src/daemon/conversation-messaging.ts +22 -2
- package/src/daemon/conversation-process.ts +11 -0
- package/src/daemon/conversation-store.ts +10 -0
- package/src/daemon/conversation-surfaces.ts +21 -8
- package/src/daemon/conversation.ts +12 -10
- package/src/daemon/daemon-control.ts +1 -0
- package/src/daemon/handlers/__tests__/config-ingress-tunnel-records.test.ts +208 -0
- package/src/daemon/handlers/config-ingress.ts +115 -5
- package/src/daemon/lifecycle.ts +30 -2
- package/src/daemon/message-types/conversations.ts +5 -7
- package/src/daemon/message-types/web-activity.ts +3 -2
- package/src/daemon/provider-rejection-log-fields.ts +124 -0
- package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
- package/src/daemon/resource-pressure-guard.ts +387 -0
- package/src/daemon/shutdown-handlers.ts +2 -0
- package/src/daemon/startup-error.ts +16 -0
- package/src/daemon/trust-context.ts +14 -10
- package/src/daemon/unsendable-image-notice.ts +76 -0
- package/src/hooks/registry.ts +65 -51
- package/src/inbound/__tests__/tunnel-probe.test.ts +448 -0
- package/src/inbound/tunnel-probe.ts +261 -0
- package/src/ipc/__tests__/socket-path.test.ts +15 -7
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/socket-cleanup.ts +2 -11
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
- package/src/messaging/provider-message-metadata.ts +128 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +138 -73
- package/src/messaging/providers/channel-transport.ts +100 -16
- package/src/messaging/providers/discord/send.ts +16 -0
- package/src/messaging/providers/discord/transport.ts +23 -1
- package/src/messaging/providers/index.ts +84 -16
- package/src/messaging/providers/slack/message-metadata.ts +45 -0
- package/src/messaging/providers/slack/render-transcript.ts +1 -4
- package/src/messaging/providers/slack/send.test.ts +88 -34
- package/src/messaging/providers/slack/send.ts +99 -50
- package/src/messaging/providers/slack/transport.ts +35 -37
- package/src/messaging/providers/telegram-bot/send.test.ts +88 -18
- package/src/messaging/providers/telegram-bot/send.ts +36 -0
- package/src/messaging/providers/telegram-bot/transport.ts +22 -8
- package/src/messaging/read-provider-metadata.test.ts +157 -0
- package/src/messaging/read-provider-metadata.ts +51 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
- package/src/notifications/__tests__/edit-notification.test.ts +174 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
- package/src/notifications/adapters/macos.ts +55 -0
- package/src/notifications/adapters/slack.ts +10 -5
- package/src/notifications/assistant-reply-producer.ts +37 -4
- package/src/notifications/conversation-pairing.ts +111 -21
- package/src/notifications/edit-notification.ts +38 -8
- package/src/notifications/emit-signal.ts +12 -14
- package/src/notifications/home-feed-side-effect.ts +218 -15
- package/src/notifications/types.ts +5 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +28 -3
- package/src/permissions/confirmation-guardian-request.ts +5 -1
- package/src/persistence/conversation-crud.ts +69 -20
- package/src/persistence/conversation-queries.ts +14 -1
- package/src/persistence/conversation-types.ts +22 -0
- package/src/persistence/db-async-query.ts +2 -1
- package/src/persistence/db-maintenance.ts +15 -0
- package/src/persistence/delivery-crud.ts +122 -15
- package/src/persistence/embeddings/qdrant-manager.ts +1 -0
- package/src/persistence/migrations/367-create-watch-timeline-entries.ts +46 -0
- package/src/persistence/migrations/368-watch-timeline-screenshot-blob.ts +33 -0
- package/src/persistence/migrations/__tests__/367-create-watch-timeline-entries.test.ts +98 -0
- package/src/persistence/migrations/__tests__/368-watch-timeline-screenshot-blob.test.ts +98 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/watch.ts +29 -0
- package/src/persistence/steps.ts +4 -0
- package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
- package/src/plugin-api/conversation-turn.ts +27 -0
- package/src/plugin-api/credential-scope.test.ts +15 -0
- package/src/plugin-api/credential-scope.ts +14 -0
- package/src/plugin-api/index.ts +19 -1
- package/src/plugin-api/resolve-credential.ts +15 -10
- package/src/plugin-api/store-credential.ts +148 -0
- package/src/plugin-api/system-card.ts +36 -0
- package/src/plugin-api/vision-support.test.ts +82 -0
- package/src/plugin-api/vision-support.ts +14 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
- package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
- package/src/plugins/defaults/image-recovery/detect.ts +24 -1
- package/src/plugins/defaults/main.ts +15 -0
- package/src/plugins/defaults/memory/AGENTS.md +11 -3
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/mtime-cache.ts +1 -1
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/plugin-tree-walk.ts +63 -4
- package/src/providers/__tests__/retry-network-error.test.ts +84 -0
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/connection-resolution.ts +25 -2
- package/src/providers/content-blocks.ts +22 -0
- package/src/providers/fetch-provider-catalog.ts +19 -0
- package/src/providers/gemini/client.ts +5 -2
- package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
- package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
- package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +92 -0
- package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
- package/src/providers/inference/__tests__/profile-config-validation.test.ts +39 -0
- package/src/providers/inference/__tests__/profile-probe-classify.test.ts +66 -0
- package/src/providers/inference/adapter-factory.ts +33 -2
- package/src/providers/inference/auth.ts +13 -2
- package/src/providers/inference/credential-usage.ts +37 -0
- package/src/providers/inference/endpoint-probe.ts +115 -0
- package/src/providers/inference/missing-credential-guard.ts +110 -0
- package/src/providers/inference/profile-probe.ts +256 -0
- package/src/providers/inference/resolve-auth.ts +7 -7
- package/src/providers/media-resolve.ts +176 -15
- package/src/providers/model-catalog.ts +10 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +17 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
- package/src/providers/openai/__tests__/connection-error-wrap.test.ts +44 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/api-error-normalization.ts +16 -2
- package/src/providers/openai/chat-completions-provider.ts +326 -44
- package/src/providers/openai/responses-provider.ts +5 -2
- package/src/providers/provider-send-message.ts +11 -0
- package/src/providers/retry.ts +6 -0
- package/src/providers/search-provider-catalog.ts +20 -0
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +31 -10
- package/src/runtime/AGENTS.md +36 -0
- package/src/runtime/__tests__/host-observe.test.ts +302 -0
- package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
- package/src/runtime/__tests__/web-presence.test.ts +234 -0
- package/src/runtime/assistant-event-hub.ts +38 -0
- package/src/runtime/channel-readiness-service.ts +39 -13
- package/src/runtime/channel-readiness-types.ts +49 -2
- package/src/runtime/channel-reply-delivery.ts +55 -32
- package/src/runtime/channel-retry-sweep.ts +6 -4
- package/src/runtime/effective-capabilities.test.ts +19 -0
- package/src/runtime/effective-capabilities.ts +25 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/host-observe.ts +246 -0
- package/src/runtime/http-errors.ts +1 -0
- package/src/runtime/http-server.ts +185 -6
- package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +80 -0
- package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +118 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +72 -1
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +508 -0
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +103 -71
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
- package/src/runtime/routes/__tests__/watch-routes-guardian-cache.test.ts +139 -0
- package/src/runtime/routes/__tests__/watch-routes.test.ts +598 -0
- package/src/runtime/routes/approval-routes.ts +37 -4
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
- package/src/runtime/routes/canned-message-complete.ts +68 -29
- package/src/runtime/routes/channel-readiness-routes.ts +14 -2
- package/src/runtime/routes/client-routes.ts +113 -0
- package/src/runtime/routes/conversation-list-routes.ts +22 -7
- package/src/runtime/routes/conversation-management-routes.ts +15 -9
- package/src/runtime/routes/conversation-query-routes.ts +10 -0
- package/src/runtime/routes/conversation-routes.ts +36 -22
- package/src/runtime/routes/credential-in-use.ts +85 -0
- package/src/runtime/routes/credential-routes.ts +55 -85
- package/src/runtime/routes/guardian-approval-interception.ts +44 -53
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
- package/src/runtime/routes/host-cu-routes.ts +18 -0
- package/src/runtime/routes/identity-routes.ts +5 -121
- package/src/runtime/routes/inbound-message-handler.ts +74 -30
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +182 -297
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +173 -386
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
- package/src/runtime/routes/index.ts +4 -0
- package/src/runtime/routes/inference-profiles-routes.ts +232 -31
- package/src/runtime/routes/inference-provider-connection-routes.ts +33 -11
- package/src/runtime/routes/ingress-status-routes.ts +180 -0
- package/src/runtime/routes/monitoring-routes.ts +9 -1
- package/src/runtime/routes/oauth-commands-routes.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +28 -32
- package/src/runtime/routes/resource-pressure-routes.ts +22 -0
- package/src/runtime/routes/secret-routes.ts +39 -8
- package/src/runtime/routes/settings-routes.ts +15 -12
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/user-route-dispatcher.ts +31 -2
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/routes/watch-routes.ts +807 -0
- package/src/runtime/slack-reply-session.test.ts +23 -13
- package/src/runtime/slack-reply-session.ts +38 -55
- package/src/runtime/web-presence.ts +90 -0
- package/src/security/__tests__/untrusted-content.test.ts +42 -0
- package/src/security/untrusted-content.ts +28 -9
- package/src/skills/validate-input.ts +177 -40
- package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
- package/src/subagent/consult-context.ts +7 -23
- package/src/tools/credentials/broker.ts +24 -79
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/credentials/tool-policy.ts +67 -0
- package/src/tools/executor.ts +53 -15
- package/src/tools/network/__tests__/firecrawl-compat.test.ts +77 -0
- package/src/tools/network/__tests__/web-fetch-fastcrw.test.ts +169 -0
- package/src/tools/network/__tests__/web-search.test.ts +138 -3
- package/src/tools/network/firecrawl-compat.ts +90 -0
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/network/web-fetch.ts +142 -62
- package/src/tools/network/web-search.ts +162 -58
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/schema-transforms.ts +40 -5
- package/src/tools/shared/input-repairs.ts +160 -0
- package/src/tools/skills/skill-tool-factory.ts +18 -4
- package/src/tools/subagent/spawn.ts +0 -1
- package/src/tools/tool-approval-handler.ts +16 -10
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/util/__tests__/cgroup-memory.test.ts +3 -0
- package/src/util/cgroup-memory.ts +3 -0
- package/src/util/container-cpu-sampler.ts +250 -0
- package/src/util/image-conversion.ts +178 -14
- package/src/util/platform.ts +93 -10
- package/src/util/worker-process.ts +1 -0
- package/src/watch/__tests__/watch-retro.test.ts +665 -0
- package/src/watch/__tests__/watch-session-manager.test.ts +566 -0
- package/src/watch/__tests__/watch-timeline.test.ts +670 -0
- package/src/watch/watch-retro.ts +480 -0
- package/src/watch/watch-session-manager.ts +575 -0
- package/src/watch/watch-timeline.ts +848 -0
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
|
@@ -9,17 +9,19 @@ const slack = {
|
|
|
9
9
|
Promise.resolve({ ts: "slack-ts" }),
|
|
10
10
|
),
|
|
11
11
|
sendSlackReaction: mock((..._args: unknown[]) => Promise.resolve()),
|
|
12
|
-
|
|
13
|
-
Promise.resolve(),
|
|
14
|
-
),
|
|
12
|
+
sendSlackAgentSessionStatus: mock((..._args: unknown[]) => Promise.resolve()),
|
|
15
13
|
sendSlackAttachments: mock((..._args: unknown[]) =>
|
|
16
14
|
Promise.resolve({ allFailed: false, failureCount: 0 }),
|
|
17
15
|
),
|
|
18
16
|
sendSlackStreamOp: mock((..._args: unknown[]) =>
|
|
19
17
|
Promise.resolve({ ok: true, ts: "stream-ts" }),
|
|
20
18
|
),
|
|
19
|
+
updateSlackMessage: mock((..._args: unknown[]) =>
|
|
20
|
+
Promise.resolve({ ts: "slack-ts" }),
|
|
21
|
+
),
|
|
21
22
|
};
|
|
22
23
|
const telegram = {
|
|
24
|
+
editTelegramMessage: mock((..._args: unknown[]) => Promise.resolve()),
|
|
23
25
|
sendTelegramReply: mock((..._args: unknown[]) => Promise.resolve()),
|
|
24
26
|
sendTelegramRichReply: mock((..._args: unknown[]) => Promise.resolve()),
|
|
25
27
|
sendTelegramTypingIndicator: mock((..._args: unknown[]) => Promise.resolve()),
|
|
@@ -57,8 +59,15 @@ mock.module("../../../util/logger.js", () => ({
|
|
|
57
59
|
getLogger: () => ({ debug() {}, info() {}, warn() {}, error() {} }),
|
|
58
60
|
}));
|
|
59
61
|
|
|
60
|
-
const {
|
|
61
|
-
|
|
62
|
+
const {
|
|
63
|
+
deliverDirect,
|
|
64
|
+
editChannelMessage,
|
|
65
|
+
sendChannelStreamOp,
|
|
66
|
+
setChannelActivity,
|
|
67
|
+
supportsChannelActivity,
|
|
68
|
+
isDirectDelivery,
|
|
69
|
+
getTransportForCallback,
|
|
70
|
+
} = await import("../index.js");
|
|
62
71
|
|
|
63
72
|
const BASE = "https://gateway.internal";
|
|
64
73
|
|
|
@@ -134,56 +143,49 @@ describe("Slack sub-operation selection", () => {
|
|
|
134
143
|
expect(opts.threadTs).toBe("1700.9");
|
|
135
144
|
});
|
|
136
145
|
|
|
137
|
-
test("
|
|
138
|
-
await
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
);
|
|
148
|
-
expect(slack.sendSlackReaction).toHaveBeenCalledTimes(1);
|
|
146
|
+
test("editChannelMessage updates in place instead of posting", async () => {
|
|
147
|
+
await editChannelMessage(`${BASE}/deliver/slack`, {
|
|
148
|
+
chatId: "C1",
|
|
149
|
+
messageId: "1700.5",
|
|
150
|
+
text: "revised",
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(slack.updateSlackMessage).toHaveBeenCalledTimes(1);
|
|
154
|
+
// The distinction the split exists for: an edit never reaches the post
|
|
155
|
+
// path, so a failed edit cannot become a second visible message.
|
|
149
156
|
expect(slack.sendSlackReply).not.toHaveBeenCalled();
|
|
150
157
|
});
|
|
151
158
|
|
|
152
|
-
test("
|
|
153
|
-
await
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
status: "is thinking",
|
|
160
|
-
},
|
|
161
|
-
}),
|
|
162
|
-
);
|
|
163
|
-
expect(slack.sendSlackAssistantThreadStatus).toHaveBeenCalledTimes(1);
|
|
159
|
+
test("setChannelActivity reaches Slack without touching the text path", async () => {
|
|
160
|
+
await setChannelActivity(`${BASE}/deliver/slack`, {
|
|
161
|
+
chatId: "C1",
|
|
162
|
+
phase: "thinking",
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(slack.sendSlackAgentSessionStatus).toHaveBeenCalledTimes(1);
|
|
164
166
|
expect(slack.sendSlackReply).not.toHaveBeenCalled();
|
|
165
167
|
});
|
|
166
168
|
|
|
167
|
-
test("a
|
|
168
|
-
await
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
);
|
|
172
|
-
|
|
169
|
+
test("a channel with no activity indicator resolves quietly", async () => {
|
|
170
|
+
const result = await setChannelActivity(`${BASE}/deliver/whatsapp`, {
|
|
171
|
+
chatId: "123",
|
|
172
|
+
phase: "thinking",
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(result).toEqual({ ok: true });
|
|
176
|
+
expect(slack.sendSlackReply).not.toHaveBeenCalled();
|
|
173
177
|
});
|
|
174
178
|
|
|
175
|
-
test("
|
|
176
|
-
const result = await
|
|
179
|
+
test("sendChannelStreamOp reaches Slack without touching the text path", async () => {
|
|
180
|
+
const result = await sendChannelStreamOp(
|
|
177
181
|
`${BASE}/deliver/slack?threadTs=1700.5`,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
slackStream: { action: "start", threadTs: "1700.5" },
|
|
181
|
-
}),
|
|
182
|
+
"C1",
|
|
183
|
+
{ action: "start", threadTs: "1700.5", markdownText: "hi" },
|
|
182
184
|
);
|
|
183
185
|
expect(slack.sendSlackStreamOp).toHaveBeenCalledTimes(1);
|
|
184
186
|
expect(slack.sendSlackStreamOp.mock.calls[0]).toEqual([
|
|
185
187
|
"C1",
|
|
186
|
-
{ action: "start", threadTs: "1700.5" },
|
|
188
|
+
{ action: "start", threadTs: "1700.5", markdownText: "hi" },
|
|
187
189
|
]);
|
|
188
190
|
expect(slack.sendSlackReply).not.toHaveBeenCalled();
|
|
189
191
|
expect(result).toEqual({ ok: true, ts: "stream-ts" });
|
|
@@ -191,22 +193,87 @@ describe("Slack sub-operation selection", () => {
|
|
|
191
193
|
});
|
|
192
194
|
|
|
193
195
|
describe("capability gating across channels", () => {
|
|
194
|
-
test("
|
|
195
|
-
await
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
196
|
+
test("Slack renders a muted edit as its own context block", async () => {
|
|
197
|
+
await editChannelMessage(`${BASE}/deliver/slack`, {
|
|
198
|
+
chatId: "C1",
|
|
199
|
+
messageId: "1700.5",
|
|
200
|
+
text: "This approval request has been resolved.",
|
|
201
|
+
emphasis: "muted",
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// The producer asked for a settled message and named no markup. Slack
|
|
205
|
+
// decides that means a context block.
|
|
206
|
+
const [, , , options] = slack.updateSlackMessage.mock.calls[0]!;
|
|
207
|
+
expect((options as { blocks?: unknown[] }).blocks).toEqual([
|
|
208
|
+
{
|
|
209
|
+
type: "context",
|
|
210
|
+
elements: [
|
|
211
|
+
{
|
|
212
|
+
type: "mrkdwn",
|
|
213
|
+
text: "This approval request has been resolved.",
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
]);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("an edit with no emphasis carries no channel markup", async () => {
|
|
221
|
+
await editChannelMessage(`${BASE}/deliver/slack`, {
|
|
222
|
+
chatId: "C1",
|
|
223
|
+
messageId: "1700.5",
|
|
224
|
+
text: "revised",
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const [, , , options] = slack.updateSlackMessage.mock.calls[0]!;
|
|
228
|
+
expect((options as { blocks?: unknown[] }).blocks).toBeUndefined();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("editChannelMessage now reaches Telegram, not only Slack", async () => {
|
|
232
|
+
// The capability gate is what let Telegram gain this: no caller changed,
|
|
233
|
+
// the transport grew the method.
|
|
234
|
+
await editChannelMessage(`${BASE}/deliver/telegram`, {
|
|
235
|
+
chatId: "123",
|
|
236
|
+
messageId: "456",
|
|
237
|
+
text: "revised",
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
expect(telegram.editTelegramMessage).toHaveBeenCalledTimes(1);
|
|
241
|
+
expect(telegram.sendTelegramReply).not.toHaveBeenCalled();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("a channel that cannot revise a sent message resolves quietly", async () => {
|
|
245
|
+
// Discord has no `edit` yet. A channel without the method is not a failed
|
|
246
|
+
// delivery: nothing is attempted, nothing throws, and no fresh message is
|
|
247
|
+
// posted in place of the revision.
|
|
248
|
+
expect(
|
|
249
|
+
await editChannelMessage(`${BASE}/deliver/discord`, {
|
|
250
|
+
chatId: "C1",
|
|
251
|
+
messageId: "1",
|
|
252
|
+
text: "revised",
|
|
200
253
|
}),
|
|
201
|
-
);
|
|
202
|
-
expect(
|
|
254
|
+
).toEqual({ ok: true });
|
|
255
|
+
expect(discord.sendDiscordReply).not.toHaveBeenCalled();
|
|
203
256
|
});
|
|
204
257
|
|
|
205
|
-
test("
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
258
|
+
test("the activity capability is read from the transport, not the channel name", () => {
|
|
259
|
+
// The gate in background-dispatch asks this rather than testing
|
|
260
|
+
// `sourceChannel === "slack"`, so a channel that implements the method
|
|
261
|
+
// starts showing an indicator without a caller being changed. All three
|
|
262
|
+
// answer the same question now, which is the point of the single method:
|
|
263
|
+
// Slack holds its indicator and the other two re-assert theirs, and a
|
|
264
|
+
// caller cannot tell the difference.
|
|
265
|
+
expect(supportsChannelActivity(`${BASE}/deliver/slack`)).toBe(true);
|
|
266
|
+
expect(supportsChannelActivity(`${BASE}/deliver/telegram`)).toBe(true);
|
|
267
|
+
expect(supportsChannelActivity(`${BASE}/deliver/discord`)).toBe(true);
|
|
268
|
+
expect(supportsChannelActivity(`${BASE}/deliver/whatsapp`)).toBe(false);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("setChannelActivity reaches Telegram's typing indicator", async () => {
|
|
272
|
+
await setChannelActivity(`${BASE}/deliver/telegram`, {
|
|
273
|
+
chatId: "123",
|
|
274
|
+
phase: "thinking",
|
|
275
|
+
});
|
|
276
|
+
|
|
210
277
|
expect(telegram.sendTelegramTypingIndicator).toHaveBeenCalledTimes(1);
|
|
211
278
|
});
|
|
212
279
|
|
|
@@ -244,26 +311,24 @@ describe("capability gating across channels", () => {
|
|
|
244
311
|
});
|
|
245
312
|
});
|
|
246
313
|
|
|
247
|
-
test("
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
);
|
|
255
|
-
expect(discord.sendDiscordReply).toHaveBeenCalledTimes(1);
|
|
314
|
+
test("setChannelActivity reaches Discord's typing indicator", async () => {
|
|
315
|
+
await setChannelActivity(`${BASE}/deliver/discord`, {
|
|
316
|
+
chatId: "999",
|
|
317
|
+
phase: "thinking",
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(discord.sendDiscordTypingIndicator).toHaveBeenCalledTimes(1);
|
|
256
321
|
});
|
|
257
322
|
|
|
258
|
-
test("a
|
|
259
|
-
await
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
);
|
|
266
|
-
expect(discord.sendDiscordReply).
|
|
323
|
+
test("a channel that cannot stream resolves quietly, and posts nothing", async () => {
|
|
324
|
+
const result = await sendChannelStreamOp(`${BASE}/deliver/discord`, "999", {
|
|
325
|
+
action: "start",
|
|
326
|
+
threadTs: "1700.5",
|
|
327
|
+
markdownText: "hi",
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
expect(result).toEqual({ ok: true });
|
|
331
|
+
expect(discord.sendDiscordReply).not.toHaveBeenCalled();
|
|
267
332
|
});
|
|
268
333
|
});
|
|
269
334
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ChannelDeliveryResult,
|
|
3
3
|
ChannelReplyPayload,
|
|
4
|
+
SlackStreamOp,
|
|
4
5
|
} from "@vellumai/gateway-client";
|
|
5
6
|
|
|
7
|
+
import type { AssistantActivityPhase } from "../../api/index.js";
|
|
6
8
|
import type { ChannelId } from "../../channels/types.js";
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -15,15 +17,69 @@ export interface CallbackContext {
|
|
|
15
17
|
readonly params: Readonly<Record<string, string>>;
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
/**
|
|
21
|
+
* How busy the assistant is in one conversation, for the channel to show.
|
|
22
|
+
*
|
|
23
|
+
* `phase` is the daemon's own activity phase rather than a channel's word for
|
|
24
|
+
* it, so every channel is told the same lifecycle and decides how to render
|
|
25
|
+
* it. A channel whose indicator expires on its own re-sends while the busy
|
|
26
|
+
* phases last; a channel whose indicator holds sets it once and clears it on
|
|
27
|
+
* `idle`. Neither shape leaks into what the caller says.
|
|
28
|
+
*/
|
|
29
|
+
export interface ActivityTarget {
|
|
30
|
+
readonly chatId: string;
|
|
31
|
+
readonly phase: AssistantActivityPhase;
|
|
32
|
+
/**
|
|
33
|
+
* Who started the turn. Channels that attribute a session to a person read
|
|
34
|
+
* it when the session is created, and ignore it afterwards.
|
|
35
|
+
*/
|
|
36
|
+
readonly initiatorUserId?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether a phase means a turn is actually running.
|
|
41
|
+
*
|
|
42
|
+
* A channel with a plain busy indicator shows it for these and nothing else.
|
|
43
|
+
* `awaiting_confirmation` is deliberately not one: an approval is waiting on a
|
|
44
|
+
* person, and a typing bubble there promises output that is not coming.
|
|
45
|
+
*/
|
|
46
|
+
export function isBusyActivityPhase(phase: AssistantActivityPhase): boolean {
|
|
47
|
+
return (
|
|
48
|
+
phase === "thinking" || phase === "streaming" || phase === "tool_running"
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* An existing message to replace in place, and what to replace it with.
|
|
54
|
+
*
|
|
55
|
+
* `messageId` is the target in the channel's own id space, the same way
|
|
56
|
+
* `chatId` is. `renderRichly` and `emphasis` are the rendering inputs the edit
|
|
57
|
+
* carries, so a replacement renders the way the original did rather than
|
|
58
|
+
* degrading to plain text.
|
|
59
|
+
*/
|
|
60
|
+
export interface EditTarget {
|
|
61
|
+
readonly chatId: string;
|
|
62
|
+
readonly messageId: string;
|
|
63
|
+
readonly text: string;
|
|
64
|
+
readonly renderRichly?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* How prominent the replacement should read. Surface-agnostic, the way
|
|
67
|
+
* `ApprovalActionOption.emphasis` is: each channel translates it to its own
|
|
68
|
+
* token, and one with no equivalent renders plain text. `muted` marks a
|
|
69
|
+
* message as settled rather than current.
|
|
70
|
+
*/
|
|
71
|
+
readonly emphasis?: "muted";
|
|
72
|
+
}
|
|
73
|
+
|
|
18
74
|
/**
|
|
19
75
|
* Direct outbound delivery for one channel, wrapping the channel's provider-API
|
|
20
76
|
* send functions behind a uniform surface. Transports are registered statically
|
|
21
77
|
* (delivery runs in non-daemon contexts) and dispatched by channel, resolved
|
|
22
78
|
* from the gateway callback URL via `callback-routing.ts`.
|
|
23
79
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
80
|
+
* Each operation takes its own parameters and is reached through its own entry
|
|
81
|
+
* point; a transport implements only the operations its channel supports, so
|
|
82
|
+
* an absent method is an absent capability.
|
|
27
83
|
*/
|
|
28
84
|
export interface ChannelTransport {
|
|
29
85
|
/** Canonical source channel id, e.g. `"slack"`. */
|
|
@@ -35,27 +91,55 @@ export interface ChannelTransport {
|
|
|
35
91
|
payload: ChannelReplyPayload,
|
|
36
92
|
): Promise<ChannelDeliveryResult>;
|
|
37
93
|
|
|
38
|
-
/**
|
|
39
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Replace a message the assistant already sent.
|
|
96
|
+
*
|
|
97
|
+
* Distinct from `deliver`, which always posts something new. A channel that
|
|
98
|
+
* cannot revise a sent message omits this, and a caller that needs the
|
|
99
|
+
* revision to be visible has to post instead of silently doing nothing.
|
|
100
|
+
*
|
|
101
|
+
* An implementation must not fall back to posting when the edit fails. The
|
|
102
|
+
* original would remain beside the replacement, which reads as the assistant
|
|
103
|
+
* answering twice.
|
|
104
|
+
*/
|
|
105
|
+
edit?(
|
|
40
106
|
ctx: CallbackContext,
|
|
41
|
-
|
|
107
|
+
target: EditTarget,
|
|
42
108
|
): Promise<ChannelDeliveryResult>;
|
|
43
109
|
|
|
44
|
-
/**
|
|
45
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Show how busy the assistant is, in whatever form the channel has.
|
|
112
|
+
*
|
|
113
|
+
* One capability rather than one per indicator shape: a self-expiring typing
|
|
114
|
+
* bubble and a status that holds until cleared answer the same question, and
|
|
115
|
+
* splitting them by how the channel keeps them alive forces every caller to
|
|
116
|
+
* know which channel it is talking to. A channel without any such affordance
|
|
117
|
+
* omits the method, so implementing it is the whole of declaring it.
|
|
118
|
+
*/
|
|
119
|
+
setActivity?(
|
|
46
120
|
ctx: CallbackContext,
|
|
47
|
-
|
|
121
|
+
target: ActivityTarget,
|
|
48
122
|
): Promise<ChannelDeliveryResult>;
|
|
49
123
|
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
124
|
+
/**
|
|
125
|
+
* How often a busy indicator must be re-asserted to stay visible, for a
|
|
126
|
+
* channel whose indicator expires on its own.
|
|
127
|
+
*
|
|
128
|
+
* Omitted by a channel whose indicator holds until it is changed, which is
|
|
129
|
+
* how a caller knows to set it once rather than run a timer. Declaring the
|
|
130
|
+
* cadence here keeps the caller from having to know which channel it is.
|
|
131
|
+
*/
|
|
132
|
+
readonly activityRefreshMs?: number;
|
|
55
133
|
|
|
56
|
-
/**
|
|
134
|
+
/**
|
|
135
|
+
* Advance a streamed reply: open it, add to it, or close it.
|
|
136
|
+
*
|
|
137
|
+
* A channel that can only post a finished message omits this, and the
|
|
138
|
+
* caller falls back to delivering the reply whole.
|
|
139
|
+
*/
|
|
57
140
|
streamReply?(
|
|
58
141
|
ctx: CallbackContext,
|
|
59
|
-
|
|
142
|
+
chatId: string,
|
|
143
|
+
op: SlackStreamOp,
|
|
60
144
|
): Promise<ChannelDeliveryResult>;
|
|
61
145
|
}
|
|
@@ -210,3 +210,19 @@ export async function sendDiscordAttachments(
|
|
|
210
210
|
totalCount: attachments.length,
|
|
211
211
|
};
|
|
212
212
|
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Show the Discord typing indicator in a channel.
|
|
216
|
+
*
|
|
217
|
+
* Discord expires it after about ten seconds, so a caller that wants it to
|
|
218
|
+
* persist re-sends on a timer rather than clearing it; there is no stop route.
|
|
219
|
+
*/
|
|
220
|
+
export async function sendDiscordTypingIndicator(
|
|
221
|
+
target: DiscordSendTarget,
|
|
222
|
+
): Promise<void> {
|
|
223
|
+
await callDiscordApi(
|
|
224
|
+
"POST",
|
|
225
|
+
`/channels/${encodeURIComponent(target.channelId)}/typing`,
|
|
226
|
+
{},
|
|
227
|
+
);
|
|
228
|
+
}
|
|
@@ -5,9 +5,14 @@ import type {
|
|
|
5
5
|
CallbackContext,
|
|
6
6
|
ChannelTransport,
|
|
7
7
|
} from "../channel-transport.js";
|
|
8
|
+
import { isBusyActivityPhase } from "../channel-transport.js";
|
|
8
9
|
import { openDiscordDmChannel } from "./api.js";
|
|
9
10
|
import type { DiscordSendTarget } from "./send.js";
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
sendDiscordAttachments,
|
|
13
|
+
sendDiscordReply,
|
|
14
|
+
sendDiscordTypingIndicator,
|
|
15
|
+
} from "./send.js";
|
|
11
16
|
|
|
12
17
|
const log = getLogger("discord-transport");
|
|
13
18
|
|
|
@@ -43,6 +48,23 @@ async function sendTarget(
|
|
|
43
48
|
export const discordTransport: ChannelTransport = {
|
|
44
49
|
channel: "discord",
|
|
45
50
|
|
|
51
|
+
// Discord clears a typing indicator after ten seconds.
|
|
52
|
+
activityRefreshMs: 8_000,
|
|
53
|
+
|
|
54
|
+
async setActivity(ctx, target) {
|
|
55
|
+
// Discord's typing indicator expires by itself after ten seconds, so a
|
|
56
|
+
// phase that is not running needs no clearing call.
|
|
57
|
+
if (!isBusyActivityPhase(target.phase)) {
|
|
58
|
+
return { ok: true };
|
|
59
|
+
}
|
|
60
|
+
await sendDiscordTypingIndicator(await sendTarget(ctx, target.chatId));
|
|
61
|
+
log.debug(
|
|
62
|
+
{ chatId: target.chatId, phase: target.phase },
|
|
63
|
+
"Discord typing indicator delivered (direct)",
|
|
64
|
+
);
|
|
65
|
+
return { ok: true };
|
|
66
|
+
},
|
|
67
|
+
|
|
46
68
|
async deliver(ctx, payload) {
|
|
47
69
|
const { chatId, text, attachments, approval } = payload;
|
|
48
70
|
const target = await sendTarget(ctx, chatId);
|
|
@@ -11,12 +11,18 @@
|
|
|
11
11
|
import type {
|
|
12
12
|
ChannelDeliveryResult,
|
|
13
13
|
ChannelReplyPayload,
|
|
14
|
+
SlackStreamOp,
|
|
14
15
|
} from "@vellumai/gateway-client";
|
|
15
16
|
|
|
16
17
|
import { a2aTransport } from "./a2a/transport.js";
|
|
17
18
|
import type { DirectDeliveryChannel } from "./callback-routing.js";
|
|
18
19
|
import { channelForCallback } from "./callback-routing.js";
|
|
19
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
ActivityTarget,
|
|
22
|
+
CallbackContext,
|
|
23
|
+
ChannelTransport,
|
|
24
|
+
EditTarget,
|
|
25
|
+
} from "./channel-transport.js";
|
|
20
26
|
import { discordTransport } from "./discord/transport.js";
|
|
21
27
|
import { slackTransport } from "./slack/transport.js";
|
|
22
28
|
import { telegramTransport } from "./telegram-bot/transport.js";
|
|
@@ -45,6 +51,81 @@ export function getTransportForCallback(
|
|
|
45
51
|
return channel ? TRANSPORTS[channel] : undefined;
|
|
46
52
|
}
|
|
47
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Whether the channel this callback addresses can show how busy the assistant
|
|
56
|
+
* is.
|
|
57
|
+
*
|
|
58
|
+
* Asks the transport rather than the channel id, so a channel that gains the
|
|
59
|
+
* method starts being asked without a caller being told about it.
|
|
60
|
+
*/
|
|
61
|
+
export function supportsChannelActivity(callbackUrl: string): boolean {
|
|
62
|
+
return getTransportForCallback(callbackUrl)?.setActivity !== undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* How often this channel's busy indicator has to be re-asserted, or
|
|
67
|
+
* `undefined` when it holds until changed and one call is enough.
|
|
68
|
+
*/
|
|
69
|
+
export function channelActivityRefreshMs(
|
|
70
|
+
callbackUrl: string,
|
|
71
|
+
): number | undefined {
|
|
72
|
+
return getTransportForCallback(callbackUrl)?.activityRefreshMs;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Show how busy the assistant is on the channel this callback addresses.
|
|
77
|
+
*
|
|
78
|
+
* Resolves to nothing when the channel has no such affordance, which is the
|
|
79
|
+
* ordinary case rather than a failure: the indicator is decoration, and a
|
|
80
|
+
* channel that cannot show one is not degraded by its absence.
|
|
81
|
+
*/
|
|
82
|
+
export async function setChannelActivity(
|
|
83
|
+
callbackUrl: string,
|
|
84
|
+
target: ActivityTarget,
|
|
85
|
+
): Promise<ChannelDeliveryResult> {
|
|
86
|
+
const transport = getTransportForCallback(callbackUrl);
|
|
87
|
+
if (!transport?.setActivity) {
|
|
88
|
+
return { ok: true };
|
|
89
|
+
}
|
|
90
|
+
return transport.setActivity(callbackContext(callbackUrl), target);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Replace a message the assistant already sent.
|
|
95
|
+
*
|
|
96
|
+
* Returns `ok` without acting when the channel cannot revise a sent message,
|
|
97
|
+
* matching the other capability entry points: an absent method is an absent
|
|
98
|
+
* capability, not an error.
|
|
99
|
+
*/
|
|
100
|
+
export async function editChannelMessage(
|
|
101
|
+
callbackUrl: string,
|
|
102
|
+
target: EditTarget,
|
|
103
|
+
): Promise<ChannelDeliveryResult> {
|
|
104
|
+
const transport = getTransportForCallback(callbackUrl);
|
|
105
|
+
if (!transport?.edit) {
|
|
106
|
+
return { ok: true };
|
|
107
|
+
}
|
|
108
|
+
return transport.edit(callbackContext(callbackUrl), target);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Advance a streamed reply on the channel this callback addresses.
|
|
113
|
+
*
|
|
114
|
+
* Resolves to nothing when the channel cannot stream, so a caller that wants a
|
|
115
|
+
* streamed reply learns it has to send the whole thing instead.
|
|
116
|
+
*/
|
|
117
|
+
export async function sendChannelStreamOp(
|
|
118
|
+
callbackUrl: string,
|
|
119
|
+
chatId: string,
|
|
120
|
+
op: SlackStreamOp,
|
|
121
|
+
): Promise<ChannelDeliveryResult> {
|
|
122
|
+
const transport = getTransportForCallback(callbackUrl);
|
|
123
|
+
if (!transport?.streamReply) {
|
|
124
|
+
return { ok: true };
|
|
125
|
+
}
|
|
126
|
+
return transport.streamReply(callbackContext(callbackUrl), chatId, op);
|
|
127
|
+
}
|
|
128
|
+
|
|
48
129
|
function callbackContext(callbackUrl: string): CallbackContext {
|
|
49
130
|
const params: Record<string, string> = {};
|
|
50
131
|
try {
|
|
@@ -74,9 +155,8 @@ export function isDirectDelivery(callbackUrl: string): boolean {
|
|
|
74
155
|
* Deliver a channel reply directly to the provider API, bypassing the gateway
|
|
75
156
|
* HTTP proxy. Callers MUST check `isDirectDelivery()` first.
|
|
76
157
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* otherwise the reply is delivered as text / approval / attachments.
|
|
158
|
+
* Delivers the reply itself: text, approval card, attachments. Every other
|
|
159
|
+
* operation a transport supports is reached through its own entry point.
|
|
80
160
|
*/
|
|
81
161
|
export async function deliverDirect(
|
|
82
162
|
callbackUrl: string,
|
|
@@ -90,17 +170,5 @@ export async function deliverDirect(
|
|
|
90
170
|
}
|
|
91
171
|
|
|
92
172
|
const ctx = callbackContext(callbackUrl);
|
|
93
|
-
if (payload.slackStream && transport.streamReply) {
|
|
94
|
-
return transport.streamReply(ctx, payload);
|
|
95
|
-
}
|
|
96
|
-
if (payload.reaction && transport.sendReaction) {
|
|
97
|
-
return transport.sendReaction(ctx, payload);
|
|
98
|
-
}
|
|
99
|
-
if (payload.assistantThreadStatus && transport.setThreadStatus) {
|
|
100
|
-
return transport.setThreadStatus(ctx, payload);
|
|
101
|
-
}
|
|
102
|
-
if (payload.chatAction === "typing" && transport.sendTyping) {
|
|
103
|
-
return transport.sendTyping(ctx, payload);
|
|
104
|
-
}
|
|
105
173
|
return transport.deliver(ctx, payload);
|
|
106
174
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
+
import type { ProviderMessageMetadata } from "../../provider-message-metadata.js";
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Typed Slack message metadata stored flat in the `messages.metadata` column
|
|
5
7
|
* alongside whatever other top-level keys the broader metadata envelope
|
|
@@ -288,6 +290,49 @@ export function readSlackMetadataFromMessageMetadata(
|
|
|
288
290
|
return opts?.allowFlatLegacy ? readSlackMetadata(metadata) : null;
|
|
289
291
|
}
|
|
290
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Present a Slack row's stored metadata as the channel-neutral shape.
|
|
295
|
+
*
|
|
296
|
+
* `channelTs` is the row's own provider id and `targetChannelTs` the id of the
|
|
297
|
+
* message a reaction was attached to. Both are Slack `ts` strings, which is a
|
|
298
|
+
* Slack detail of the id format, not of the fact.
|
|
299
|
+
*
|
|
300
|
+
* The rest of Slack's envelope (file markers, timezone labels, channel name)
|
|
301
|
+
* stays there and is read from there by the Slack transcript renderer, which
|
|
302
|
+
* is a provider renderer by design.
|
|
303
|
+
*/
|
|
304
|
+
export function slackMetadataAsProviderMetadata(
|
|
305
|
+
meta: SlackMessageMetadata,
|
|
306
|
+
): ProviderMessageMetadata {
|
|
307
|
+
return {
|
|
308
|
+
source: "slack",
|
|
309
|
+
conversationExternalId: meta.channelId,
|
|
310
|
+
// A reaction row stores the reacted message's ts in `channelTs`, which is
|
|
311
|
+
// its target rather than an id of its own.
|
|
312
|
+
...(meta.eventKind === "reaction" ? {} : { messageId: meta.channelTs }),
|
|
313
|
+
...(meta.actorExternalUserId
|
|
314
|
+
? { actorExternalId: meta.actorExternalUserId }
|
|
315
|
+
: {}),
|
|
316
|
+
...(meta.threadTs ? { threadId: meta.threadTs } : {}),
|
|
317
|
+
...(meta.displayName ? { displayName: meta.displayName } : {}),
|
|
318
|
+
eventKind: meta.eventKind,
|
|
319
|
+
...(meta.reaction
|
|
320
|
+
? {
|
|
321
|
+
reaction: {
|
|
322
|
+
targetMessageId: meta.reaction.targetChannelTs,
|
|
323
|
+
emoji: meta.reaction.emoji,
|
|
324
|
+
op: meta.reaction.op,
|
|
325
|
+
...(meta.reaction.actorDisplayName
|
|
326
|
+
? { actorDisplayName: meta.reaction.actorDisplayName }
|
|
327
|
+
: {}),
|
|
328
|
+
},
|
|
329
|
+
}
|
|
330
|
+
: {}),
|
|
331
|
+
...(meta.editedAt !== undefined ? { editedAt: meta.editedAt } : {}),
|
|
332
|
+
...(meta.deletedAt !== undefined ? { deletedAt: meta.deletedAt } : {}),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
291
336
|
/**
|
|
292
337
|
* Serialize `SlackMessageMetadata` to a JSON string suitable for a fresh
|
|
293
338
|
* write to the `messages.metadata` column. Use `mergeSlackMetadata` when an
|