@vellumai/assistant 0.12.1 → 0.12.2-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/ARCHITECTURE.md +20 -21
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +13 -8
- package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
- package/openapi.yaml +360 -51
- package/package.json +3 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/anthropic-provider.test.ts +0 -63
- package/src/__tests__/app-compiler.test.ts +6 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
- package/src/__tests__/app-dir-path-guard.test.ts +1 -3
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
- package/src/__tests__/assistant-event-hub.test.ts +44 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +19 -7
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +28 -29
- package/src/__tests__/config-watcher.test.ts +23 -8
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +26 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
- package/src/__tests__/conversation-queue.test.ts +48 -207
- package/src/__tests__/conversation-retry-discard.test.ts +54 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +28 -0
- package/src/__tests__/conversation-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-title-service.test.ts +14 -10
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
- package/src/__tests__/credential-record-retire.test.ts +171 -1
- package/src/__tests__/credential-routes.test.ts +250 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/cu-unified-flow.test.ts +14 -4
- package/src/__tests__/document-sync-tags.test.ts +25 -0
- package/src/__tests__/drain-kick-guard.test.ts +0 -2
- package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/git-porcelain.test.ts +47 -0
- package/src/__tests__/heartbeat-service.test.ts +56 -40
- package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
- package/src/__tests__/host-bash-proxy.test.ts +26 -45
- package/src/__tests__/host-browser-proxy.test.ts +6 -17
- package/src/__tests__/host-cu-proxy.test.ts +596 -0
- package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
- package/src/__tests__/host-file-proxy.test.ts +11 -23
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
- package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
- package/src/__tests__/llm-catalog-parity.test.ts +25 -0
- package/src/__tests__/mcp-auth-routes.test.ts +6 -9
- package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
- package/src/__tests__/mcp-health-check.test.ts +8 -11
- package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
- package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +355 -548
- package/src/__tests__/model-intents.test.ts +3 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
- package/src/__tests__/oauth-commands-routes.test.ts +168 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/openai-provider.test.ts +1 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-callback-registration.test.ts +8 -16
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
- package/src/__tests__/qdrant-manager.test.ts +132 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
- package/src/__tests__/service-account-token.test.ts +247 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +70 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/ui-channel-variants.test.ts +1 -56
- package/src/__tests__/unicode.test.ts +13 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
- package/src/__tests__/workspace-git-service.test.ts +26 -0
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
- package/src/acp/__tests__/acp-credentials.test.ts +13 -0
- package/src/acp/__tests__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -0
- package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
- package/src/acp/__tests__/client-handler.test.ts +90 -2
- package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
- package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
- package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
- package/src/acp/__tests__/session-manager.test.ts +895 -4
- package/src/acp/acp-claude-oauth.ts +328 -14
- package/src/acp/acp-credentials.ts +19 -0
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +96 -10
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/claude-token-refresh.ts +150 -0
- package/src/acp/client-handler.ts +16 -3
- package/src/acp/model-config.test.ts +166 -0
- package/src/acp/model-config.ts +138 -0
- package/src/acp/prepare-agent-env.ts +21 -6
- package/src/acp/resolve-agent.test.ts +220 -6
- package/src/acp/resolve-agent.ts +88 -23
- package/src/acp/session-manager.test.ts +196 -3
- package/src/acp/session-manager.ts +441 -18
- package/src/acp/types.ts +50 -5
- package/src/agent/loop.ts +1 -0
- package/src/api/events/acp-session-model-update.test.ts +88 -0
- package/src/api/events/acp-session-model-update.ts +46 -0
- package/src/api/events/app-files-changed.ts +4 -3
- package/src/api/events/notification-intent.ts +2 -0
- package/src/api/events/user-message-echo.ts +7 -1
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -0
- package/src/apps/app-store.ts +1 -1
- package/src/apps/resolve-app.ts +81 -0
- package/src/apps/source-fingerprint.ts +266 -0
- package/src/avatar/identity-avatar.ts +7 -2
- package/src/bundler/app-compiler.ts +15 -0
- package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
- package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
- package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
- package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
- package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
- package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
- package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
- package/src/calls/__tests__/voice-control-protocol.test.ts +120 -25
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
- package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
- package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
- package/src/calls/__tests__/voice-session-bridge.test.ts +56 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
- package/src/calls/barge-in-guard.ts +118 -0
- package/src/calls/call-constants.ts +24 -4
- package/src/calls/call-controller.ts +499 -199
- package/src/calls/call-transport.ts +16 -0
- package/src/calls/media-stream-output.ts +32 -0
- package/src/calls/media-stream-server.ts +82 -30
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/{live-voice → calls}/progress-narration.ts +2 -2
- package/src/calls/resolve-call-tts-provider.ts +33 -2
- package/src/calls/tts-call-strategy.ts +9 -5
- package/src/calls/types.ts +2 -0
- package/src/calls/voice-control-protocol.ts +155 -21
- package/src/calls/voice-leg-coordinator.ts +189 -0
- package/src/calls/voice-progress-cadence.ts +423 -0
- package/src/calls/voice-session-bridge.ts +92 -72
- package/src/calls/voice-triage-escalate.ts +178 -59
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/bash.test.ts +53 -0
- package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
- package/src/cli/commands/apps.help.ts +67 -13
- package/src/cli/commands/apps.ts +183 -0
- package/src/cli/commands/bash.help.ts +8 -13
- package/src/cli/commands/bash.ts +7 -25
- package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
- package/src/cli/commands/channels/index.help.ts +70 -2
- package/src/cli/commands/channels/index.ts +16 -3
- package/src/cli/commands/channels/send.ts +128 -0
- package/src/cli/commands/credentials.help.ts +6 -2
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/oauth/status.ts +5 -0
- package/src/cli/commands/roadmap.help.ts +5 -1
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +103 -90
- package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
- package/src/config/__tests__/platform-identity.test.ts +255 -0
- package/src/config/__tests__/platform-rehydration.test.ts +99 -9
- package/src/config/acp-agent-write.ts +43 -0
- package/src/config/acp-defaults.test.ts +13 -3
- package/src/config/acp-defaults.ts +23 -6
- package/src/config/acp-schema.test.ts +81 -0
- package/src/config/acp-schema.ts +30 -2
- package/src/config/bundled-skills/acp/SKILL.md +22 -14
- package/src/config/bundled-skills/acp/TOOLS.json +5 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +29 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
- package/src/config/bundled-skills/schedule/SKILL.md +3 -4
- package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +33 -13
- package/src/config/loader.ts +2 -0
- package/src/config/memory-v3-gate.test.ts +31 -0
- package/src/config/memory-v3-gate.ts +20 -16
- package/src/config/platform-identity.ts +201 -0
- package/src/config/platform-rehydration.ts +11 -27
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
- package/src/config/schemas/__tests__/voice.test.ts +148 -0
- package/src/config/schemas/calls.ts +1 -28
- package/src/config/schemas/live-voice.ts +1 -173
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +10 -0
- package/src/config/schemas/services.ts +18 -0
- package/src/config/schemas/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- package/src/credential-health/credential-health-service.ts +11 -17
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
- package/src/daemon/app-change-notify.ts +45 -0
- package/src/daemon/config-watcher.ts +13 -194
- package/src/daemon/conversation-error.ts +3 -2
- package/src/daemon/conversation-media-retry.ts +2 -1
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-runtime-assembly.ts +20 -7
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +21 -10
- package/src/daemon/conversation-tool-setup.ts +45 -59
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/dictation-profile-store.ts +2 -1
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +6 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-cu-proxy.ts +224 -19
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-proxy-preactivation.ts +2 -1
- package/src/daemon/host-transfer-proxy.ts +14 -18
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/orphan-reaper.ts +1 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/reaction-history-render.ts +2 -1
- package/src/daemon/shutdown-handlers.ts +1 -9
- package/src/daemon/skill-memory-refresh.ts +64 -0
- package/src/daemon/tool-side-effects.ts +7 -18
- package/src/desktop/__tests__/fake-desktop.ts +122 -0
- package/src/desktop/desktop-chrome-policy.test.ts +79 -0
- package/src/desktop/desktop-chrome-policy.ts +47 -0
- package/src/desktop/desktop-chrome-session.ts +37 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +308 -0
- package/src/desktop/desktop-feature.test.ts +18 -0
- package/src/desktop/desktop-feature.ts +19 -0
- package/src/desktop/desktop-panel-config.test.ts +135 -0
- package/src/desktop/desktop-panel-config.ts +149 -0
- package/src/desktop/desktop-session-manager.test.ts +730 -0
- package/src/desktop/desktop-session-manager.ts +850 -0
- package/src/desktop/desktop-stream-bridge.test.ts +395 -0
- package/src/desktop/desktop-stream-bridge.ts +214 -0
- package/src/desktop/desktop-wallpaper.test.ts +191 -0
- package/src/desktop/desktop-wallpaper.ts +113 -0
- package/src/desktop/desktop-window-drag.ts +177 -0
- package/src/desktop/desktop-window-manager-config.test.ts +230 -0
- package/src/desktop/desktop-window-manager-config.ts +238 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/heartbeat/heartbeat-service.ts +21 -3
- package/src/home/__tests__/feed-writer.test.ts +22 -0
- package/src/home/feed-writer.ts +11 -2
- package/src/hooks/types.ts +33 -0
- package/src/i18n/AGENTS.md +22 -0
- package/src/i18n/__tests__/i18n.test.ts +108 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/locales.ts +93 -0
- package/src/i18n/messages.ts +61 -0
- package/src/i18n/title.ts +25 -0
- package/src/inbound/platform-callback-registration.ts +4 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +121 -0
- package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +94 -15
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/protocol.test.ts +39 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/__tests__/session-controls.test.ts +79 -0
- package/src/live-voice/live-voice-photo.ts +25 -6
- package/src/live-voice/live-voice-session.ts +297 -699
- package/src/live-voice/protocol.ts +60 -2
- package/src/live-voice/session-controls.ts +111 -0
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +29 -45
- package/src/mcp/spec-schema.ts +111 -0
- package/src/mcp/startup.ts +8 -4
- package/src/mcp/workspace-mcp-config.ts +188 -0
- package/src/messaging/provider.ts +10 -3
- package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
- package/src/messaging/providers/callback-routing.ts +18 -0
- package/src/messaging/providers/channel-transport.ts +56 -0
- package/src/messaging/providers/discord/transport.ts +17 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
- package/src/messaging/providers/slack/adapter.ts +7 -44
- package/src/messaging/providers/slack/transport.ts +16 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
- package/src/messaging/providers/telegram-bot/client.ts +0 -26
- package/src/messaging/providers/telegram-bot/transport.ts +20 -0
- package/src/messaging/providers/whatsapp/adapter.ts +1 -48
- package/src/messaging/providers/whatsapp/transport.ts +18 -0
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/assistant-reply-producer.ts +8 -3
- package/src/notifications/conversation-pairing.ts +36 -16
- package/src/notifications/delivered-post-record.ts +11 -1
- package/src/notifications/guardian-feed-projection.ts +125 -5
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/notifications/schedule-result-producer.ts +66 -22
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/seed-providers-managed.test.ts +149 -0
- package/src/oauth/byo-connection.test.ts +105 -0
- package/src/oauth/byo-connection.ts +36 -6
- package/src/oauth/connection-resolver.test.ts +100 -0
- package/src/oauth/connection-resolver.ts +120 -3
- package/src/oauth/provider-serializer.ts +9 -0
- package/src/oauth/scope-utils.ts +24 -0
- package/src/oauth/seed-providers.ts +314 -27
- package/src/outbound-proxy/certs.ts +3 -5
- package/src/permissions/checker.test.ts +11 -6
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +71 -63
- package/src/persistence/conversation-lineage.ts +12 -4
- package/src/persistence/conversation-queries.ts +2 -2
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/embeddings/qdrant-manager.ts +163 -18
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-cursor.ts +78 -0
- package/src/persistence/message-reads.ts +30 -0
- package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
- package/src/persistence/schema/memory-core.ts +5 -0
- package/src/platform/client.test.ts +4 -2
- package/src/platform/client.ts +2 -10
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +2 -0
- package/src/plugins/defaults/index.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
- package/src/plugins/defaults/memory/context-search/format.ts +3 -2
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
- package/src/plugins/defaults/memory/hooks/init.ts +10 -0
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
- package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
- package/src/plugins/defaults/memory/host-utils.ts +10 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
- package/src/plugins/defaults/memory/startup.ts +28 -2
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
- package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
- package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
- package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
- package/src/plugins/defaults/memory/v3/card.ts +3 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
- package/src/plugins/defaults/memory/v3/sections.ts +9 -3
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
- package/src/plugins/defaults/memory/worker.ts +6 -0
- package/src/plugins/mcp-servers.ts +15 -75
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/__tests__/retry-callsite.test.ts +41 -0
- package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
- package/src/providers/anthropic/client.ts +0 -47
- package/src/providers/gemini/client.ts +9 -23
- package/src/providers/gemini-thought-signature.test.ts +63 -0
- package/src/providers/gemini-thought-signature.ts +44 -0
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
- package/src/providers/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- package/src/providers/inference/endpoint-probe.ts +10 -1
- package/src/providers/inference/resolve-auth.ts +22 -4
- package/src/providers/inference/service-account-token.ts +248 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
- package/src/providers/openai/chat-completions-provider.ts +212 -79
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/providers/opencode/client.test.ts +73 -4
- package/src/providers/opencode/client.ts +60 -3
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/outbound-request-sanitize.ts +96 -0
- package/src/providers/retry.ts +30 -19
- package/src/providers/unparseable-tool-args.ts +5 -1
- package/src/runtime/AGENTS.md +3 -1
- package/src/runtime/__tests__/channel-send.test.ts +443 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
- package/src/runtime/__tests__/host-observe.test.ts +40 -1
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
- package/src/runtime/agent-wake.ts +2 -1
- package/src/runtime/assistant-event-hub.ts +21 -3
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/host-observe.ts +11 -1
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +275 -0
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
- package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
- package/src/runtime/routes/acp-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +90 -2
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/channel-send-routes.ts +85 -0
- package/src/runtime/routes/conversation-list-routes.ts +14 -3
- package/src/runtime/routes/conversation-query-routes.ts +40 -0
- package/src/runtime/routes/conversation-routes.ts +16 -3
- package/src/runtime/routes/credential-routes.ts +20 -14
- package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
- package/src/runtime/routes/desktop-setup-routes.ts +35 -0
- package/src/runtime/routes/diagnostics-routes.ts +61 -4
- package/src/runtime/routes/events-routes.ts +4 -0
- package/src/runtime/routes/host-cu-routes.ts +6 -0
- package/src/runtime/routes/index.ts +4 -2
- package/src/runtime/routes/log-export-routes.ts +2 -0
- package/src/runtime/routes/mcp-auth-routes.ts +39 -47
- package/src/runtime/routes/migration-routes.ts +6 -20
- package/src/runtime/routes/oauth-commands-routes.ts +50 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/routes/workspace-routes.test.ts +55 -26
- package/src/runtime/routes/workspace-routes.ts +46 -63
- package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
- package/src/runtime/routes/workspace-tree-walk.ts +240 -0
- package/src/runtime/routes/workspace-utils.ts +17 -0
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +16 -3
- package/src/runtime/sync/resource-sync-events.ts +2 -1
- package/src/schedule/run-script.ts +2 -1
- package/src/schedule/schedule-store.ts +9 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
- package/src/security/ces-http-record-backend.ts +120 -0
- package/src/security/ces-rpc-record-backend.ts +7 -0
- package/src/security/untrusted-content.ts +3 -1
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
- package/src/skills/workspace-skill-md-signature.ts +50 -0
- package/src/subagent/consult-context.ts +12 -25
- package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
- package/src/tools/acp/list-agents.test.ts +3 -1
- package/src/tools/acp/spawn.test.ts +146 -11
- package/src/tools/acp/spawn.ts +21 -3
- package/src/tools/acp/status.test.ts +94 -0
- package/src/tools/acp/status.ts +40 -3
- package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
- package/src/tools/browser/network-recorder.ts +2 -1
- package/src/tools/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +121 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/filesystem/search.ts +2 -1
- package/src/tools/host-filesystem/edit.test.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +2 -1
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +2 -1
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +4 -20
- package/src/tools/network/web-search.ts +6 -1
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/skills/scaffold-managed.ts +61 -3
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- package/src/tools/ui-surface/channel-variants.ts +10 -59
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/types/host-capabilities.ts +5 -3
- package/src/util/__tests__/language-subtag.test.ts +23 -1
- package/src/util/ca-bundle.ts +13 -0
- package/src/util/language-subtag.ts +16 -0
- package/src/util/platform.ts +8 -0
- package/src/util/unicode.ts +15 -1
- package/src/watch/watch-retro.ts +6 -8
- package/src/watch/watch-timeline.ts +2 -2
- package/src/workflows/run-manager.ts +2 -1
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +6 -55
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/app-source-watcher.test.ts +0 -218
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
- package/src/__tests__/speaker-identification.test.ts +0 -56
- package/src/calls/speaker-identification.ts +0 -233
- package/src/daemon/app-source-watcher.ts +0 -218
- package/src/messaging/providers/whatsapp/client.ts +0 -29
- package/src/runtime/routes/debug-bash-routes.ts +0 -173
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -55,6 +55,13 @@ const SCHEDULE_ID = "sched-1";
|
|
|
55
55
|
const RUN_ID = "run-1";
|
|
56
56
|
const RUN_STARTED_AT = 1700000000000;
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* The synthetic user rows carrying each tool call's result. A delivery counts
|
|
60
|
+
* only when its call reported success, so a case that wants suppression has to
|
|
61
|
+
* supply the matching successful result.
|
|
62
|
+
*/
|
|
63
|
+
let toolResultRows: MessageRow[] = [];
|
|
64
|
+
|
|
58
65
|
const realCrud = await import("../../persistence/conversation-crud.js");
|
|
59
66
|
mock.module("../../persistence/conversation-crud.js", () => ({
|
|
60
67
|
...realCrud,
|
|
@@ -66,6 +73,7 @@ mock.module("../../persistence/conversation-crud.js", () => ({
|
|
|
66
73
|
messageId === ASSISTANT_MESSAGE_ID
|
|
67
74
|
? [...turnRows.map((row) => row.id), ASSISTANT_MESSAGE_ID]
|
|
68
75
|
: [messageId],
|
|
76
|
+
getMessagesAfter: () => toolResultRows,
|
|
69
77
|
}));
|
|
70
78
|
|
|
71
79
|
const realAttentionStore =
|
|
@@ -114,6 +122,25 @@ function makeToolCallRow(
|
|
|
114
122
|
);
|
|
115
123
|
}
|
|
116
124
|
|
|
125
|
+
/** The executor's verdict on the call `makeToolCallRow(name, ...)` made. */
|
|
126
|
+
function makeToolResultRow(name: string, isError = false): MessageRow {
|
|
127
|
+
return makeAssistantRow(
|
|
128
|
+
[
|
|
129
|
+
{
|
|
130
|
+
type: "tool_result",
|
|
131
|
+
tool_use_id: `tu-${name}`,
|
|
132
|
+
content: isError ? "failed" : "ok",
|
|
133
|
+
...(isError ? { is_error: true } : {}),
|
|
134
|
+
},
|
|
135
|
+
] as ContentBlock[],
|
|
136
|
+
{
|
|
137
|
+
id: `res-${name}`,
|
|
138
|
+
role: "user",
|
|
139
|
+
createdAt: RUN_STARTED_AT + 150,
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
117
144
|
function makeAttentionState(
|
|
118
145
|
overrides: Partial<AttentionState> = {},
|
|
119
146
|
): AttentionState {
|
|
@@ -189,6 +216,7 @@ beforeEach(() => {
|
|
|
189
216
|
notifiedProbeArgs.length = 0;
|
|
190
217
|
alreadyNotified = false;
|
|
191
218
|
turnRows = [];
|
|
219
|
+
toolResultRows = [];
|
|
192
220
|
attentionState = makeAttentionState();
|
|
193
221
|
assistantRow = makeAssistantRow([
|
|
194
222
|
{ type: "text", text: "**3 new emails** and one calendar change." },
|
|
@@ -245,7 +273,8 @@ describe("emitScheduleResultNotification", () => {
|
|
|
245
273
|
|
|
246
274
|
test("stays silent when the run delivered by email through messaging_send", async () => {
|
|
247
275
|
// The schedule skill's prescribed route for rich content. It writes no
|
|
248
|
-
// notification event, so
|
|
276
|
+
// notification event, so the successful tool call is what shows the user
|
|
277
|
+
// has the result.
|
|
249
278
|
turnRows = [
|
|
250
279
|
makeToolCallRow("messaging_send", {
|
|
251
280
|
platform: "gmail",
|
|
@@ -253,6 +282,7 @@ describe("emitScheduleResultNotification", () => {
|
|
|
253
282
|
message: "Inbox digest…",
|
|
254
283
|
}),
|
|
255
284
|
];
|
|
285
|
+
toolResultRows = [makeToolResultRow("messaging_send")];
|
|
256
286
|
assistantRow = makeAssistantRow([
|
|
257
287
|
{ type: "text", text: "Sent the digest to your inbox." },
|
|
258
288
|
] as ContentBlock[]);
|
|
@@ -262,20 +292,86 @@ describe("emitScheduleResultNotification", () => {
|
|
|
262
292
|
expect(emitCalls).toHaveLength(0);
|
|
263
293
|
});
|
|
264
294
|
|
|
265
|
-
test("stays silent when the run posted to
|
|
295
|
+
test("stays silent when the run posted to a channel through messaging_send", async () => {
|
|
296
|
+
turnRows = [
|
|
297
|
+
makeToolCallRow("messaging_send", {
|
|
298
|
+
platform: "slack",
|
|
299
|
+
conversation_id: "C0123456789",
|
|
300
|
+
text: "the digest",
|
|
301
|
+
}),
|
|
302
|
+
];
|
|
303
|
+
toolResultRows = [makeToolResultRow("messaging_send")];
|
|
304
|
+
assistantRow = makeAssistantRow([
|
|
305
|
+
{ type: "text", text: "Posted the digest to #general." },
|
|
306
|
+
] as ContentBlock[]);
|
|
307
|
+
|
|
308
|
+
await run();
|
|
309
|
+
|
|
310
|
+
expect(emitCalls).toHaveLength(0);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("notifies when the channel send failed, so the run is not silent", async () => {
|
|
314
|
+
// The failure this guard exists for: the post never landed, the run said
|
|
315
|
+
// so in a conversation nobody has open, and suppressing on the attempt
|
|
316
|
+
// would cost the user the digest and the explanation both.
|
|
317
|
+
turnRows = [
|
|
318
|
+
makeToolCallRow("messaging_send", {
|
|
319
|
+
platform: "slack",
|
|
320
|
+
conversation_id: "C0123456789",
|
|
321
|
+
text: "the digest",
|
|
322
|
+
}),
|
|
323
|
+
];
|
|
324
|
+
toolResultRows = [makeToolResultRow("messaging_send", true)];
|
|
325
|
+
assistantRow = makeAssistantRow([
|
|
326
|
+
{ type: "text", text: "I could not post the digest to #general." },
|
|
327
|
+
] as ContentBlock[]);
|
|
328
|
+
|
|
329
|
+
await run();
|
|
330
|
+
|
|
331
|
+
expect(emitCalls).toHaveLength(1);
|
|
332
|
+
expect(emitCalls[0].contextPayload.requestedMessage).toBe(
|
|
333
|
+
"I could not post the digest to #general.",
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("notifies when a delivery call left no result at all", async () => {
|
|
338
|
+
// An interrupted turn persists the call without its verdict. Unproven is
|
|
339
|
+
// not delivered.
|
|
340
|
+
turnRows = [
|
|
341
|
+
makeToolCallRow("messaging_send", {
|
|
342
|
+
platform: "slack",
|
|
343
|
+
conversation_id: "C0123456789",
|
|
344
|
+
text: "the digest",
|
|
345
|
+
}),
|
|
346
|
+
];
|
|
347
|
+
toolResultRows = [];
|
|
348
|
+
assistantRow = makeAssistantRow([
|
|
349
|
+
{ type: "text", text: "Here is the digest." },
|
|
350
|
+
] as ContentBlock[]);
|
|
351
|
+
|
|
352
|
+
await run();
|
|
353
|
+
|
|
354
|
+
expect(emitCalls).toHaveLength(1);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("notifies after a Web API post, whose success cannot prove Slack accepted it", async () => {
|
|
358
|
+
// Slack refuses a call with HTTP 200 and `ok: false`, so a successful bash
|
|
359
|
+
// result is no evidence the post landed. This is the best case the route
|
|
360
|
+
// can produce, and it still gets the fallback.
|
|
266
361
|
turnRows = [
|
|
267
362
|
makeToolCallRow("bash", {
|
|
268
363
|
command:
|
|
269
364
|
'assistant oauth request --provider slack_channel /chat.postMessage --json \'{"channel":"C1","text":"digest"}\'',
|
|
270
365
|
}),
|
|
271
366
|
];
|
|
367
|
+
toolResultRows = [makeToolResultRow("bash")];
|
|
272
368
|
assistantRow = makeAssistantRow([
|
|
273
369
|
{ type: "text", text: "Posted the digest to #general." },
|
|
274
370
|
] as ContentBlock[]);
|
|
275
371
|
|
|
276
372
|
await run();
|
|
277
373
|
|
|
278
|
-
expect(emitCalls).toHaveLength(
|
|
374
|
+
expect(emitCalls).toHaveLength(1);
|
|
279
375
|
});
|
|
280
376
|
|
|
281
377
|
test("still notifies when the run's tool calls were not deliveries", async () => {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import type { AssistantEvent } from "../../api/index.js";
|
|
22
22
|
import type { InterfaceId } from "../../channels/types.js";
|
|
23
|
+
import { getAssistantName } from "../../daemon/identity-helpers.js";
|
|
23
24
|
import { updateMessageContent } from "../../persistence/conversation-crud.js";
|
|
24
25
|
import { publishConversationMessagesChanged } from "../../runtime/sync/resource-sync-events.js";
|
|
25
26
|
import { getLogger } from "../../util/logger.js";
|
|
@@ -101,12 +102,14 @@ export class VellumAdapter implements ChannelAdapter {
|
|
|
101
102
|
|
|
102
103
|
const silent =
|
|
103
104
|
payload.urgency !== "high" && payload.urgency !== "critical";
|
|
105
|
+
const assistantName = getAssistantName()?.trim() || undefined;
|
|
104
106
|
|
|
105
107
|
this.broadcast({
|
|
106
108
|
type: "notification_intent",
|
|
107
109
|
deliveryId: payload.deliveryId,
|
|
108
110
|
correlationId: payload.correlationId,
|
|
109
111
|
sourceEventName: payload.sourceEventName,
|
|
112
|
+
...(assistantName ? { assistantName } : {}),
|
|
110
113
|
title: payload.copy.title,
|
|
111
114
|
body: payload.copy.body,
|
|
112
115
|
deepLinkMetadata: payload.deepLinkTarget,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
getMessageById,
|
|
23
23
|
parseMessageMetadata,
|
|
24
24
|
} from "../persistence/conversation-crud.js";
|
|
25
|
+
import { isReplaceableTitle } from "../persistence/conversation-title-placeholders.js";
|
|
25
26
|
import {
|
|
26
27
|
isDesktopOriginatedUserMessage,
|
|
27
28
|
isReplyPushIneligibleUserMessage,
|
|
@@ -247,9 +248,13 @@ export async function emitAssistantReplyNotification(params: {
|
|
|
247
248
|
// lock screen. Absent `requestedTitle` lets the decision branch derive a
|
|
248
249
|
// title from the body, which reads better than an empty or placeholder
|
|
249
250
|
// conversation title.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
251
|
+
//
|
|
252
|
+
// Placeholder titles are plausible non-empty strings and survive
|
|
253
|
+
// sanitizing. They count as absent so the body supplies the title instead.
|
|
254
|
+
const storedTitle = conversation.title?.trim() ?? "";
|
|
255
|
+
const requestedTitle = isReplaceableTitle(storedTitle)
|
|
256
|
+
? ""
|
|
257
|
+
: sanitizeNotificationTitle(flattenTitleWhitespace(storedTitle));
|
|
253
258
|
|
|
254
259
|
// Read as close to the emit as possible: nothing short-circuits on it.
|
|
255
260
|
// Presence only speaks for a turn the desktop itself opened, on that row's
|
|
@@ -130,6 +130,7 @@ export interface PairingOptions {
|
|
|
130
130
|
* transactional request or a system alert.
|
|
131
131
|
*/
|
|
132
132
|
const ASSISTANT_SHARE_EVENT = "assistant.share";
|
|
133
|
+
const ASSISTANT_REPLY_EVENT = "chat.assistant_reply";
|
|
133
134
|
|
|
134
135
|
/**
|
|
135
136
|
* Promote a background share into an assistant-initiated thread, under the
|
|
@@ -263,7 +264,25 @@ export async function pairDeliveryWithConversation(
|
|
|
263
264
|
// notification can appear. The home feed aims its "Go to Conversation"
|
|
264
265
|
// button at the same row whenever it mirrors the signal.
|
|
265
266
|
//
|
|
266
|
-
//
|
|
267
|
+
// `chat.assistant_reply` already has its complete reply in that transcript.
|
|
268
|
+
// Its notification body is a lock-screen preview, so appending it would
|
|
269
|
+
// create a second, truncated assistant row. Keep the conversation target
|
|
270
|
+
// for deep links without writing the preview into the transcript.
|
|
271
|
+
if (
|
|
272
|
+
strategy === "start_new_conversation" &&
|
|
273
|
+
!signal.requiresConversation &&
|
|
274
|
+
signal.sourceEventName === ASSISTANT_REPLY_EVENT
|
|
275
|
+
) {
|
|
276
|
+
return {
|
|
277
|
+
conversationId: resolveSourceConversationId(signal),
|
|
278
|
+
messageId: null,
|
|
279
|
+
strategy,
|
|
280
|
+
createdNewConversation: false,
|
|
281
|
+
conversationFallbackUsed: false,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// This is deliberately not gated on home-feed eligibility: a signal the
|
|
267
286
|
// feed declines to mirror still reaches the user through the banner, and
|
|
268
287
|
// this row is what makes that landing honest.
|
|
269
288
|
if (strategy === "start_new_conversation" && !signal.requiresConversation) {
|
|
@@ -748,44 +767,45 @@ async function resolveChannelDeliveryHome(params: {
|
|
|
748
767
|
* Indexing is skipped for parity with the other notification write paths:
|
|
749
768
|
* notification copy is delivery audit, not conversational memory.
|
|
750
769
|
*/
|
|
751
|
-
|
|
752
|
-
signal
|
|
753
|
-
channel: NotificationChannel,
|
|
754
|
-
messageContent: string,
|
|
755
|
-
): Promise<{ conversationId: string; messageId: string } | null> {
|
|
756
|
-
const sourceContextId = signal.sourceContextId;
|
|
757
|
-
if (!sourceContextId) {
|
|
770
|
+
function resolveSourceConversationId(signal: NotificationSignal): string | null {
|
|
771
|
+
if (!signal.sourceContextId) {
|
|
758
772
|
return null;
|
|
759
773
|
}
|
|
760
|
-
|
|
761
|
-
let existing: ReturnType<typeof getConversation>;
|
|
762
774
|
try {
|
|
763
|
-
|
|
775
|
+
return getConversation(signal.sourceContextId)?.id ?? null;
|
|
764
776
|
} catch {
|
|
765
777
|
return null;
|
|
766
778
|
}
|
|
767
|
-
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
async function appendBodyToSourceConversation(
|
|
782
|
+
signal: NotificationSignal,
|
|
783
|
+
channel: NotificationChannel,
|
|
784
|
+
messageContent: string,
|
|
785
|
+
): Promise<{ conversationId: string; messageId: string } | null> {
|
|
786
|
+
const conversationId = resolveSourceConversationId(signal);
|
|
787
|
+
if (!conversationId) {
|
|
768
788
|
return null;
|
|
769
789
|
}
|
|
770
790
|
|
|
771
|
-
const message = await addMessage(
|
|
791
|
+
const message = await addMessage(conversationId, "assistant", messageContent, {
|
|
772
792
|
skipIndexing: true,
|
|
773
793
|
});
|
|
774
794
|
// `addMessage` projects attention metadata alone, so a client with this
|
|
775
795
|
// conversation open needs the messages tag to refetch the transcript. A
|
|
776
796
|
// notification the user taps through to has every chance of landing on an
|
|
777
797
|
// already-open conversation.
|
|
778
|
-
publishConversationMessagesChanged(
|
|
798
|
+
publishConversationMessagesChanged(conversationId);
|
|
779
799
|
|
|
780
800
|
log.info(
|
|
781
801
|
{
|
|
782
802
|
signalId: signal.signalId,
|
|
783
803
|
channel,
|
|
784
|
-
conversationId
|
|
804
|
+
conversationId,
|
|
785
805
|
messageId: message.id,
|
|
786
806
|
},
|
|
787
807
|
"Appended notification body to producing conversation",
|
|
788
808
|
);
|
|
789
809
|
|
|
790
|
-
return { conversationId
|
|
810
|
+
return { conversationId, messageId: message.id };
|
|
791
811
|
}
|
|
@@ -39,6 +39,12 @@ export interface DeliveredChannelPost {
|
|
|
39
39
|
text: string;
|
|
40
40
|
/** The message id the channel assigned when it acknowledged the post. */
|
|
41
41
|
providerMessageId: string;
|
|
42
|
+
/**
|
|
43
|
+
* The ids of the further posts the same text became when the channel
|
|
44
|
+
* split it, in send order. Each is reconciled onto the row like the first,
|
|
45
|
+
* so a reaction, edit, or delete naming any chunk resolves to this row.
|
|
46
|
+
*/
|
|
47
|
+
additionalProviderMessageIds?: readonly string[];
|
|
42
48
|
/**
|
|
43
49
|
* The conversation whose turn made the post, when a different one from
|
|
44
50
|
* the home it is recorded in (the messaging tool sending from a scheduled
|
|
@@ -75,7 +81,11 @@ export async function recordDeliveredChannelPost(
|
|
|
75
81
|
: {}),
|
|
76
82
|
},
|
|
77
83
|
});
|
|
78
|
-
|
|
84
|
+
const reconcile = makeSentMessageIdReconciler(row.id);
|
|
85
|
+
await reconcile(post.providerMessageId);
|
|
86
|
+
for (const id of post.additionalProviderMessageIds ?? []) {
|
|
87
|
+
await reconcile(id);
|
|
88
|
+
}
|
|
79
89
|
// A resident conversation reloads its history on the next turn so the
|
|
80
90
|
// post is in context; the client refetches the transcript.
|
|
81
91
|
findConversation(post.conversationId)?.markHistoryStale();
|
|
@@ -31,6 +31,10 @@ import {
|
|
|
31
31
|
patchFeedItemContent,
|
|
32
32
|
readHomeFeed,
|
|
33
33
|
} from "../home/feed-writer.js";
|
|
34
|
+
import {
|
|
35
|
+
getMemoryCheckpoint,
|
|
36
|
+
setMemoryCheckpoint,
|
|
37
|
+
} from "../persistence/checkpoints.js";
|
|
34
38
|
import { getLogger } from "../util/logger.js";
|
|
35
39
|
import {
|
|
36
40
|
buildToolApprovalSourceView,
|
|
@@ -166,11 +170,17 @@ export interface GuardianFeedReceiptParams {
|
|
|
166
170
|
*
|
|
167
171
|
* The item keeps its title and summary; `guardianRequest` flips to the
|
|
168
172
|
* terminal status (which is what removes the client's action buttons),
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
173
|
+
* urgency drops to `medium` so the item leaves the protected "Needs
|
|
174
|
+
* attention" treatment and becomes an ordinary clearable notification,
|
|
175
|
+
* and an item still marked `new` is marked `seen`: a request settled on
|
|
176
|
+
* any surface is no longer something the user has to review, and leaving
|
|
177
|
+
* it unread is a false attention signal. That clear happens only on the
|
|
178
|
+
* edge into terminal, so a status the user set later (including marking
|
|
179
|
+
* the receipt unread again) survives a caller that receipts twice.
|
|
180
|
+
*
|
|
181
|
+
* Returns false only when the write itself failed, so the withdrawal
|
|
182
|
+
* fan-out can hold its per-request receipt back and retry (same
|
|
183
|
+
* contract as the other surfaces it settles). A request with no
|
|
174
184
|
* projection item resolves true: the pending writer converges the
|
|
175
185
|
* write-vs-resolve race by re-checking canonical status after its
|
|
176
186
|
* append, so there is nothing here for a retry to fix.
|
|
@@ -197,6 +207,15 @@ export async function writeGuardianFeedReceipt(
|
|
|
197
207
|
}
|
|
198
208
|
const updated = await patchFeedItemContent(itemId, {
|
|
199
209
|
urgency: "medium",
|
|
210
|
+
// Only on the pending-to-terminal edge. A later receipt for the
|
|
211
|
+
// same request (the fan-out retries per surface, reconciliation
|
|
212
|
+
// heals drift) must not undo a user who marked the receipt unread
|
|
213
|
+
// again; from `new` specifically, because a pending item the user
|
|
214
|
+
// dismissed outright stays dismissed.
|
|
215
|
+
status: (existing) =>
|
|
216
|
+
existing.status === "new" && isPendingGuardianFeedItem(existing)
|
|
217
|
+
? "seen"
|
|
218
|
+
: existing.status,
|
|
200
219
|
guardianRequest: (existing) => ({
|
|
201
220
|
...existing,
|
|
202
221
|
status: params.status,
|
|
@@ -268,6 +287,103 @@ export async function receiptGuardianFeedItemIfRequestTerminal(
|
|
|
268
287
|
*/
|
|
269
288
|
const RECONCILE_BACKFILL_LIMIT = 50;
|
|
270
289
|
|
|
290
|
+
/** Ceiling on rows healed per round, mirroring the backfill bound. */
|
|
291
|
+
const LEGACY_UNREAD_HEAL_LIMIT = 50;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Completion sentinel for the one-time heal below, in the daemon's
|
|
295
|
+
* checkpoint ledger (the same shape as the lexical backfill's
|
|
296
|
+
* `lexical:messages:backfill_complete`). It is the only thing that
|
|
297
|
+
* separates the two kinds of terminal receipt stored at `status: "new"`:
|
|
298
|
+
* one that never passed the pending-to-terminal edge, and one the user
|
|
299
|
+
* marked unread after it did. The rows are byte-identical (terminal
|
|
300
|
+
* projection, urgency `medium`), so the boundary is recorded rather
|
|
301
|
+
* than inferred.
|
|
302
|
+
*/
|
|
303
|
+
const LEGACY_UNREAD_HEAL_COMPLETE_KEY =
|
|
304
|
+
"guardian_feed:receipt_unread_heal_complete";
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Clear unread on terminal guardian receipts that never passed the
|
|
308
|
+
* pending-to-terminal edge in `writeGuardianFeedReceipt`.
|
|
309
|
+
*
|
|
310
|
+
* Nothing else reaches those rows: the edge runs only while a
|
|
311
|
+
* projection still reads pending, reconciliation skips items whose
|
|
312
|
+
* projection is already terminal, and guardian items carry no
|
|
313
|
+
* `expiresAt`. Without this pass they read unread indefinitely.
|
|
314
|
+
*
|
|
315
|
+
* Runs once per assistant, not once per boot. A recurring pass would
|
|
316
|
+
* take back a deliberate "mark unread" every minute, and a per-boot pass
|
|
317
|
+
* would take it back on the next restart; the sentinel is what makes a
|
|
318
|
+
* later `new` unambiguously the user's, so it is never touched.
|
|
319
|
+
*
|
|
320
|
+
* Same user-choice semantics as the edge, enforced the same way: the
|
|
321
|
+
* transition is re-evaluated inside the writer's coalescing queue, moves
|
|
322
|
+
* only `new`, and leaves `seen`, `acted_on` and `dismissed` alone.
|
|
323
|
+
* Bounded per round, and the sentinel is written only once a round drains
|
|
324
|
+
* with nothing left and nothing failed, so an interrupted pass resumes.
|
|
325
|
+
*/
|
|
326
|
+
export async function healLegacyGuardianReceiptUnread(): Promise<void> {
|
|
327
|
+
try {
|
|
328
|
+
if (getMemoryCheckpoint(LEGACY_UNREAD_HEAL_COMPLETE_KEY) === "1") {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
} catch (err) {
|
|
332
|
+
// A DB that cannot answer skips the round rather than risk re-healing
|
|
333
|
+
// a row the user has since marked unread.
|
|
334
|
+
log.warn({ err }, "Guardian receipt unread heal: checkpoint unreadable");
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const stale = readHomeFeed().items.filter(
|
|
339
|
+
(item) =>
|
|
340
|
+
item.guardianRequest !== undefined &&
|
|
341
|
+
!isPendingGuardianFeedItem(item) &&
|
|
342
|
+
item.status === "new",
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
const batch = stale.slice(0, LEGACY_UNREAD_HEAL_LIMIT);
|
|
346
|
+
let failed = 0;
|
|
347
|
+
for (const item of batch) {
|
|
348
|
+
// Re-tested at write time rather than trusted from the read above,
|
|
349
|
+
// so a row the edge transition or the user changed in between keeps
|
|
350
|
+
// whatever it was given.
|
|
351
|
+
const updated = await patchFeedItemContent(item.id, {
|
|
352
|
+
status: (existing) =>
|
|
353
|
+
existing.status === "new" && !isPendingGuardianFeedItem(existing)
|
|
354
|
+
? "seen"
|
|
355
|
+
: existing.status,
|
|
356
|
+
});
|
|
357
|
+
if (!updated) {
|
|
358
|
+
failed++;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (batch.length > 0) {
|
|
363
|
+
log.info(
|
|
364
|
+
{
|
|
365
|
+
healed: batch.length - failed,
|
|
366
|
+
failed,
|
|
367
|
+
remaining: stale.length - batch.length,
|
|
368
|
+
},
|
|
369
|
+
"Cleared unread on terminal guardian receipts that never passed the edge",
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (failed > 0 || stale.length > batch.length) {
|
|
374
|
+
// Not finished: leave the sentinel unset so the next round resumes.
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
setMemoryCheckpoint(LEGACY_UNREAD_HEAL_COMPLETE_KEY, "1");
|
|
380
|
+
} catch (err) {
|
|
381
|
+
// The heal itself landed; only the sentinel is missing, so the next
|
|
382
|
+
// round repeats a pass that now has nothing to do.
|
|
383
|
+
log.warn({ err }, "Guardian receipt unread heal: checkpoint not persisted");
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
271
387
|
/**
|
|
272
388
|
* Converge the feed against canonical guardian state. Run from the
|
|
273
389
|
* guardian expiry sweep, so drift heals on the same cadence expiry does:
|
|
@@ -285,6 +401,10 @@ const RECONCILE_BACKFILL_LIMIT = 50;
|
|
|
285
401
|
* not be mistaken for "everything resolved".
|
|
286
402
|
*/
|
|
287
403
|
export async function reconcileGuardianFeedProjections(): Promise<void> {
|
|
404
|
+
// Ahead of the gateway read: the heal heads off stored rows alone and
|
|
405
|
+
// must not be held up by an unreachable gateway.
|
|
406
|
+
await healLegacyGuardianReceiptUnread();
|
|
407
|
+
|
|
288
408
|
let pending: GuardianRequestWire[];
|
|
289
409
|
try {
|
|
290
410
|
pending = await listGuardianRequests({ status: "pending" });
|
|
@@ -23,7 +23,10 @@ import {
|
|
|
23
23
|
getMessageById,
|
|
24
24
|
updateMessageContent,
|
|
25
25
|
} from "../persistence/conversation-crud.js";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
ASSISTANT_INITIATED_SOURCE,
|
|
28
|
+
isBackgroundConversationType,
|
|
29
|
+
} from "../persistence/conversation-types.js";
|
|
27
30
|
import { publishConversationMessagesChanged } from "../runtime/sync/resource-sync-events.js";
|
|
28
31
|
import { getLogger } from "../util/logger.js";
|
|
29
32
|
import { normalizeTitle, stripMarkdown } from "../util/short-title.js";
|
|
@@ -272,6 +275,11 @@ async function resolveOwnedConversationMessageId(
|
|
|
272
275
|
sourceConversationId: string | undefined,
|
|
273
276
|
summary: string,
|
|
274
277
|
): Promise<string | undefined> {
|
|
278
|
+
// The completed reply is already the source conversation's canonical row.
|
|
279
|
+
// This signal's body is a compact push preview, not conversation content.
|
|
280
|
+
if (signal.sourceEventName === "chat.assistant_reply") {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
275
283
|
if (vellumDelivery?.conversationId) {
|
|
276
284
|
return vellumDelivery.conversationId === sourceConversationId
|
|
277
285
|
? vellumDelivery.messageId
|
|
@@ -495,6 +503,18 @@ function deriveDetailPanelKind(
|
|
|
495
503
|
* `isAsyncBackground` hint. `chat.assistant_reply` also mirrors: it is the
|
|
496
504
|
* durable in-app record for the push sent after a user leaves a chat, while
|
|
497
505
|
* retaining the normal interactive conversation as its navigation target.
|
|
506
|
+
*
|
|
507
|
+
* A delivery that materialized an assistant-initiated thread never mirrors.
|
|
508
|
+
* Under the `assistant-initiated-threads` flag, `conversation-pairing.ts`
|
|
509
|
+
* promotes a background `assistant.share` into a standard conversation
|
|
510
|
+
* stamped {@link ASSISTANT_INITIATED_SOURCE}, and that thread's row in the
|
|
511
|
+
* sidebar's assistant section is the share's one surface. Mirroring it here
|
|
512
|
+
* as well put the same share in the bell a second time, labeled by its
|
|
513
|
+
* source channel ("Heartbeat"). The check reads the paired conversation's
|
|
514
|
+
* `source` rather than re-deriving the promotion rule, so it holds however
|
|
515
|
+
* that rule narrows or widens. Guardian requests are checked first on
|
|
516
|
+
* purpose: the bell is their canonical home whatever conversation they pair
|
|
517
|
+
* with.
|
|
498
518
|
*/
|
|
499
519
|
function resolveHomeFeedMirror(
|
|
500
520
|
signal: NotificationSignal,
|
|
@@ -525,6 +545,15 @@ function resolveHomeFeedMirror(
|
|
|
525
545
|
: fallbackConversationId;
|
|
526
546
|
const sourceScheduleJobId = sourceRow?.scheduleJobId ?? undefined;
|
|
527
547
|
|
|
548
|
+
// Guardian requests always project into the feed: the "Needs
|
|
549
|
+
// attention" item is the request's canonical home, and the request
|
|
550
|
+
// blocks on the guardian whatever kind of conversation raised it.
|
|
551
|
+
if (isGuardianRequestSignalEvent(signal.sourceEventName)) {
|
|
552
|
+
return { mirror: true, sourceConversationId, sourceScheduleJobId };
|
|
553
|
+
}
|
|
554
|
+
if (isAssistantInitiatedThreadDelivery(fallbackConversationId)) {
|
|
555
|
+
return { mirror: false };
|
|
556
|
+
}
|
|
528
557
|
if (
|
|
529
558
|
signal.sourceChannel === "assistant_tool" ||
|
|
530
559
|
signal.sourceEventName === "chat.assistant_reply"
|
|
@@ -534,18 +563,37 @@ function resolveHomeFeedMirror(
|
|
|
534
563
|
if (signal.attentionHints.isAsyncBackground) {
|
|
535
564
|
return { mirror: true, sourceConversationId, sourceScheduleJobId };
|
|
536
565
|
}
|
|
537
|
-
// Guardian requests always project into the feed: the "Needs
|
|
538
|
-
// attention" item is the request's canonical home, and the request
|
|
539
|
-
// blocks on the guardian whatever kind of conversation raised it.
|
|
540
|
-
if (isGuardianRequestSignalEvent(signal.sourceEventName)) {
|
|
541
|
-
return { mirror: true, sourceConversationId, sourceScheduleJobId };
|
|
542
|
-
}
|
|
543
566
|
if (isBackgroundConversationType(sourceRow?.conversationType)) {
|
|
544
567
|
return { mirror: true, sourceConversationId, sourceScheduleJobId };
|
|
545
568
|
}
|
|
546
569
|
return { mirror: false };
|
|
547
570
|
}
|
|
548
571
|
|
|
572
|
+
/**
|
|
573
|
+
* Whether the vellum delivery this signal produced landed in a thread the
|
|
574
|
+
* sidebar's assistant section already shows.
|
|
575
|
+
*
|
|
576
|
+
* Best-effort like the source lookup above: a missing id, a missing row, or
|
|
577
|
+
* a lookup failure all read as "not an assistant thread", so a storage
|
|
578
|
+
* hiccup degrades to the pre-existing behavior (a bell row) rather than
|
|
579
|
+
* dropping the notification everywhere.
|
|
580
|
+
*/
|
|
581
|
+
function isAssistantInitiatedThreadDelivery(
|
|
582
|
+
deliveryConversationId: string | undefined,
|
|
583
|
+
): boolean {
|
|
584
|
+
if (!deliveryConversationId) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
return (
|
|
589
|
+
getConversation(deliveryConversationId)?.source ===
|
|
590
|
+
ASSISTANT_INITIATED_SOURCE
|
|
591
|
+
);
|
|
592
|
+
} catch {
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
549
597
|
/**
|
|
550
598
|
* Routing-intent enforcement can prune `selectedChannels` without also
|
|
551
599
|
* pruning `renderedCopy`, so iterating `renderedCopy` directly risks
|