@vellumai/assistant 0.8.5 → 0.8.6
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 +33 -1
- package/ARCHITECTURE.md +1 -1
- package/bunfig.toml +6 -1
- package/docs/credential-execution-service.md +6 -6
- package/docs/plugins.md +4 -3
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
- package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
- package/openapi.yaml +1900 -166
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +3 -2
- package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
- package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
- package/src/__tests__/annotate-risk-options.test.ts +1 -0
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +9 -13
- package/src/__tests__/assert-not-live-db.ts +79 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
- package/src/__tests__/audit-log-rotation.test.ts +2 -2
- package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
- package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
- package/src/__tests__/browser-skill-endstate.test.ts +3 -3
- package/src/__tests__/btw-routes.test.ts +3 -2
- package/src/__tests__/call-controller.test.ts +3 -2
- package/src/__tests__/channel-approval-routes.test.ts +3 -2
- package/src/__tests__/channel-guardian.test.ts +3 -2
- package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
- package/src/__tests__/channel-reply-delivery.test.ts +35 -0
- package/src/__tests__/channel-retry-sweep.test.ts +320 -3
- package/src/__tests__/checker.test.ts +12 -12
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/compaction-trail-store.test.ts +264 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
- package/src/__tests__/computer-use-tools.test.ts +12 -14
- package/src/__tests__/config-loader-backfill.test.ts +13 -28
- package/src/__tests__/config-loader-corrupt.test.ts +5 -5
- package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
- package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
- package/src/__tests__/config-schema.test.ts +10 -10
- package/src/__tests__/connection-model-compat.test.ts +83 -0
- package/src/__tests__/contacts-tools.test.ts +3 -2
- package/src/__tests__/context-token-estimator.test.ts +22 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
- package/src/__tests__/conversation-agent-loop.test.ts +488 -2
- package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
- package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
- package/src/__tests__/conversation-attention-store.test.ts +101 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-error.test.ts +30 -0
- package/src/__tests__/conversation-fork-crud.test.ts +69 -8
- package/src/__tests__/conversation-fork-route.test.ts +3 -2
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
- package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
- package/src/__tests__/conversation-lifecycle.test.ts +1 -0
- package/src/__tests__/conversation-list-source.test.ts +3 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
- package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
- package/src/__tests__/conversation-pairing.test.ts +53 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
- package/src/__tests__/conversation-queue.test.ts +333 -291
- package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
- package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
- package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
- package/src/__tests__/conversation-skill-tools.test.ts +38 -142
- package/src/__tests__/conversation-slash-queue.test.ts +84 -32
- package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
- package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
- package/src/__tests__/conversation-sync-tags.test.ts +128 -12
- package/src/__tests__/conversation-title-service.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
- package/src/__tests__/conversation-usage.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
- package/src/__tests__/credential-broker-server-use.test.ts +5 -5
- package/src/__tests__/credential-execution-client.test.ts +72 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
- package/src/__tests__/credential-health-service.test.ts +252 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -5
- package/src/__tests__/credential-vault-unit.test.ts +19 -19
- package/src/__tests__/credential-vault.test.ts +5 -5
- package/src/__tests__/cross-provider-web-search.test.ts +56 -2
- package/src/__tests__/db-connection-isolation.test.ts +7 -6
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
- package/src/__tests__/db-test-helpers.ts +58 -0
- package/src/__tests__/disk-pressure-guard.test.ts +58 -41
- package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
- package/src/__tests__/disk-pressure-routes.test.ts +0 -33
- package/src/__tests__/disk-pressure-tools.test.ts +0 -4
- package/src/__tests__/dm-persistence.test.ts +26 -40
- package/src/__tests__/document-create-dedupe.test.ts +189 -0
- package/src/__tests__/document-find-replace.test.ts +3 -2
- package/src/__tests__/document-tool-security.test.ts +81 -2
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
- package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
- package/src/__tests__/encrypted-store.test.ts +11 -9
- package/src/__tests__/feature-flag-test-helpers.ts +53 -0
- package/src/__tests__/filing-service.test.ts +1 -0
- package/src/__tests__/first-greeting.test.ts +62 -12
- package/src/__tests__/gateway-flag-listener.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +26 -0
- package/src/__tests__/guardian-action-sweep.test.ts +3 -2
- package/src/__tests__/guardian-outbound-http.test.ts +3 -2
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/helpers/mock-logger.ts +26 -0
- package/src/__tests__/host-bash-routes.test.ts +1 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
- package/src/__tests__/host-shell-tool.test.ts +5 -4
- package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
- package/src/__tests__/http-conversation-lineage.test.ts +3 -2
- package/src/__tests__/http-user-message-parity.test.ts +29 -7
- package/src/__tests__/identity-intro-cache.test.ts +133 -22
- package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
- package/src/__tests__/inference-profile-reaper.test.ts +3 -2
- package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
- package/src/__tests__/injector-disk-pressure.test.ts +3 -17
- package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
- package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
- package/src/__tests__/llm-context-normalization.test.ts +42 -0
- package/src/__tests__/llm-resolver.test.ts +331 -0
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
- package/src/__tests__/mcp-abort-signal.test.ts +14 -0
- package/src/__tests__/mcp-client-auth.test.ts +14 -0
- package/src/__tests__/messaging-send-tool.test.ts +1 -0
- package/src/__tests__/migration-import-from-url.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +18 -2
- package/src/__tests__/model-intents.test.ts +3 -3
- package/src/__tests__/native-web-search.test.ts +30 -2
- package/src/__tests__/notification-deep-link.test.ts +62 -0
- package/src/__tests__/oauth-commands-routes.test.ts +37 -0
- package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
- package/src/__tests__/oauth-store.test.ts +3 -2
- package/src/__tests__/onboarding-template-contract.test.ts +3 -2
- package/src/__tests__/openai-provider.test.ts +8 -9
- package/src/__tests__/openai-responses-provider.test.ts +70 -10
- package/src/__tests__/openrouter-provider-only.test.ts +27 -5
- package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
- package/src/__tests__/persistence-pipeline.test.ts +139 -1
- package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
- package/src/__tests__/plugin-bootstrap.test.ts +9 -11
- package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
- package/src/__tests__/process-message-background-slack.test.ts +21 -16
- package/src/__tests__/process-message-display-content.test.ts +19 -22
- package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
- package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
- package/src/__tests__/provider-registry-ollama.test.ts +45 -22
- package/src/__tests__/recording-handler.test.ts +1 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +82 -76
- package/src/__tests__/relay-server.test.ts +10 -10
- package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
- package/src/__tests__/schedule-store.test.ts +16 -1
- package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
- package/src/__tests__/secret-ingress-http.test.ts +5 -1
- package/src/__tests__/secure-keys.test.ts +3 -3
- package/src/__tests__/send-endpoint-busy.test.ts +81 -42
- package/src/__tests__/server-history-render.test.ts +4 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
- package/src/__tests__/skill-feature-flags.test.ts +14 -16
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
- package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
- package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
- package/src/__tests__/skill-tool-factory.test.ts +96 -95
- package/src/__tests__/slack-channel-config.test.ts +3 -3
- package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
- package/src/__tests__/subagent-disposal.test.ts +27 -8
- package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
- package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
- package/src/__tests__/subagent-manager-notify.test.ts +20 -8
- package/src/__tests__/subagent-notify-parent.test.ts +5 -4
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
- package/src/__tests__/subagent-tools.test.ts +2 -1
- package/src/__tests__/suggestion-routes.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +38 -0
- package/src/__tests__/test-preload-verifier.ts +68 -0
- package/src/__tests__/test-preload.ts +32 -39
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
- package/src/__tests__/tool-executor.test.ts +55 -10
- package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
- package/src/__tests__/twilio-routes.test.ts +3 -2
- package/src/__tests__/validate-input.test.ts +381 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
- package/src/__tests__/voice-session-bridge.test.ts +37 -28
- package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
- package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
- package/src/acp/session-manager.ts +5 -6
- package/src/agent/loop.ts +80 -0
- package/src/api/README.md +124 -2
- package/src/api/constants/call-sites.ts +27 -0
- package/src/api/events/assistant-outbound-attachment.ts +51 -0
- package/src/api/events/assistant-text-delta.ts +32 -0
- package/src/api/events/assistant-turn-start.ts +33 -0
- package/src/api/events/document-comment-created.ts +48 -0
- package/src/api/events/document-comment-deleted.ts +24 -0
- package/src/api/events/document-comment-reopened.ts +25 -0
- package/src/api/events/document-comment-resolved.ts +27 -0
- package/src/api/events/generation-cancelled.ts +24 -0
- package/src/api/events/generation-handoff.ts +41 -0
- package/src/api/events/message-complete.ts +42 -0
- package/src/api/events/open-url.ts +30 -0
- package/src/{events → api/events}/relationship-state-updated.ts +3 -3
- package/src/api/events/tool-use-start.ts +32 -0
- package/src/api/index.ts +128 -3
- package/src/api/responses/llm-context-response.ts +39 -0
- package/src/api/responses/llm-request-log-entry.ts +93 -0
- package/src/api/responses/memory-recall-log.ts +65 -0
- package/src/api/responses/memory-v2-activation-log.ts +78 -0
- package/src/background-wake/background-wake-routes.test.ts +687 -52
- package/src/background-wake/platform-client.test.ts +308 -0
- package/src/background-wake/platform-client.ts +167 -0
- package/src/background-wake/publisher.ts +91 -0
- package/src/background-wake/runtime-registry.ts +2 -2
- package/src/background-wake/wake-intent-hooks.test.ts +282 -0
- package/src/calls/guardian-dispatch.ts +1 -0
- package/src/calls/voice-session-bridge.ts +4 -4
- package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
- package/src/cli/commands/__tests__/notifications.test.ts +184 -40
- package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
- package/src/cli/commands/channels/index.ts +229 -0
- package/src/cli/commands/memory-v3-render.ts +147 -0
- package/src/cli/commands/memory-v3.ts +255 -4
- package/src/cli/commands/notifications.ts +365 -55
- package/src/cli/lib/open-browser.ts +7 -2
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +23 -42
- package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
- package/src/config/call-site-defaults.ts +1 -1
- package/src/config/feature-flag-cache.ts +86 -0
- package/src/config/feature-flag-registry.json +17 -17
- package/src/config/llm-context-resolution.ts +10 -1
- package/src/config/llm-resolver.ts +121 -15
- package/src/config/loader.ts +4 -5
- package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +90 -1
- package/src/config/schemas/memory-v2.ts +26 -0
- package/src/config/schemas/services.ts +6 -2
- package/src/config/seed-inference-profiles.ts +36 -16
- package/src/context/token-estimator.ts +10 -5
- package/src/credential-execution/executable-discovery.ts +40 -0
- package/src/credential-execution/process-manager.ts +6 -2
- package/src/credential-health/credential-health-service.ts +125 -40
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
- package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
- package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
- package/src/daemon/conversation-agent-loop.ts +132 -28
- package/src/daemon/conversation-error.ts +33 -5
- package/src/daemon/conversation-messaging.ts +84 -43
- package/src/daemon/conversation-process.ts +74 -37
- package/src/daemon/conversation-runtime-assembly.ts +29 -9
- package/src/daemon/conversation-skill-tools.ts +14 -30
- package/src/daemon/conversation-surfaces.ts +69 -34
- package/src/daemon/conversation-tool-setup.ts +33 -48
- package/src/daemon/conversation.ts +26 -46
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/daemon-skill-host.ts +9 -2
- package/src/daemon/disk-pressure-guard.ts +27 -29
- package/src/daemon/first-greeting.ts +31 -13
- package/src/daemon/handlers/shared.ts +6 -1
- package/src/daemon/lifecycle.ts +12 -12
- package/src/daemon/mcp-reload-service.ts +1 -1
- package/src/daemon/meet-manifest-loader.ts +10 -17
- package/src/daemon/message-types/conversations.ts +20 -22
- package/src/daemon/message-types/document-comments.ts +8 -44
- package/src/daemon/message-types/home.ts +2 -2
- package/src/daemon/message-types/integrations.ts +2 -7
- package/src/daemon/message-types/messages.ts +23 -38
- package/src/daemon/message-types/subagents.ts +6 -0
- package/src/daemon/process-message.ts +9 -9
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +16 -0
- package/src/daemon/switch-inference-profile-tool.ts +13 -3
- package/src/daemon/tool-setup-types.ts +0 -6
- package/src/daemon/wake-target-adapter.ts +10 -0
- package/src/documents/document-store.ts +38 -0
- package/src/export/__tests__/transcript-formatter.test.ts +1 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
- package/src/heartbeat/heartbeat-service.ts +63 -0
- package/src/home/__tests__/feed-writer.test.ts +161 -0
- package/src/home/__tests__/post-connect-feed.test.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +55 -59
- package/src/home/feed-writer.ts +146 -7
- package/src/home/suggested-prompts.ts +27 -145
- package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
- package/src/ipc/gateway-client.test.ts +4 -1
- package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
- package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
- package/src/ipc/skill-routes/memory.ts +4 -3
- package/src/ipc/skill-routes/registries.ts +28 -29
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
- package/src/memory/conversation-attention-store.ts +17 -3
- package/src/memory/conversation-crud.ts +352 -112
- package/src/memory/db-connection.ts +29 -19
- package/src/memory/db-init.ts +4 -0
- package/src/memory/db-singleton.ts +77 -0
- package/src/memory/delivery-channels.ts +82 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
- package/src/memory/graph/retriever.test.ts +3 -3
- package/src/memory/job-handlers/embedding.test.ts +3 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
- package/src/memory/jobs-worker.ts +12 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
- package/src/memory/llm-request-log-source-local.ts +24 -0
- package/src/memory/llm-request-log-source.ts +31 -0
- package/src/memory/llm-request-log-store.ts +188 -3
- package/src/memory/memory-v2-activation-log-store.ts +95 -1
- package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
- package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/schema/conversations.ts +9 -1
- package/src/memory/schema/inference.ts +0 -1
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
- package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
- package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
- package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
- package/src/memory/v2/harness/metrics.ts +5 -1
- package/src/memory/v2/harness/replay-input.ts +19 -3
- package/src/memory/v2/harness/runner.ts +6 -0
- package/src/memory/v2/harness/trace.ts +6 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
- package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
- package/src/memory/v3/__tests__/edges.test.ts +144 -1
- package/src/memory/v3/__tests__/filter.test.ts +48 -0
- package/src/memory/v3/__tests__/gate.test.ts +96 -33
- package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
- package/src/memory/v3/__tests__/loop.test.ts +250 -5
- package/src/memory/v3/__tests__/scouts.test.ts +49 -0
- package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
- package/src/memory/v3/__tests__/traversal.test.ts +39 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
- package/src/memory/v3/__tests__/validate.test.ts +32 -0
- package/src/memory/v3/coretrieval-seed.ts +240 -0
- package/src/memory/v3/edges.ts +58 -21
- package/src/memory/v3/filter.ts +27 -22
- package/src/memory/v3/gate.ts +51 -36
- package/src/memory/v3/index-composition.ts +18 -5
- package/src/memory/v3/loop.ts +65 -17
- package/src/memory/v3/scouts.ts +15 -4
- package/src/memory/v3/shadow-diff.ts +287 -0
- package/src/memory/v3/shadow-middleware.ts +44 -2
- package/src/memory/v3/traversal.ts +6 -1
- package/src/memory/v3/tree-walk.ts +6 -1
- package/src/memory/v3/validate.ts +56 -33
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
- package/src/notifications/adapters/slack.ts +45 -11
- package/src/notifications/broadcaster.ts +114 -63
- package/src/notifications/conversation-pairing.ts +23 -3
- package/src/notifications/decisions-store.ts +32 -1
- package/src/notifications/deliveries-store.ts +45 -0
- package/src/notifications/edit-notification.ts +201 -0
- package/src/notifications/emit-signal.ts +11 -1
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +37 -0
- package/src/oauth/byo-connection.test.ts +67 -3
- package/src/oauth/byo-connection.ts +32 -5
- package/src/oauth/connect-orchestrator.ts +9 -0
- package/src/oauth/connection-resolver.test.ts +76 -0
- package/src/oauth/connection-resolver.ts +49 -10
- package/src/oauth/manual-token-connection.ts +51 -3
- package/src/oauth/seed-providers.ts +3 -0
- package/src/permissions/approval-policy.test.ts +19 -5
- package/src/permissions/approval-policy.ts +14 -3
- package/src/permissions/checker.ts +21 -8
- package/src/platform/client.test.ts +24 -1
- package/src/platform/client.ts +8 -0
- package/src/platform/feature-gate.ts +15 -0
- package/src/plugins/defaults/injectors.ts +2 -8
- package/src/plugins/defaults/persistence.ts +25 -6
- package/src/plugins/types.ts +57 -13
- package/src/proactive-artifact/job.test.ts +1 -0
- package/src/prompts/__tests__/system-prompt.test.ts +4 -4
- package/src/prompts/system-prompt.ts +38 -40
- package/src/prompts/template-detection.ts +10 -4
- package/src/prompts/templates/BOOTSTRAP.md +7 -11
- package/src/prompts/templates/IDENTITY.md +0 -2
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
- package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
- package/src/providers/call-site-routing.ts +33 -9
- package/src/providers/connection-model-compat.ts +23 -0
- package/src/providers/connection-resolution.ts +39 -20
- package/src/providers/fireworks/client.ts +1 -0
- package/src/providers/gemini/client.ts +24 -3
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
- package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
- package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
- package/src/providers/inference/auth.ts +0 -8
- package/src/providers/inference/connections.ts +3 -66
- package/src/providers/inference/resolve-auth.ts +2 -3
- package/src/providers/model-catalog.ts +35 -1
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
- package/src/providers/openai/chat-completions-provider.ts +110 -12
- package/src/providers/openai/codex-models.ts +2 -0
- package/src/providers/openai/responses-provider.ts +53 -53
- package/src/providers/openrouter/client.ts +13 -8
- package/src/providers/provider-send-message.ts +18 -9
- package/src/providers/registry.ts +48 -8
- package/src/providers/retry.ts +16 -4
- package/src/providers/search-provider-catalog.ts +17 -9
- package/src/providers/types.ts +9 -0
- package/src/runtime/__tests__/agent-wake.test.ts +1 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
- package/src/runtime/access-request-helper.ts +1 -0
- package/src/runtime/auth/route-policy.ts +10 -0
- package/src/runtime/channel-readiness-service.ts +68 -0
- package/src/runtime/channel-reply-delivery.ts +23 -0
- package/src/runtime/channel-retry-sweep.ts +47 -14
- package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
- package/src/runtime/migrations/vbundle-builder.ts +3 -2
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
- package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
- package/src/runtime/routes/acp-routes-list.test.ts +3 -0
- package/src/runtime/routes/app-management-routes.ts +111 -4
- package/src/runtime/routes/background-wake-routes.ts +188 -20
- package/src/runtime/routes/btw-routes.ts +4 -4
- package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
- package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
- package/src/runtime/routes/conversation-list-routes.ts +147 -0
- package/src/runtime/routes/conversation-management-routes.ts +39 -14
- package/src/runtime/routes/conversation-query-routes.ts +60 -10
- package/src/runtime/routes/conversation-routes.ts +186 -140
- package/src/runtime/routes/conversations-import-routes.ts +19 -6
- package/src/runtime/routes/documents-routes.ts +10 -1
- package/src/runtime/routes/group-routes.ts +11 -0
- package/src/runtime/routes/home-feed-routes.ts +129 -0
- package/src/runtime/routes/identity-intro-cache.ts +61 -16
- package/src/runtime/routes/identity-routes.ts +30 -9
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
- package/src/runtime/routes/integrations/vercel.ts +15 -0
- package/src/runtime/routes/llm-context-normalization.ts +7 -2
- package/src/runtime/routes/memory-v3-routes.ts +160 -2
- package/src/runtime/routes/migration-routes.ts +20 -13
- package/src/runtime/routes/notification-routes.ts +63 -1
- package/src/runtime/routes/oauth-commands-routes.ts +6 -1
- package/src/runtime/routes/surface-action-routes.ts +1 -38
- package/src/runtime/routes/surface-content-routes.ts +12 -5
- package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
- package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
- package/src/runtime/slack-dm-text-delivery.ts +177 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -1
- package/src/runtime/tool-grant-request-helper.ts +1 -0
- package/src/schedule/schedule-store.ts +8 -1
- package/src/schedule/scheduler.ts +111 -15
- package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
- package/src/security/encrypted-store.ts +7 -16
- package/src/security/store-path-override.ts +61 -0
- package/src/signals/user-message.ts +5 -8
- package/src/skills/validate-input.ts +177 -0
- package/src/subagent/manager.ts +13 -13
- package/src/subagent/types.ts +6 -0
- package/src/tasks/tool-sanitizer.ts +2 -2
- package/src/tools/apps/definitions.ts +35 -21
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
- package/src/tools/computer-use/definitions.ts +268 -266
- package/src/tools/document/document-tool.ts +131 -8
- package/src/tools/execution-target.ts +2 -5
- package/src/tools/executor.ts +18 -55
- package/src/tools/host-filesystem/edit.test.ts +1 -0
- package/src/tools/host-filesystem/read.test.ts +1 -0
- package/src/tools/host-filesystem/transfer.test.ts +31 -6
- package/src/tools/host-filesystem/write.test.ts +1 -0
- package/src/tools/mcp/mcp-tool-factory.ts +0 -2
- package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
- package/src/tools/network/__tests__/web-search.test.ts +211 -3
- package/src/tools/network/managed-search-proxy.ts +183 -0
- package/src/tools/network/web-search.ts +199 -44
- package/src/tools/policy-context.ts +3 -1
- package/src/tools/registry.ts +146 -103
- package/src/tools/schedule/create.ts +1 -1
- package/src/tools/skills/skill-tool-factory.ts +17 -36
- package/src/tools/subagent/spawn.ts +3 -0
- package/src/tools/tool-approval-handler.ts +10 -4
- package/src/tools/tool-name-aliases.ts +72 -14
- package/src/tools/types.ts +17 -15
- package/src/tools/ui-surface/definitions.ts +98 -86
- package/src/types/onboarding-context.ts +6 -0
- package/src/usage/attribution.ts +32 -1
- package/src/util/browser.ts +7 -2
- package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
- package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
- package/src/workspace/migrations/registry.ts +4 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit an already-sent notification.
|
|
3
|
+
*
|
|
4
|
+
* Patches the home-feed entry the user actually sees, then attempts a
|
|
5
|
+
* best-effort update of any per-channel deliveries that support
|
|
6
|
+
* in-place edits (Slack via chat.update today). Feed-only fields
|
|
7
|
+
* (`urgency`, `status`) skip the channel hop — channel messages don't
|
|
8
|
+
* carry that metadata, only body/title.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
type FeedItem,
|
|
13
|
+
type FeedItemStatus,
|
|
14
|
+
type FeedItemUrgency,
|
|
15
|
+
} from "../home/feed-types.js";
|
|
16
|
+
import { patchFeedItemContent } from "../home/feed-writer.js";
|
|
17
|
+
import { getLogger } from "../util/logger.js";
|
|
18
|
+
import { findLatestDecisionByEventId } from "./decisions-store.js";
|
|
19
|
+
import {
|
|
20
|
+
findDeliveriesByDecisionId,
|
|
21
|
+
type NotificationDeliveryRow,
|
|
22
|
+
updateDeliveryRenderedCopy,
|
|
23
|
+
} from "./deliveries-store.js";
|
|
24
|
+
import { getBroadcaster } from "./emit-signal.js";
|
|
25
|
+
import type { NotificationChannel } from "./types.js";
|
|
26
|
+
|
|
27
|
+
const log = getLogger("edit-notification");
|
|
28
|
+
|
|
29
|
+
/** Prefix used by `home-feed-side-effect` when minting feed item ids. */
|
|
30
|
+
export const FEED_ITEM_ID_PREFIX = "notif:";
|
|
31
|
+
|
|
32
|
+
export interface EditNotificationParams {
|
|
33
|
+
/** Feed item id (`notif:<uuid>`) or bare signal uuid. */
|
|
34
|
+
id: string;
|
|
35
|
+
title?: string;
|
|
36
|
+
body?: string;
|
|
37
|
+
urgency?: FeedItemUrgency;
|
|
38
|
+
status?: FeedItemStatus;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type ChannelEditOutcome =
|
|
42
|
+
| "updated"
|
|
43
|
+
| "unsupported"
|
|
44
|
+
| "skipped"
|
|
45
|
+
| "failed";
|
|
46
|
+
|
|
47
|
+
export interface ChannelEditResult {
|
|
48
|
+
channel: NotificationChannel;
|
|
49
|
+
deliveryId: string;
|
|
50
|
+
outcome: ChannelEditOutcome;
|
|
51
|
+
/** Reason for skip/failure when `outcome` is not `"updated"`. */
|
|
52
|
+
reason?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface EditNotificationResult {
|
|
56
|
+
feedItem: FeedItem;
|
|
57
|
+
channels: ChannelEditResult[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Normalize a user-supplied id into the canonical feed-item form
|
|
62
|
+
* (`notif:<uuid>`). Accepts either the full prefixed id or a bare uuid.
|
|
63
|
+
*/
|
|
64
|
+
export function normalizeFeedItemId(id: string): string {
|
|
65
|
+
const trimmed = id.trim();
|
|
66
|
+
if (trimmed.startsWith(FEED_ITEM_ID_PREFIX)) return trimmed;
|
|
67
|
+
return `${FEED_ITEM_ID_PREFIX}${trimmed}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Strip the `notif:` prefix to recover the original signal/event id. */
|
|
71
|
+
export function feedItemIdToSignalId(feedItemId: string): string {
|
|
72
|
+
return feedItemId.startsWith(FEED_ITEM_ID_PREFIX)
|
|
73
|
+
? feedItemId.slice(FEED_ITEM_ID_PREFIX.length)
|
|
74
|
+
: feedItemId;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Apply an edit to a previously-sent notification.
|
|
79
|
+
*
|
|
80
|
+
* Returns the updated feed item plus per-channel update outcomes.
|
|
81
|
+
* Resolves to `null` when the feed item id isn't on disk so callers
|
|
82
|
+
* can surface a clear "not found" to the user.
|
|
83
|
+
*/
|
|
84
|
+
export async function editNotification(
|
|
85
|
+
params: EditNotificationParams,
|
|
86
|
+
): Promise<EditNotificationResult | null> {
|
|
87
|
+
const feedItemId = normalizeFeedItemId(params.id);
|
|
88
|
+
|
|
89
|
+
const feedItem = await patchFeedItemContent(feedItemId, {
|
|
90
|
+
title: params.title,
|
|
91
|
+
summary: params.body,
|
|
92
|
+
urgency: params.urgency,
|
|
93
|
+
status: params.status,
|
|
94
|
+
});
|
|
95
|
+
if (!feedItem) {
|
|
96
|
+
log.warn({ feedItemId }, "Edit requested for unknown feed item");
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Only edit channel messages when the user-visible text changed.
|
|
101
|
+
// Urgency/status are feed-only — pushing a channel update for those
|
|
102
|
+
// alone would re-deliver the same body and confuse the recipient.
|
|
103
|
+
const shouldUpdateChannels =
|
|
104
|
+
params.title !== undefined || params.body !== undefined;
|
|
105
|
+
if (!shouldUpdateChannels) {
|
|
106
|
+
return { feedItem, channels: [] };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const signalId = feedItemIdToSignalId(feedItemId);
|
|
110
|
+
const decision = findLatestDecisionByEventId(signalId);
|
|
111
|
+
if (!decision) {
|
|
112
|
+
log.info(
|
|
113
|
+
{ feedItemId, signalId },
|
|
114
|
+
"Feed item has no persisted decision — skipping channel updates",
|
|
115
|
+
);
|
|
116
|
+
return { feedItem, channels: [] };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const deliveries = findDeliveriesByDecisionId(decision.id);
|
|
120
|
+
const channels = await updateChannelDeliveries(deliveries, {
|
|
121
|
+
title: params.title,
|
|
122
|
+
body: params.body,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return { feedItem, channels };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function updateChannelDeliveries(
|
|
129
|
+
deliveries: NotificationDeliveryRow[],
|
|
130
|
+
patch: { title?: string; body?: string },
|
|
131
|
+
): Promise<ChannelEditResult[]> {
|
|
132
|
+
const broadcaster = getBroadcaster();
|
|
133
|
+
const results: ChannelEditResult[] = [];
|
|
134
|
+
|
|
135
|
+
for (const delivery of deliveries) {
|
|
136
|
+
const channel = delivery.channel as NotificationChannel;
|
|
137
|
+
if (delivery.status !== "sent") {
|
|
138
|
+
results.push({
|
|
139
|
+
channel,
|
|
140
|
+
deliveryId: delivery.id,
|
|
141
|
+
outcome: "skipped",
|
|
142
|
+
reason: `delivery status is ${delivery.status}`,
|
|
143
|
+
});
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const adapter = broadcaster.getAdapter(channel);
|
|
148
|
+
if (!adapter?.update) {
|
|
149
|
+
results.push({
|
|
150
|
+
channel,
|
|
151
|
+
deliveryId: delivery.id,
|
|
152
|
+
outcome: "unsupported",
|
|
153
|
+
reason: `${channel} adapter does not support in-place edits`,
|
|
154
|
+
});
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const result = await adapter.update(
|
|
160
|
+
{
|
|
161
|
+
deliveryId: delivery.id,
|
|
162
|
+
destination: delivery.destination,
|
|
163
|
+
messageId: delivery.messageId,
|
|
164
|
+
},
|
|
165
|
+
patch,
|
|
166
|
+
);
|
|
167
|
+
if (!result.success) {
|
|
168
|
+
results.push({
|
|
169
|
+
channel,
|
|
170
|
+
deliveryId: delivery.id,
|
|
171
|
+
outcome: "failed",
|
|
172
|
+
reason: result.error ?? "unknown error",
|
|
173
|
+
});
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
updateDeliveryRenderedCopy(delivery.id, {
|
|
177
|
+
renderedTitle: patch.title,
|
|
178
|
+
renderedBody: patch.body,
|
|
179
|
+
});
|
|
180
|
+
results.push({
|
|
181
|
+
channel,
|
|
182
|
+
deliveryId: delivery.id,
|
|
183
|
+
outcome: "updated",
|
|
184
|
+
});
|
|
185
|
+
} catch (err) {
|
|
186
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
187
|
+
log.error(
|
|
188
|
+
{ err, channel, deliveryId: delivery.id },
|
|
189
|
+
"Channel adapter update threw",
|
|
190
|
+
);
|
|
191
|
+
results.push({
|
|
192
|
+
channel,
|
|
193
|
+
deliveryId: delivery.id,
|
|
194
|
+
outcome: "failed",
|
|
195
|
+
reason: message,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return results;
|
|
201
|
+
}
|
|
@@ -51,7 +51,7 @@ const log = getLogger("emit-signal");
|
|
|
51
51
|
|
|
52
52
|
let broadcasterInstance: NotificationBroadcaster | null = null;
|
|
53
53
|
|
|
54
|
-
function getBroadcaster(): NotificationBroadcaster {
|
|
54
|
+
export function getBroadcaster(): NotificationBroadcaster {
|
|
55
55
|
if (!broadcasterInstance) {
|
|
56
56
|
broadcasterInstance = new NotificationBroadcaster([
|
|
57
57
|
new VellumAdapter(broadcastMessage),
|
|
@@ -188,6 +188,15 @@ export interface EmitSignalParams<TEventName extends string = string> {
|
|
|
188
188
|
source?: string;
|
|
189
189
|
conversationType?: ConversationCreateType;
|
|
190
190
|
};
|
|
191
|
+
/**
|
|
192
|
+
* When true, the vellum-channel delivery materializes a fresh conversation
|
|
193
|
+
* to host the notification (and any follow-up interaction). Set this only
|
|
194
|
+
* for flows where the conversation IS the interaction surface — e.g.
|
|
195
|
+
* guardian.question, tool grant requests, ingress access requests. Passive
|
|
196
|
+
* notifications leave this unset; they surface via the home feed and link
|
|
197
|
+
* back to their originating conversation via `sourceContextId`.
|
|
198
|
+
*/
|
|
199
|
+
requiresConversation?: boolean;
|
|
191
200
|
}
|
|
192
201
|
|
|
193
202
|
export interface EmitSignalResult {
|
|
@@ -223,6 +232,7 @@ export async function emitNotificationSignal<TEventName extends string>(
|
|
|
223
232
|
routingHints: params.routingHints,
|
|
224
233
|
conversationAffinityHint: params.conversationAffinityHint,
|
|
225
234
|
conversationMetadata: params.conversationMetadata,
|
|
235
|
+
requiresConversation: params.requiresConversation,
|
|
226
236
|
};
|
|
227
237
|
|
|
228
238
|
try {
|
|
@@ -213,4 +213,14 @@ export interface NotificationSignal<TEventName extends string = string> {
|
|
|
213
213
|
source?: string;
|
|
214
214
|
conversationType?: ConversationCreateType;
|
|
215
215
|
};
|
|
216
|
+
/**
|
|
217
|
+
* When true, the vellum-channel delivery materializes a fresh conversation
|
|
218
|
+
* to host the notification (and any follow-up interaction). Set this only
|
|
219
|
+
* for flows where the conversation IS the interaction surface — e.g.
|
|
220
|
+
* guardian.question, tool grant requests, ingress access requests. Passive
|
|
221
|
+
* notifications (scheduler fired, watcher alert, activity complete, etc.)
|
|
222
|
+
* should leave this unset; they surface via the home feed and link back
|
|
223
|
+
* to their originating conversation via `sourceContextId`.
|
|
224
|
+
*/
|
|
225
|
+
requiresConversation?: boolean;
|
|
216
226
|
}
|
|
@@ -45,6 +45,12 @@ export interface NotificationDeliveryResult {
|
|
|
45
45
|
export interface DeliveryResult {
|
|
46
46
|
success: boolean;
|
|
47
47
|
error?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Channel-native message identifier captured at send time (e.g. Slack `ts`).
|
|
50
|
+
* Persisted onto `notification_deliveries.messageId` so later edits can
|
|
51
|
+
* target the same message via `ChannelAdapter.update()`.
|
|
52
|
+
*/
|
|
53
|
+
messageId?: string;
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
/** Resolved destination for a specific channel. */
|
|
@@ -91,6 +97,16 @@ export interface ChannelDeliveryPayload {
|
|
|
91
97
|
urgency: AttentionHints["urgency"];
|
|
92
98
|
}
|
|
93
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Patch supplied when an already-delivered notification is edited.
|
|
102
|
+
* Adapters only need to act on `title` and `body` — feed-only fields
|
|
103
|
+
* like `urgency` and `status` are handled by the home-feed patch.
|
|
104
|
+
*/
|
|
105
|
+
export interface ChannelUpdatePayload {
|
|
106
|
+
title?: string;
|
|
107
|
+
body?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
94
110
|
/** Interface that each channel adapter must implement. */
|
|
95
111
|
export interface ChannelAdapter {
|
|
96
112
|
channel: NotificationChannel;
|
|
@@ -98,6 +114,27 @@ export interface ChannelAdapter {
|
|
|
98
114
|
payload: ChannelDeliveryPayload,
|
|
99
115
|
destination: ChannelDestination,
|
|
100
116
|
): Promise<DeliveryResult>;
|
|
117
|
+
/**
|
|
118
|
+
* Optional: edit a previously-sent message in place. Channels that
|
|
119
|
+
* cannot edit (push, email, SMS) omit this and the router treats
|
|
120
|
+
* them as `"unsupported"`. Adapters that implement it use
|
|
121
|
+
* `delivery.messageId` (captured at send time) as the channel-native
|
|
122
|
+
* handle for the in-place update.
|
|
123
|
+
*/
|
|
124
|
+
update?(
|
|
125
|
+
delivery: ChannelUpdateContext,
|
|
126
|
+
patch: ChannelUpdatePayload,
|
|
127
|
+
): Promise<DeliveryResult>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Per-delivery state an adapter needs to update a previously-sent message. */
|
|
131
|
+
export interface ChannelUpdateContext {
|
|
132
|
+
/** notification_deliveries.id */
|
|
133
|
+
deliveryId: string;
|
|
134
|
+
/** Channel-native destination (e.g. Slack chat id). */
|
|
135
|
+
destination: string;
|
|
136
|
+
/** Channel-native message identifier captured at send time. */
|
|
137
|
+
messageId: string | null;
|
|
101
138
|
}
|
|
102
139
|
|
|
103
140
|
// -- Decision engine output ---------------------------------------------------
|
|
@@ -28,7 +28,7 @@ mock.module("../util/logger.js", () => ({
|
|
|
28
28
|
// Use encrypted backend with a temp store path
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
30
|
|
|
31
|
-
import {
|
|
31
|
+
import { setStorePathForTesting } from "../__tests__/encrypted-store-test-helpers.js";
|
|
32
32
|
import { _resetBackend } from "../security/secure-keys.js";
|
|
33
33
|
|
|
34
34
|
const TEST_DIR = join(
|
|
@@ -130,6 +130,7 @@ mock.module("./oauth-store.js", () => ({
|
|
|
130
130
|
// Imports (after mocks)
|
|
131
131
|
// ---------------------------------------------------------------------------
|
|
132
132
|
|
|
133
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
133
134
|
import { setSecureKeyAsync } from "../security/secure-keys.js";
|
|
134
135
|
import {
|
|
135
136
|
_resetInflightRefreshes,
|
|
@@ -163,7 +164,7 @@ afterAll(() => {
|
|
|
163
164
|
});
|
|
164
165
|
|
|
165
166
|
beforeEach(() => {
|
|
166
|
-
|
|
167
|
+
setStorePathForTesting(STORE_PATH);
|
|
167
168
|
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
168
169
|
_resetBackend();
|
|
169
170
|
_resetRefreshBreakers();
|
|
@@ -251,11 +252,22 @@ function createConnection(service = "google"): BYOOAuthConnection {
|
|
|
251
252
|
return new BYOOAuthConnection({
|
|
252
253
|
id: `conn-${service}`,
|
|
253
254
|
provider: service,
|
|
254
|
-
baseUrl:
|
|
255
|
+
baseUrl:
|
|
256
|
+
service === "telegram"
|
|
257
|
+
? "https://api.telegram.org"
|
|
258
|
+
: "https://gmail.googleapis.com/gmail/v1/users/me",
|
|
255
259
|
accountInfo: null,
|
|
256
260
|
});
|
|
257
261
|
}
|
|
258
262
|
|
|
263
|
+
async function setupTelegramCredential() {
|
|
264
|
+
await setupCredential("telegram");
|
|
265
|
+
await setSecureKeyAsync(
|
|
266
|
+
credentialKey("telegram", "bot_token"),
|
|
267
|
+
"telegram-test-token",
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
259
271
|
// ---------------------------------------------------------------------------
|
|
260
272
|
// Tests
|
|
261
273
|
// ---------------------------------------------------------------------------
|
|
@@ -437,6 +449,58 @@ describe("BYOOAuthConnection", () => {
|
|
|
437
449
|
expect(headers.get("X-Custom-Header")).toBe("custom-value");
|
|
438
450
|
expect(headers.get("Authorization")).toBe("Bearer test-access-token");
|
|
439
451
|
});
|
|
452
|
+
|
|
453
|
+
test("uses Telegram Bot API token URL format without Bearer auth", async () => {
|
|
454
|
+
await setupTelegramCredential();
|
|
455
|
+
const conn = createConnection("telegram");
|
|
456
|
+
|
|
457
|
+
const result = await conn.request({
|
|
458
|
+
method: "GET",
|
|
459
|
+
path: "/getMe",
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
expect(result.status).toBe(200);
|
|
463
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
464
|
+
|
|
465
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
466
|
+
expect(url).toBe("https://api.telegram.org/bottelegram-test-token/getMe");
|
|
467
|
+
const headers = (init as RequestInit).headers as Headers;
|
|
468
|
+
expect(headers.has("Authorization")).toBe(false);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test("does not double-prefix Telegram paths that already include bot token", async () => {
|
|
472
|
+
await setupTelegramCredential();
|
|
473
|
+
const conn = createConnection("telegram");
|
|
474
|
+
|
|
475
|
+
await conn.request({
|
|
476
|
+
method: "GET",
|
|
477
|
+
path: "/bottelegram-test-token/getMe",
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
481
|
+
expect(url).toBe("https://api.telegram.org/bottelegram-test-token/getMe");
|
|
482
|
+
const headers = (init as RequestInit).headers as Headers;
|
|
483
|
+
expect(headers.has("Authorization")).toBe(false);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test("returns Telegram 401 responses without OAuth refresh", async () => {
|
|
487
|
+
await setupTelegramCredential();
|
|
488
|
+
const conn = createConnection("telegram");
|
|
489
|
+
const refreshCallsBefore = mockRefreshOAuth2Token.mock.calls.length;
|
|
490
|
+
|
|
491
|
+
globalThis.fetch = mock(() =>
|
|
492
|
+
Promise.resolve(new Response("Unauthorized", { status: 401 })),
|
|
493
|
+
) as unknown as typeof fetch;
|
|
494
|
+
|
|
495
|
+
const result = await conn.request({
|
|
496
|
+
method: "GET",
|
|
497
|
+
path: "/getMe",
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
expect(result.status).toBe(401);
|
|
501
|
+
expect(result.body).toBe("Unauthorized");
|
|
502
|
+
expect(mockRefreshOAuth2Token.mock.calls.length).toBe(refreshCallsBefore);
|
|
503
|
+
});
|
|
440
504
|
});
|
|
441
505
|
|
|
442
506
|
describe("proactive token refresh", () => {
|
|
@@ -46,7 +46,11 @@ export class BYOOAuthConnection implements OAuthConnection {
|
|
|
46
46
|
this.provider,
|
|
47
47
|
async (token) => {
|
|
48
48
|
const effectiveBaseUrl = req.baseUrl ?? this.baseUrl;
|
|
49
|
-
|
|
49
|
+
const isTelegram = this.provider === "telegram";
|
|
50
|
+
const requestPath = isTelegram
|
|
51
|
+
? buildTelegramBotApiPath(req.path, token)
|
|
52
|
+
: req.path;
|
|
53
|
+
let fullUrl = `${effectiveBaseUrl}${requestPath}`;
|
|
50
54
|
|
|
51
55
|
if (req.query && Object.keys(req.query).length > 0) {
|
|
52
56
|
const params = new URLSearchParams();
|
|
@@ -60,8 +64,12 @@ export class BYOOAuthConnection implements OAuthConnection {
|
|
|
60
64
|
fullUrl += `?${params.toString()}`;
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
const logUrl = isTelegram
|
|
68
|
+
? redactTelegramBotTokenFromUrl(fullUrl, token)
|
|
69
|
+
: fullUrl;
|
|
70
|
+
|
|
63
71
|
log.debug(
|
|
64
|
-
{ method: req.method, url:
|
|
72
|
+
{ method: req.method, url: logUrl, provider: this.provider },
|
|
65
73
|
"Making authenticated request",
|
|
66
74
|
);
|
|
67
75
|
|
|
@@ -76,18 +84,23 @@ export class BYOOAuthConnection implements OAuthConnection {
|
|
|
76
84
|
headers.set(key, value);
|
|
77
85
|
}
|
|
78
86
|
}
|
|
79
|
-
|
|
87
|
+
if (!isTelegram) {
|
|
88
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
89
|
+
}
|
|
80
90
|
|
|
81
91
|
const resp = await fetch(fullUrl, {
|
|
82
92
|
method: req.method,
|
|
83
93
|
headers,
|
|
84
94
|
body: req.body ? JSON.stringify(req.body) : undefined,
|
|
85
95
|
signal: req.signal
|
|
86
|
-
? AbortSignal.any([
|
|
96
|
+
? AbortSignal.any([
|
|
97
|
+
req.signal,
|
|
98
|
+
AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
99
|
+
])
|
|
87
100
|
: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
88
101
|
});
|
|
89
102
|
|
|
90
|
-
if (resp.status === 401) {
|
|
103
|
+
if (resp.status === 401 && !isTelegram) {
|
|
91
104
|
// Throw with a status property so withValidToken detects the 401
|
|
92
105
|
// and triggers its refresh-and-retry logic.
|
|
93
106
|
const err = new Error(`HTTP 401 from ${this.provider}`);
|
|
@@ -108,6 +121,20 @@ export class BYOOAuthConnection implements OAuthConnection {
|
|
|
108
121
|
}
|
|
109
122
|
}
|
|
110
123
|
|
|
124
|
+
function buildTelegramBotApiPath(path: string, token: string): string {
|
|
125
|
+
if (path.startsWith("/bot")) return path;
|
|
126
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
127
|
+
return `/bot${token}${normalizedPath}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function redactTelegramBotTokenFromUrl(url: string, token: string): string {
|
|
131
|
+
const redactedStoredToken = url.split(token).join("[REDACTED]");
|
|
132
|
+
return redactedStoredToken.replace(
|
|
133
|
+
/\/bot[^/?#]+(?=\/|[?#]|$)/g,
|
|
134
|
+
"/bot[REDACTED]",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
111
138
|
async function buildResponse(resp: Response): Promise<OAuthConnectionResponse> {
|
|
112
139
|
const headers: Record<string, string> = {};
|
|
113
140
|
resp.headers.forEach((value, key) => {
|
|
@@ -64,6 +64,12 @@ export interface OAuthConnectOptions {
|
|
|
64
64
|
openUrl?: (url: string) => void;
|
|
65
65
|
/** Send a message to the client (e.g. open_url). */
|
|
66
66
|
sendToClient?: (msg: { type: string; [key: string]: unknown }) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Conversation this connect flow belongs to. When set and `sendToClient`
|
|
69
|
+
* is used, the emitted `open_url` event carries it on the inner message
|
|
70
|
+
* so downstream consumers can route per-conversation.
|
|
71
|
+
*/
|
|
72
|
+
conversationId?: string;
|
|
67
73
|
|
|
68
74
|
/**
|
|
69
75
|
* Callback transport to use for the OAuth redirect.
|
|
@@ -331,6 +337,9 @@ export async function orchestrateOAuthConnect(
|
|
|
331
337
|
type: "open_url",
|
|
332
338
|
url,
|
|
333
339
|
title: `Connect ${options.service}`,
|
|
340
|
+
...(options.conversationId
|
|
341
|
+
? { conversationId: options.conversationId }
|
|
342
|
+
: {}),
|
|
334
343
|
});
|
|
335
344
|
} else {
|
|
336
345
|
log.warn(
|
|
@@ -9,6 +9,7 @@ let mockConnection: Record<string, unknown> | undefined;
|
|
|
9
9
|
let mockAccessToken: string | undefined;
|
|
10
10
|
let mockConfig: Record<string, unknown> = {};
|
|
11
11
|
let mockPlatformClient: Record<string, unknown> | null = null;
|
|
12
|
+
let syncManualTokenCalls: string[] = [];
|
|
12
13
|
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
// Module mocks (must precede imports of the module under test)
|
|
@@ -47,6 +48,15 @@ mock.module("./credential-token-resolver.js", () => ({
|
|
|
47
48
|
}),
|
|
48
49
|
}));
|
|
49
50
|
|
|
51
|
+
mock.module("./manual-token-connection.js", () => ({
|
|
52
|
+
syncManualTokenConnection: async (provider: string) => {
|
|
53
|
+
syncManualTokenCalls.push(provider);
|
|
54
|
+
if (provider === "telegram" && mockConnection?.provider === "telegram") {
|
|
55
|
+
mockConnection.accountInfo = "@example_bot";
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
}));
|
|
59
|
+
|
|
50
60
|
mock.module("../config/loader.js", () => ({
|
|
51
61
|
getConfig: () => mockConfig,
|
|
52
62
|
}));
|
|
@@ -92,6 +102,7 @@ function setupDefaults(): void {
|
|
|
92
102
|
mockProvider = {
|
|
93
103
|
provider: "google",
|
|
94
104
|
baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
|
|
105
|
+
authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
95
106
|
managedServiceConfigKey: null,
|
|
96
107
|
};
|
|
97
108
|
mockConnection = {
|
|
@@ -121,6 +132,7 @@ function setupDefaults(): void {
|
|
|
121
132
|
},
|
|
122
133
|
};
|
|
123
134
|
mockPlatformClient = makeMockClient();
|
|
135
|
+
syncManualTokenCalls = [];
|
|
124
136
|
}
|
|
125
137
|
|
|
126
138
|
// ---------------------------------------------------------------------------
|
|
@@ -159,6 +171,42 @@ describe("resolveOAuthConnection", () => {
|
|
|
159
171
|
expect(result.accountInfo).toBe("user@example.com");
|
|
160
172
|
});
|
|
161
173
|
|
|
174
|
+
test("managed path falls back to displayed account label when account_identifier does not match", async () => {
|
|
175
|
+
mockProvider!.managedServiceConfigKey = "google-oauth";
|
|
176
|
+
const fetchPaths: string[] = [];
|
|
177
|
+
mockPlatformClient = {
|
|
178
|
+
...makeMockClient(),
|
|
179
|
+
fetch: mock(async (path: string) => {
|
|
180
|
+
fetchPaths.push(path);
|
|
181
|
+
if (path.includes("account_identifier=alice%40example.com")) {
|
|
182
|
+
return new Response(JSON.stringify({ results: [] }), { status: 200 });
|
|
183
|
+
}
|
|
184
|
+
return new Response(
|
|
185
|
+
JSON.stringify({
|
|
186
|
+
results: [
|
|
187
|
+
{
|
|
188
|
+
id: "platform-conn-1",
|
|
189
|
+
account_label: "alice@example.com",
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
}),
|
|
193
|
+
{ status: 200 },
|
|
194
|
+
);
|
|
195
|
+
}),
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const result = await resolveOAuthConnection("google", {
|
|
199
|
+
account: "alice@example.com",
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
expect(result).toBeInstanceOf(PlatformOAuthConnection);
|
|
203
|
+
expect(result.accountInfo).toBe("alice@example.com");
|
|
204
|
+
expect(fetchPaths).toEqual([
|
|
205
|
+
"/v1/assistants/asst-123/oauth/connections/?provider=google&status=ACTIVE&account_identifier=alice%40example.com",
|
|
206
|
+
"/v1/assistants/asst-123/oauth/connections/?provider=google&status=ACTIVE",
|
|
207
|
+
]);
|
|
208
|
+
});
|
|
209
|
+
|
|
162
210
|
test("returns PlatformOAuthConnection when GitHub is in managed mode", async () => {
|
|
163
211
|
mockProvider!.provider = "github";
|
|
164
212
|
mockProvider!.managedServiceConfigKey = "github-oauth";
|
|
@@ -216,6 +264,34 @@ describe("resolveOAuthConnection", () => {
|
|
|
216
264
|
}),
|
|
217
265
|
).rejects.toThrow(/No active OAuth connection found/);
|
|
218
266
|
});
|
|
267
|
+
|
|
268
|
+
test("BYO path reconciles manual-token providers before exact account lookup", async () => {
|
|
269
|
+
mockProvider = {
|
|
270
|
+
provider: "telegram",
|
|
271
|
+
baseUrl: "https://api.telegram.org",
|
|
272
|
+
authorizeUrl: "urn:manual-token",
|
|
273
|
+
managedServiceConfigKey: null,
|
|
274
|
+
};
|
|
275
|
+
mockConnection = {
|
|
276
|
+
id: "conn-telegram",
|
|
277
|
+
provider: "telegram",
|
|
278
|
+
oauthAppId: "app-telegram",
|
|
279
|
+
accountInfo: null,
|
|
280
|
+
grantedScopes: JSON.stringify([]),
|
|
281
|
+
status: "active",
|
|
282
|
+
clientId: "manual-config",
|
|
283
|
+
};
|
|
284
|
+
mockAccessToken = "telegram-test-token";
|
|
285
|
+
|
|
286
|
+
const result = await resolveOAuthConnection("telegram", {
|
|
287
|
+
account: "@example_bot",
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
expect(syncManualTokenCalls).toEqual(["telegram"]);
|
|
291
|
+
expect(mockConnection.accountInfo).toBe("@example_bot");
|
|
292
|
+
expect(result).toBeInstanceOf(BYOOAuthConnection);
|
|
293
|
+
expect(result.id).toBe("conn-telegram");
|
|
294
|
+
});
|
|
219
295
|
});
|
|
220
296
|
|
|
221
297
|
describe("resolveEffectiveBaseUrl", () => {
|