@vellumai/assistant 0.12.1-staging.1 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +0 -4
- package/ARCHITECTURE.md +12 -11
- package/docs/architecture/turn-actor.md +8 -15
- package/openapi.yaml +1 -291
- package/package.json +1 -1
- package/src/__tests__/always-loaded-tools-guard.test.ts +10 -18
- package/src/__tests__/app-compiler.test.ts +0 -6
- package/src/__tests__/app-control-tool-schemas.test.ts +2 -5
- package/src/__tests__/app-dir-path-guard.test.ts +3 -1
- package/src/__tests__/app-source-watcher.test.ts +218 -0
- package/src/{calls/__tests__ → __tests__}/call-controller.test.ts +82 -503
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +2 -5
- package/src/__tests__/computer-use-tools.test.ts +5 -17
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +29 -28
- package/src/__tests__/config-watcher-skill-reseed.test.ts +359 -0
- package/src/__tests__/config-watcher.test.ts +8 -23
- 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 +0 -26
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +10 -4
- package/src/__tests__/conversation-queue.test.ts +207 -48
- package/src/__tests__/conversation-retry-discard.test.ts +1 -54
- package/src/__tests__/conversation-skill-tools.test.ts +0 -27
- package/src/__tests__/conversation-surfaces-app-control.test.ts +3 -8
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -67
- package/src/__tests__/conversation-title-service.test.ts +10 -14
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +5 -3
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -51
- package/src/__tests__/credential-record-retire.test.ts +1 -171
- package/src/__tests__/credential-routes.test.ts +3 -212
- package/src/__tests__/credential-security-invariants.test.ts +1 -2
- package/src/__tests__/cu-unified-flow.test.ts +2 -8
- package/src/__tests__/drain-kick-guard.test.ts +2 -0
- package/src/__tests__/drain-requeue-on-contention.test.ts +6 -0
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/host-bash-proxy.test.ts +45 -26
- package/src/__tests__/host-browser-proxy.test.ts +17 -6
- package/src/__tests__/host-file-proxy-targeted.test.ts +0 -23
- package/src/__tests__/host-file-proxy.test.ts +23 -11
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +9 -30
- package/src/__tests__/llm-catalog-parity.test.ts +0 -25
- package/src/__tests__/mcp-auth-routes.test.ts +9 -6
- package/src/__tests__/mcp-config-secret-boundary.test.ts +136 -78
- package/src/__tests__/mcp-health-check.test.ts +11 -8
- package/src/__tests__/mcp-list-plugin-servers.test.ts +10 -8
- package/src/{calls/__tests__ → __tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{calls/__tests__ → __tests__}/media-stream-output.test.ts +8 -37
- package/src/{calls/__tests__ → __tests__}/media-stream-parser.test.ts +1 -1
- package/src/{calls/__tests__ → __tests__}/media-stream-server-integration.test.ts +57 -150
- package/src/{calls/__tests__ → __tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{calls/__tests__ → __tests__}/media-turn-detector.test.ts +1 -1
- package/src/__tests__/messages-after-tiebreaker.test.ts +0 -91
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +548 -355
- package/src/__tests__/model-intents.test.ts +0 -3
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-client-verify-credential.test.ts +0 -2
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -13
- package/src/__tests__/script-proxy-certs.test.ts +0 -35
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/speaker-identification.test.ts +56 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +3 -19
- package/src/__tests__/subagent-tools.test.ts +2 -3
- package/src/__tests__/telephony-tts-guard.test.ts +3 -38
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -4
- package/src/{calls/__tests__/voice-session-bridge-turn-flow.test.ts → __tests__/voice-session-bridge.test.ts} +12 -20
- package/src/__tests__/watch-retro-tool-availability.test.ts +17 -8
- package/src/__tests__/workspace-git-service.test.ts +0 -26
- package/src/acp/__tests__/agent-process.test.ts +4 -258
- package/src/acp/__tests__/auth-required.test.ts +0 -96
- package/src/acp/__tests__/client-handler.test.ts +2 -90
- package/src/acp/__tests__/session-manager-persistence.test.ts +1 -5
- package/src/acp/__tests__/session-manager-resume.test.ts +6 -270
- package/src/acp/__tests__/session-manager-usage.test.ts +2 -4
- package/src/acp/__tests__/session-manager.test.ts +4 -895
- package/src/acp/agent-process.test.ts +1 -4
- package/src/acp/agent-process.ts +9 -94
- package/src/acp/auth-required.ts +1 -50
- package/src/acp/auto-install.test.ts +7 -9
- package/src/acp/client-handler.ts +3 -16
- package/src/acp/resolve-agent.test.ts +6 -220
- package/src/acp/resolve-agent.ts +23 -88
- package/src/acp/session-manager.test.ts +3 -196
- package/src/acp/session-manager.ts +18 -441
- package/src/acp/types.ts +5 -50
- package/src/api/events/app-files-changed.ts +3 -4
- package/src/api/index.ts +0 -6
- package/src/apps/app-store.ts +1 -1
- package/src/bundler/app-compiler.ts +0 -15
- package/src/calls/__tests__/voice-control-protocol.test.ts +25 -58
- package/src/calls/__tests__/voice-session-bridge.test.ts +1 -37
- package/src/calls/__tests__/voice-triage-escalate.test.ts +0 -105
- package/src/calls/call-constants.ts +4 -24
- package/src/calls/call-controller.ts +199 -499
- package/src/calls/call-transport.ts +0 -16
- package/src/calls/media-stream-output.ts +0 -32
- package/src/calls/media-stream-server.ts +30 -82
- package/src/calls/media-stream-stt-session.ts +0 -15
- package/src/calls/resolve-call-tts-provider.ts +2 -33
- package/src/calls/speaker-identification.ts +233 -0
- package/src/calls/tts-call-strategy.ts +5 -9
- package/src/calls/types.ts +0 -2
- package/src/calls/voice-control-protocol.ts +21 -53
- package/src/calls/voice-session-bridge.ts +46 -59
- package/src/calls/voice-triage-escalate.ts +56 -175
- package/src/cli/commands/apps.help.ts +13 -67
- package/src/cli/commands/apps.ts +0 -183
- package/src/cli/commands/channels/index.help.ts +2 -70
- package/src/cli/commands/channels/index.ts +3 -16
- package/src/cli/commands/credentials.help.ts +2 -6
- 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/roadmap.help.ts +1 -5
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +90 -103
- package/src/config/__tests__/default-profile-catalog.test.ts +0 -22
- package/src/config/__tests__/memory-retrospective-schema.test.ts +1 -15
- package/src/config/__tests__/platform-rehydration.test.ts +0 -93
- package/src/config/acp-defaults.test.ts +3 -13
- package/src/config/acp-defaults.ts +6 -23
- package/src/config/acp-schema.ts +2 -30
- package/src/config/bundled-skills/acp/SKILL.md +14 -22
- package/src/config/bundled-skills/acp/TOOLS.json +1 -5
- package/src/config/bundled-skills/app-builder/SKILL.md +9 -12
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +139 -67
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/screen-annotation/SKILL.md +1 -8
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +1 -4
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +12 -8
- package/src/config/memory-v3-gate.test.ts +0 -31
- package/src/config/memory-v3-gate.ts +16 -20
- package/src/config/platform-rehydration.ts +21 -75
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +158 -8
- package/src/config/schemas/calls.ts +28 -1
- package/src/config/schemas/live-voice.ts +173 -1
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +0 -10
- package/src/config/schemas/skills.ts +14 -0
- package/src/credential-execution/process-manager.test.ts +19 -18
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +121 -66
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +1 -4
- package/src/daemon/app-source-watcher.ts +218 -0
- package/src/daemon/config-watcher.ts +194 -13
- package/src/daemon/conversation-process.ts +64 -35
- package/src/daemon/conversation-skill-tools.ts +31 -13
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +8 -10
- package/src/daemon/conversation-tool-setup.ts +43 -37
- package/src/daemon/conversation.ts +3 -0
- package/src/daemon/host-bash-proxy.ts +12 -8
- package/src/daemon/host-browser-proxy.ts +1 -15
- package/src/daemon/host-file-proxy.ts +10 -8
- package/src/daemon/host-transfer-proxy.ts +18 -14
- package/src/daemon/lifecycle.ts +5 -0
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/shutdown-handlers.ts +9 -1
- package/src/daemon/skill-memory-refresh.ts +0 -64
- package/src/daemon/tool-side-effects.ts +18 -7
- package/src/hooks/types.ts +0 -33
- package/src/inbound/platform-callback-registration.ts +5 -4
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +3 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/{calls → live-voice}/__tests__/progress-narration.test.ts +4 -4
- package/src/{calls → live-voice}/__tests__/progress-phrases.test.ts +1 -1
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +69 -13
- package/src/live-voice/live-voice-session.ts +693 -213
- package/src/{calls → live-voice}/progress-narration.ts +2 -2
- package/src/live-voice/protocol.ts +2 -0
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +45 -29
- package/src/mcp/startup.ts +4 -8
- package/src/messaging/provider.ts +3 -10
- package/src/messaging/providers/callback-routing.ts +0 -18
- package/src/messaging/providers/channel-transport.ts +0 -56
- package/src/messaging/providers/discord/transport.ts +0 -17
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +17 -6
- package/src/messaging/providers/slack/adapter.ts +44 -7
- package/src/messaging/providers/slack/transport.ts +0 -16
- package/src/messaging/providers/telegram-bot/adapter.ts +48 -1
- package/src/messaging/providers/telegram-bot/client.ts +26 -0
- package/src/messaging/providers/telegram-bot/transport.ts +0 -20
- package/src/messaging/providers/whatsapp/adapter.ts +48 -1
- package/src/messaging/providers/whatsapp/client.ts +29 -0
- package/src/messaging/providers/whatsapp/transport.ts +0 -18
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +0 -8
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +0 -55
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +3 -153
- package/src/notifications/assistant-reply-producer.ts +3 -8
- package/src/notifications/conversation-pairing.ts +16 -36
- package/src/notifications/delivered-post-record.ts +1 -11
- package/src/notifications/home-feed-side-effect.ts +7 -55
- package/src/outbound-proxy/certs.ts +5 -3
- package/src/permissions/checker.test.ts +6 -11
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +63 -71
- package/src/persistence/conversation-lineage.ts +4 -12
- package/src/persistence/conversation-title-service.ts +26 -9
- 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-reads.ts +0 -30
- package/src/persistence/migrations/157-invite-contact-id.ts +6 -18
- package/src/persistence/schema/memory-core.ts +0 -5
- package/src/platform/client.test.ts +0 -2
- package/src/platform/client.ts +10 -2
- package/src/plugin-api/constants.ts +0 -2
- package/src/plugin-api/index.ts +0 -1
- package/src/plugin-api/types.ts +0 -2
- package/src/plugins/defaults/index.ts +0 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +0 -46
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +5 -9
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +10 -20
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +0 -219
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +0 -31
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -63
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +0 -4
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -4
- package/src/plugins/defaults/memory/hooks/init.ts +0 -10
- package/src/plugins/defaults/memory/hooks/shutdown.ts +0 -2
- package/src/plugins/defaults/memory/jobs-worker.ts +0 -13
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +60 -34
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +5 -3
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +3 -12
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +71 -312
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +1 -2
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +3 -5
- package/src/plugins/defaults/memory/startup.ts +1 -6
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +9 -7
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +0 -89
- package/src/plugins/defaults/memory/worker.ts +0 -6
- package/src/plugins/mcp-servers.ts +75 -15
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/gemini/client.ts +23 -9
- package/src/providers/model-catalog.ts +0 -34
- package/src/providers/model-intents.ts +5 -10
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +18 -146
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +1 -83
- package/src/providers/openai/chat-completions-provider.ts +78 -192
- package/src/providers/openai/codex-models.ts +4 -0
- package/src/runtime/AGENTS.md +0 -2
- package/src/runtime/auth/same-actor.ts +3 -38
- package/src/runtime/http-server.ts +151 -160
- package/src/runtime/routes/__tests__/acp-routes.test.ts +4 -159
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -108
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +12 -16
- package/src/runtime/routes/acp-routes.test.ts +0 -15
- package/src/runtime/routes/acp-routes.ts +9 -72
- package/src/runtime/routes/app-management-routes.ts +0 -73
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +3 -14
- package/src/runtime/routes/conversation-query-routes.ts +0 -40
- package/src/runtime/routes/credential-routes.ts +10 -19
- package/src/runtime/routes/diagnostics-routes.ts +3 -55
- package/src/runtime/routes/index.ts +0 -4
- package/src/runtime/routes/log-export-routes.ts +0 -2
- package/src/runtime/routes/mcp-auth-routes.ts +47 -39
- package/src/runtime/routes/migration-routes.ts +21 -8
- package/src/runtime/routes/platform-routes.ts +15 -18
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -28
- package/src/runtime/services/conversation-serializer.ts +3 -16
- package/src/runtime/sync/resource-sync-events.ts +1 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/ces-rpc-record-backend.ts +0 -7
- package/src/subagent/consult-context.ts +25 -12
- package/src/tools/acp/list-agents.test.ts +1 -3
- package/src/tools/acp/spawn.test.ts +11 -146
- package/src/tools/acp/spawn.ts +3 -21
- package/src/tools/acp/status.ts +3 -40
- package/src/tools/app-control/skill-proxy-bridge.ts +1 -2
- package/src/tools/capability-offer.test.ts +2 -22
- package/src/tools/capability-offer.ts +1 -16
- package/src/tools/computer-use/definitions.ts +2 -4
- package/src/tools/computer-use/skill-proxy-bridge.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +0 -100
- package/src/tools/host-filesystem/edit.test.ts +1 -2
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +1 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +1 -2
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +1 -2
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +0 -16
- package/src/tools/host-terminal/host-shell.ts +20 -4
- package/src/tools/policy-context.ts +7 -6
- package/src/tools/subagent/spawn.ts +8 -5
- package/src/tools/watch/watch-retro-report.ts +8 -0
- package/src/util/__tests__/language-subtag.test.ts +1 -23
- package/src/util/fs-watcher-error.ts +36 -0
- package/src/util/language-subtag.ts +0 -16
- package/src/util/platform.ts +0 -8
- package/src/workspace/git-service.ts +54 -5
- package/src/workspace/migrations/registry.ts +0 -6
- package/scripts/smoke-desktop-window-theme.ts +0 -186
- package/src/__tests__/git-porcelain.test.ts +0 -47
- package/src/__tests__/helpers/set-workspace-mcp.ts +0 -11
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +0 -119
- package/src/__tests__/resolve-app.test.ts +0 -135
- package/src/__tests__/source-fingerprint.test.ts +0 -184
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +0 -336
- package/src/acp/__tests__/helpers/acp-model-option.ts +0 -51
- package/src/acp/model-config.test.ts +0 -166
- package/src/acp/model-config.ts +0 -138
- package/src/api/events/acp-session-model-update.test.ts +0 -88
- package/src/api/events/acp-session-model-update.ts +0 -46
- package/src/apps/resolve-app.ts +0 -81
- package/src/apps/source-fingerprint.ts +0 -266
- package/src/calls/__tests__/barge-in-guard.test.ts +0 -81
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +0 -229
- package/src/calls/__tests__/voice-progress-cadence.test.ts +0 -274
- package/src/calls/barge-in-guard.ts +0 -118
- package/src/calls/voice-leg-coordinator.ts +0 -189
- package/src/calls/voice-progress-cadence.ts +0 -423
- package/src/cli/commands/__tests__/apps.test.ts +0 -245
- package/src/cli/commands/__tests__/roadmap.help.test.ts +0 -19
- package/src/cli/commands/channels/__tests__/send.test.ts +0 -218
- package/src/cli/commands/channels/send.ts +0 -128
- package/src/config/__tests__/platform-identity.test.ts +0 -117
- package/src/config/acp-agent-write.ts +0 -43
- package/src/config/acp-schema.test.ts +0 -81
- package/src/config/platform-identity.ts +0 -144
- package/src/config/schemas/__tests__/voice.test.ts +0 -148
- package/src/config/schemas/voice.ts +0 -150
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +0 -133
- package/src/daemon/app-change-notify.ts +0 -45
- package/src/desktop/__tests__/fake-desktop.ts +0 -119
- package/src/desktop/desktop-chrome-policy.test.ts +0 -71
- package/src/desktop/desktop-chrome-policy.ts +0 -42
- package/src/desktop/desktop-dependencies.test.ts +0 -102
- package/src/desktop/desktop-dependencies.ts +0 -306
- package/src/desktop/desktop-feature.test.ts +0 -18
- package/src/desktop/desktop-feature.ts +0 -19
- package/src/desktop/desktop-panel-config.test.ts +0 -135
- package/src/desktop/desktop-panel-config.ts +0 -149
- package/src/desktop/desktop-session-manager.test.ts +0 -627
- package/src/desktop/desktop-session-manager.ts +0 -814
- package/src/desktop/desktop-stream-bridge.test.ts +0 -395
- package/src/desktop/desktop-stream-bridge.ts +0 -214
- package/src/desktop/desktop-wallpaper.test.ts +0 -191
- package/src/desktop/desktop-wallpaper.ts +0 -113
- package/src/desktop/desktop-window-theme.ts +0 -170
- package/src/i18n/AGENTS.md +0 -22
- package/src/i18n/__tests__/i18n.test.ts +0 -108
- package/src/i18n/index.ts +0 -16
- package/src/i18n/locales.ts +0 -93
- package/src/i18n/messages.ts +0 -61
- package/src/i18n/title.ts +0 -25
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +0 -135
- package/src/mcp/spec-schema.ts +0 -111
- package/src/mcp/workspace-mcp-config.ts +0 -188
- package/src/messaging/providers/__tests__/proactive-address.test.ts +0 -95
- package/src/persistence/conversation-title-placeholders.ts +0 -23
- package/src/persistence/message-cursor.ts +0 -78
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +0 -149
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +0 -118
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +0 -83
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +0 -21
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +0 -126
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +0 -79
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +0 -28
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +0 -9
- package/src/providers/gemini-thought-signature.test.ts +0 -63
- package/src/providers/gemini-thought-signature.ts +0 -44
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +0 -613
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +0 -235
- package/src/providers/openai/google-thought-signature.ts +0 -320
- package/src/runtime/__tests__/channel-send.test.ts +0 -443
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +0 -89
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +0 -58
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +0 -72
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +0 -59
- package/src/runtime/channel-send.ts +0 -316
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +0 -205
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +0 -127
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
- package/src/runtime/routes/channel-send-routes.ts +0 -85
- package/src/runtime/routes/desktop-setup-routes.test.ts +0 -74
- package/src/runtime/routes/desktop-setup-routes.ts +0 -35
- package/src/security/__tests__/ces-http-record-backend.test.ts +0 -137
- package/src/security/ces-http-record-backend.ts +0 -120
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +0 -60
- package/src/skills/workspace-skill-md-signature.ts +0 -50
- package/src/tools/acp/status.test.ts +0 -94
- package/src/util/ca-bundle.ts +0 -13
- package/src/workspace/git-porcelain.ts +0 -66
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +0 -258
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +0 -78
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +0 -214
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +0 -102
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +0 -199
- /package/src/{calls → live-voice}/progress-phrases.ts +0 -0
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* The voice front door owns hold, escalation, and direct-response behavior.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type { LiveVoiceProgressConfig } from "../config/schemas/live-voice.js";
|
|
9
9
|
import {
|
|
10
10
|
extractToolUse,
|
|
11
11
|
getConfiguredProvider,
|
|
@@ -216,7 +216,7 @@ export function effectiveSpokenTextMaxChars(
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
export function createVoiceProgressNarrator(options: {
|
|
219
|
-
config:
|
|
219
|
+
config: LiveVoiceProgressConfig;
|
|
220
220
|
/** Provider resolver, injectable for tests. */
|
|
221
221
|
getProvider?: () => Promise<Provider | null>;
|
|
222
222
|
}): VoiceProgressNarrator {
|
|
@@ -627,6 +627,8 @@ export interface LiveVoiceMetricsServerFrame extends LiveVoiceServerFrameBase {
|
|
|
627
627
|
* condition as the two fields above.
|
|
628
628
|
*/
|
|
629
629
|
readonly endpointDecisionSource?: "front-door" | "provider";
|
|
630
|
+
/** Which floor-holding ack actually spoke during the turn, if any. */
|
|
631
|
+
readonly ackSpoken?: "first_delta" | "tool_use";
|
|
630
632
|
/**
|
|
631
633
|
* Spoken progress narrations during the turn. Present only when at least
|
|
632
634
|
* one progress update spoke (otherwise the field is absent, keeping frames
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* The properties worth pinning are the ones a refactor could quietly
|
|
5
5
|
* break, each of which fails silently rather than loudly:
|
|
6
6
|
*
|
|
7
|
-
* 1. Plugin servers reach the manager at all. Reading only
|
|
8
|
-
*
|
|
7
|
+
* 1. Plugin servers reach the manager at all. Reading only `config.mcp`
|
|
8
|
+
* leaves a plugin's tools missing with nothing logged.
|
|
9
9
|
* 2. A workspace entry of the same id wins, so a plugin cannot redirect a
|
|
10
10
|
* server the user configured by hand.
|
|
11
11
|
* 3. Every server is attributed. `source` is what `McpClient` reads to
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The MCP configuration the daemon actually runs.
|
|
3
3
|
*
|
|
4
|
-
* Two sources contribute servers: the workspace `
|
|
4
|
+
* Two sources contribute servers: the workspace `config.json`, which the
|
|
5
5
|
* user owns, and each installed plugin's `mcp.json`, which its author
|
|
6
6
|
* owns. Both must reach the MCP manager, so every consumer that starts
|
|
7
|
-
* servers builds its config through here rather than reading
|
|
8
|
-
*
|
|
7
|
+
* servers builds its config through here rather than reading
|
|
8
|
+
* `config.mcp` directly — a path that only reads the workspace half
|
|
9
9
|
* leaves a plugin's tools silently missing.
|
|
10
10
|
*
|
|
11
11
|
* Every server comes out attributed with its {@link McpServerSource}, so
|
|
@@ -3,21 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Follows the same pattern as mcp-oauth-provider.ts: headers are stored in
|
|
5
5
|
* the secure credential store (CES or encrypted file fallback) rather than
|
|
6
|
-
* in plaintext
|
|
6
|
+
* in plaintext config.json, keeping secrets out of workspace config files.
|
|
7
7
|
*
|
|
8
8
|
* Key format: mcp:{serverId}:headers — stores JSON-serialized Record<string, string>.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { loadRawConfig, saveRawConfig } from "../config/loader.js";
|
|
11
12
|
import {
|
|
12
13
|
deleteSecureKeyAsync,
|
|
13
14
|
getSecureKeyAsync,
|
|
14
15
|
setSecureKeyAsync,
|
|
15
16
|
} from "../security/secure-keys.js";
|
|
16
17
|
import { getLogger } from "../util/logger.js";
|
|
17
|
-
import {
|
|
18
|
-
loadWorkspaceMcpConfig,
|
|
19
|
-
saveWorkspaceMcpConfig,
|
|
20
|
-
} from "./workspace-mcp-config.js";
|
|
21
18
|
|
|
22
19
|
const log = getLogger("mcp-header-store");
|
|
23
20
|
|
|
@@ -76,45 +73,64 @@ export async function deleteMcpHeaders(serverId: string): Promise<boolean> {
|
|
|
76
73
|
}
|
|
77
74
|
|
|
78
75
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
76
|
+
* One-time lazy migration: move any plaintext headers from config.json
|
|
77
|
+
* transport entries into the credential store and strip them from config.
|
|
78
|
+
* Safe to call on every MCP reload — no-ops when no legacy headers remain.
|
|
81
79
|
*/
|
|
82
80
|
export async function migrateLegacyMcpHeaders(): Promise<void> {
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
81
|
+
const raw = loadRawConfig();
|
|
82
|
+
const mcpConfig = raw.mcp as
|
|
83
|
+
| { servers?: Record<string, Record<string, unknown>> }
|
|
84
|
+
| undefined;
|
|
85
|
+
const servers = mcpConfig?.servers;
|
|
86
|
+
if (!servers) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
86
89
|
|
|
90
|
+
let configDirty = false;
|
|
87
91
|
for (const [id, server] of Object.entries(servers)) {
|
|
88
|
-
const transport = server
|
|
89
|
-
if (
|
|
92
|
+
const transport = server?.transport as Record<string, unknown> | undefined;
|
|
93
|
+
if (
|
|
94
|
+
!transport ||
|
|
95
|
+
(transport.type !== "sse" && transport.type !== "streamable-http")
|
|
96
|
+
) {
|
|
90
97
|
continue;
|
|
91
98
|
}
|
|
92
|
-
const
|
|
93
|
-
|
|
99
|
+
const legacyHeaders = transport.headers as
|
|
100
|
+
| Record<string, string>
|
|
101
|
+
| undefined;
|
|
102
|
+
if (!legacyHeaders || Object.keys(legacyHeaders).length === 0) {
|
|
94
103
|
continue;
|
|
95
104
|
}
|
|
96
105
|
|
|
106
|
+
// Only migrate if credential store doesn't already have headers for
|
|
107
|
+
// this server (idempotent — safe to re-run after partial failure).
|
|
97
108
|
const existing = await getMcpHeaders(id);
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
if (existing) {
|
|
110
|
+
// Credential store already has headers; just strip the config copy.
|
|
111
|
+
delete transport.headers;
|
|
112
|
+
configDirty = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const ok = await setMcpHeaders(id, legacyHeaders);
|
|
117
|
+
if (ok) {
|
|
118
|
+
delete transport.headers;
|
|
119
|
+
configDirty = true;
|
|
107
120
|
log.info(
|
|
108
121
|
{ serverId: id },
|
|
109
|
-
"
|
|
122
|
+
"Migrated legacy MCP headers to credential store",
|
|
123
|
+
);
|
|
124
|
+
} else {
|
|
125
|
+
log.warn(
|
|
126
|
+
{ serverId: id },
|
|
127
|
+
"Skipping legacy header migration — credential store write failed; will retry on next reload",
|
|
110
128
|
);
|
|
111
129
|
}
|
|
112
|
-
delete transport.headers;
|
|
113
|
-
dirty = true;
|
|
114
130
|
}
|
|
115
131
|
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
log.info("
|
|
132
|
+
if (configDirty) {
|
|
133
|
+
saveRawConfig(raw);
|
|
134
|
+
log.info("Config updated: legacy MCP headers removed after migration");
|
|
119
135
|
}
|
|
120
136
|
}
|
package/src/mcp/startup.ts
CHANGED
|
@@ -13,14 +13,13 @@
|
|
|
13
13
|
* further process that starts hosting turns has one call to make.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { invalidateConfigCache } from "../config/loader.js";
|
|
16
|
+
import { getConfig, invalidateConfigCache } from "../config/loader.js";
|
|
17
17
|
import type { McpConfig } from "../config/schemas/mcp.js";
|
|
18
18
|
import { createMcpToolsFromServer } from "../tools/mcp/mcp-tool-factory.js";
|
|
19
19
|
import { registerMcpTools, unregisterAllMcpTools } from "../tools/registry.js";
|
|
20
20
|
import { getLogger } from "../util/logger.js";
|
|
21
21
|
import { buildEffectiveMcpConfig } from "./effective-config.js";
|
|
22
22
|
import { getMcpServerManager, stopMcpServerManager } from "./manager.js";
|
|
23
|
-
import { loadWorkspaceMcpConfig } from "./workspace-mcp-config.js";
|
|
24
23
|
|
|
25
24
|
const log = getLogger("mcp-startup");
|
|
26
25
|
|
|
@@ -37,8 +36,7 @@ const log = getLogger("mcp-startup");
|
|
|
37
36
|
* is a startup step in processes whose other work must not be held hostage to
|
|
38
37
|
* a third-party server being up.
|
|
39
38
|
*
|
|
40
|
-
* @param workspaceMcpConfig The
|
|
41
|
-
* that omit it read `/workspace/mcp.json`.
|
|
39
|
+
* @param workspaceMcpConfig The `mcp` block of the assistant config, if any.
|
|
42
40
|
* @returns The number of tools registered across all servers.
|
|
43
41
|
*/
|
|
44
42
|
export async function startConfiguredMcpServers(
|
|
@@ -46,9 +44,7 @@ export async function startConfiguredMcpServers(
|
|
|
46
44
|
): Promise<number> {
|
|
47
45
|
let registered = 0;
|
|
48
46
|
try {
|
|
49
|
-
const mcpConfig = buildEffectiveMcpConfig(
|
|
50
|
-
workspaceMcpConfig ?? loadWorkspaceMcpConfig(),
|
|
51
|
-
);
|
|
47
|
+
const mcpConfig = buildEffectiveMcpConfig(workspaceMcpConfig);
|
|
52
48
|
if (Object.keys(mcpConfig.servers).length === 0) {
|
|
53
49
|
return 0;
|
|
54
50
|
}
|
|
@@ -101,5 +97,5 @@ export async function restartConfiguredMcpServers(): Promise<number> {
|
|
|
101
97
|
}
|
|
102
98
|
unregisterAllMcpTools();
|
|
103
99
|
invalidateConfigCache();
|
|
104
|
-
return startConfiguredMcpServers(
|
|
100
|
+
return startConfiguredMcpServers(getConfig().mcp);
|
|
105
101
|
}
|
|
@@ -56,22 +56,15 @@ export interface MessagingProvider {
|
|
|
56
56
|
query: string,
|
|
57
57
|
options?: SearchOptions,
|
|
58
58
|
): Promise<SearchResult>;
|
|
59
|
-
|
|
60
|
-
// ── Optional operations (platforms implement what they support) ───
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Send through the provider's own API. A provider whose channel has a
|
|
64
|
-
* direct transport omits this: its sends go through the transport, which
|
|
65
|
-
* is the one send implementation per addressable channel, and the
|
|
66
|
-
* messaging tool routes there before it looks for this method.
|
|
67
|
-
*/
|
|
68
|
-
sendMessage?(
|
|
59
|
+
sendMessage(
|
|
69
60
|
connection: OAuthConnection | undefined,
|
|
70
61
|
conversationId: string,
|
|
71
62
|
text: string,
|
|
72
63
|
options?: SendOptions,
|
|
73
64
|
): Promise<SendResult>;
|
|
74
65
|
|
|
66
|
+
// ── Optional operations (platforms implement what they support) ───
|
|
67
|
+
|
|
75
68
|
getThreadReplies?(
|
|
76
69
|
connection: OAuthConnection | undefined,
|
|
77
70
|
conversationId: string,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ChannelId } from "../../channels/types.js";
|
|
2
|
-
import type { CallbackContext } from "./channel-transport.js";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Channels whose outbound replies the assistant delivers directly to the
|
|
@@ -23,23 +22,6 @@ export type DirectDeliveryChannel = (typeof DIRECT_DELIVERY_CHANNELS)[number];
|
|
|
23
22
|
|
|
24
23
|
const CALLBACK_PREFIX = "/deliver/";
|
|
25
24
|
|
|
26
|
-
/**
|
|
27
|
-
* The callback context a channel's operations read for a direct delivery
|
|
28
|
-
* that no inbound message brought a callback for: the channel's `/deliver`
|
|
29
|
-
* path with its per-channel params, spelled the way `channelForCallback`
|
|
30
|
-
* resolves it and the way the transports read it.
|
|
31
|
-
*/
|
|
32
|
-
export function directDeliveryContext(
|
|
33
|
-
channel: DirectDeliveryChannel,
|
|
34
|
-
params: Readonly<Record<string, string>> = {},
|
|
35
|
-
): CallbackContext {
|
|
36
|
-
const query = new URLSearchParams(params).toString();
|
|
37
|
-
return {
|
|
38
|
-
callbackUrl: `${CALLBACK_PREFIX}${channel}${query ? `?${query}` : ""}`,
|
|
39
|
-
params,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
25
|
/**
|
|
44
26
|
* Resolve a gateway callback URL to the direct-delivery channel that owns it, or
|
|
45
27
|
* `undefined` when no channel delivers it directly.
|
|
@@ -97,35 +97,6 @@ export interface ReactionTarget {
|
|
|
97
97
|
readonly action: "add" | "remove";
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
/**
|
|
101
|
-
* Where a proactive send goes, in the caller's vocabulary rather than any
|
|
102
|
-
* channel's: a chat the caller already knows, optionally a thread within it.
|
|
103
|
-
* How a thread is spelled is each channel's own business.
|
|
104
|
-
*
|
|
105
|
-
* Tagged with its kind so a second way of naming a destination is additive
|
|
106
|
-
* rather than a reinterpretation of `chatId`. Reaching a person who has not
|
|
107
|
-
* been named as a chat is deliberately not a kind here: on Slack and Discord
|
|
108
|
-
* it costs a platform call whose result must be resolved at send time rather
|
|
109
|
-
* than stored, and nothing asks for it yet.
|
|
110
|
-
*/
|
|
111
|
-
export type ProactiveTarget = {
|
|
112
|
-
readonly kind: "chat";
|
|
113
|
-
readonly chatId: string;
|
|
114
|
-
readonly threadId?: string;
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* A proactive target resolved into what this channel's operations read: the
|
|
119
|
-
* callback context carrying the channel's routing state, the chat id
|
|
120
|
-
* `deliver` posts to, and the thread the post lands in, absent when it is
|
|
121
|
-
* not in one.
|
|
122
|
-
*/
|
|
123
|
-
export interface ProactiveAddress {
|
|
124
|
-
readonly ctx: CallbackContext;
|
|
125
|
-
readonly chatId: string;
|
|
126
|
-
readonly threadId?: string;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
100
|
/**
|
|
130
101
|
* Direct outbound delivery for one channel, wrapping the channel's provider-API
|
|
131
102
|
* send functions behind a uniform surface. Transports are registered statically
|
|
@@ -140,33 +111,6 @@ export interface ChannelTransport {
|
|
|
140
111
|
/** Canonical source channel id, e.g. `"slack"`. */
|
|
141
112
|
readonly channel: ChannelId;
|
|
142
113
|
|
|
143
|
-
/**
|
|
144
|
-
* Resolve a target the caller names into this channel's own routing state,
|
|
145
|
-
* for a send that no inbound message brought a callback for.
|
|
146
|
-
*
|
|
147
|
-
* Implementing it is the whole of declaring that the channel can be
|
|
148
|
-
* addressed from a named chat. A transport that can only answer on the
|
|
149
|
-
* callback an inbound message carried omits it, and a caller reads the
|
|
150
|
-
* omission as "not addressable" rather than switching on the channel's
|
|
151
|
-
* name. Returning `undefined` for a target this channel cannot address is
|
|
152
|
-
* the same answer for that target alone.
|
|
153
|
-
*
|
|
154
|
-
* Resolution is local: it reads the target and the channel's own
|
|
155
|
-
* vocabulary, and makes no platform call. A destination that has to be
|
|
156
|
-
* resolved over the network belongs behind its own capability, so this one
|
|
157
|
-
* stays cheap enough to call on every send.
|
|
158
|
-
*/
|
|
159
|
-
addressFor?(target: ProactiveTarget): ProactiveAddress | undefined;
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Whether a proactive send to a chat must also bind that chat's inbound
|
|
163
|
-
* conversation, so the next message from the chat resolves to the same
|
|
164
|
-
* conversation the post lives in. Declared by a channel whose inbound
|
|
165
|
-
* conversations are keyed per chat and can be reset between sends; omitted
|
|
166
|
-
* by one whose inbound binding is made at ingress and keyed by thread.
|
|
167
|
-
*/
|
|
168
|
-
readonly bindsChatOnProactiveSend?: boolean;
|
|
169
|
-
|
|
170
114
|
/** Deliver a rendered reply (text / approval / attachments). */
|
|
171
115
|
deliver(
|
|
172
116
|
ctx: CallbackContext,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ChannelDeliveryError } from "@vellumai/gateway-client/http-delivery";
|
|
2
2
|
|
|
3
3
|
import { getLogger } from "../../../util/logger.js";
|
|
4
|
-
import { directDeliveryContext } from "../callback-routing.js";
|
|
5
4
|
import type {
|
|
6
5
|
CallbackContext,
|
|
7
6
|
ChannelTransport,
|
|
@@ -52,22 +51,6 @@ async function sendTarget(
|
|
|
52
51
|
export const discordTransport: ChannelTransport = {
|
|
53
52
|
channel: "discord",
|
|
54
53
|
|
|
55
|
-
/**
|
|
56
|
-
* A chat is a channel id, with `threadId` naming the thread, which is its
|
|
57
|
-
* own channel. Reaching a person who has not been named as a chat would
|
|
58
|
-
* mean opening their DM first, a platform call this resolution does not
|
|
59
|
-
* make; the inbound `dm` param below is how an event-carried callback says
|
|
60
|
-
* its `chatId` is a recipient rather than a room.
|
|
61
|
-
*/
|
|
62
|
-
addressFor(target) {
|
|
63
|
-
const threadId = target.threadId?.trim();
|
|
64
|
-
return {
|
|
65
|
-
ctx: directDeliveryContext("discord", threadId ? { threadId } : {}),
|
|
66
|
-
chatId: target.chatId,
|
|
67
|
-
...(threadId ? { threadId } : {}),
|
|
68
|
-
};
|
|
69
|
-
},
|
|
70
|
-
|
|
71
54
|
// Discord clears a typing indicator after ten seconds.
|
|
72
55
|
activityRefreshMs: 8_000,
|
|
73
56
|
|
|
@@ -162,11 +162,10 @@ describe("Slack adapter token routing", () => {
|
|
|
162
162
|
globalThis.fetch = originalFetch;
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
-
test("bot-token only: reads authenticate with the bot token (regression guard for pre-user-token behavior)", async () => {
|
|
165
|
+
test("bot-token only: reads and writes both authenticate with the bot token (regression guard for pre-user-token behavior)", async () => {
|
|
166
166
|
// With only a bot token stored, reads must fall back to the bot token
|
|
167
167
|
// so the adapter keeps working for installs that haven't re-consented
|
|
168
|
-
// the user scope. Writes
|
|
169
|
-
// channel transport's senders, which always post as the bot.
|
|
168
|
+
// the user scope. Writes stay on the bot token always.
|
|
170
169
|
const resolved = await slackProvider.resolveConnection!();
|
|
171
170
|
expect(resolved).toBeUndefined();
|
|
172
171
|
|
|
@@ -177,12 +176,18 @@ describe("Slack adapter token routing", () => {
|
|
|
177
176
|
);
|
|
178
177
|
expect(readCall).toBeDefined();
|
|
179
178
|
expect(readCall!.authorization).toBe(`Bearer ${BOT_TOKEN}`);
|
|
179
|
+
|
|
180
|
+
// Write path: sendMessage → /chat.postMessage must also use bot token.
|
|
181
|
+
await slackProvider.sendMessage(undefined, "C123", "hello");
|
|
182
|
+
const writeCall = captured.find((c) => c.url.includes("/chat.postMessage"));
|
|
183
|
+
expect(writeCall).toBeDefined();
|
|
184
|
+
expect(writeCall!.authorization).toBe(`Bearer ${BOT_TOKEN}`);
|
|
180
185
|
});
|
|
181
186
|
|
|
182
|
-
test("bot + user tokens: reads authenticate with the user token", async () => {
|
|
187
|
+
test("bot + user tokens: reads authenticate with the user token, writes with the bot token", async () => {
|
|
183
188
|
// With both tokens stored, reads MUST flip to the user token so the
|
|
184
|
-
// adapter can see channels the user is in but the bot isn't.
|
|
185
|
-
// the
|
|
189
|
+
// adapter can see channels the user is in but the bot isn't. Writes
|
|
190
|
+
// MUST stay on the bot token so posts come from the bot identity.
|
|
186
191
|
getSecureKeyAsyncMock.mockImplementation(async (key: string) => {
|
|
187
192
|
if (key === credentialKey("slack_channel", "bot_token")) {
|
|
188
193
|
return BOT_TOKEN;
|
|
@@ -212,6 +217,12 @@ describe("Slack adapter token routing", () => {
|
|
|
212
217
|
expect(historyCall).toBeDefined();
|
|
213
218
|
expect(historyCall!.authorization).toBe(`Bearer ${USER_TOKEN}`);
|
|
214
219
|
|
|
220
|
+
// Writes: sendMessage → bot token.
|
|
221
|
+
await slackProvider.sendMessage(undefined, "C123", "hello");
|
|
222
|
+
const sendCall = captured.find((c) => c.url.includes("/chat.postMessage"));
|
|
223
|
+
expect(sendCall).toBeDefined();
|
|
224
|
+
expect(sendCall!.authorization).toBe(`Bearer ${BOT_TOKEN}`);
|
|
225
|
+
|
|
215
226
|
// markRead → user token. conversations.mark sets the read cursor for
|
|
216
227
|
// the authenticated identity, so it must use the same token whose unread
|
|
217
228
|
// counts the adapter exposes.
|
|
@@ -28,6 +28,8 @@ import type {
|
|
|
28
28
|
Message,
|
|
29
29
|
SearchOptions,
|
|
30
30
|
SearchResult,
|
|
31
|
+
SendOptions,
|
|
32
|
+
SendResult,
|
|
31
33
|
} from "../../provider-types.js";
|
|
32
34
|
import { resolveSlackAuth } from "./auth.js";
|
|
33
35
|
import * as slack from "./client.js";
|
|
@@ -142,12 +144,24 @@ function getReadAuth(connection?: OAuthConnection): OAuthConnection | string {
|
|
|
142
144
|
return getSlackAuth(connection);
|
|
143
145
|
}
|
|
144
146
|
|
|
145
|
-
// SAFETY: content-creating writes (
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
|
|
147
|
+
// SAFETY: content-creating writes (postMessage, updateMessage, deleteMessage,
|
|
148
|
+
// reactions) MUST use the bot token. Using the user token would post as the
|
|
149
|
+
// user, not as the bot. State-changing methods that target the authenticated
|
|
150
|
+
// identity's own state (e.g. conversations.mark) should use the read auth so
|
|
151
|
+
// the cursor matches the perspective the adapter exposes.
|
|
152
|
+
/**
|
|
153
|
+
* Resolve auth for content-creating write operations (postMessage and any
|
|
154
|
+
* future reactions, joins, leaves, updates, or deletes).
|
|
155
|
+
*/
|
|
156
|
+
function getWriteAuth(connection?: OAuthConnection): OAuthConnection | string {
|
|
157
|
+
if (connection) {
|
|
158
|
+
return connection;
|
|
159
|
+
}
|
|
160
|
+
if (_cachedSlackWriteAuth) {
|
|
161
|
+
return _cachedSlackWriteAuth;
|
|
162
|
+
}
|
|
163
|
+
return getSlackAuth(connection);
|
|
164
|
+
}
|
|
151
165
|
|
|
152
166
|
/**
|
|
153
167
|
* Resolve the bot token (raw string) and pass it to `fn`. Returns the
|
|
@@ -157,7 +171,7 @@ function getReadAuth(connection?: OAuthConnection): OAuthConnection | string {
|
|
|
157
171
|
* (`OAuthConnection.withToken`) for callers that need a raw token to hand
|
|
158
172
|
* to a non-Slack-client API call — currently `downloadSlackFile` for inline
|
|
159
173
|
* file/image fetches. Slack-client method calls should keep going through
|
|
160
|
-
* `getReadAuth` and pass the union through.
|
|
174
|
+
* `getReadAuth` / `getWriteAuth` and pass the union through.
|
|
161
175
|
*/
|
|
162
176
|
export async function withSlackBotToken<T>(
|
|
163
177
|
account: string | undefined,
|
|
@@ -816,6 +830,29 @@ export const slackProvider: MessagingProvider = {
|
|
|
816
830
|
};
|
|
817
831
|
},
|
|
818
832
|
|
|
833
|
+
async sendMessage(
|
|
834
|
+
connection: OAuthConnection | undefined,
|
|
835
|
+
conversationId: string,
|
|
836
|
+
text: string,
|
|
837
|
+
options?: SendOptions,
|
|
838
|
+
): Promise<SendResult> {
|
|
839
|
+
const auth = getWriteAuth(connection);
|
|
840
|
+
const resp = await slack.postMessage(
|
|
841
|
+
auth,
|
|
842
|
+
conversationId,
|
|
843
|
+
text,
|
|
844
|
+
options?.threadId,
|
|
845
|
+
);
|
|
846
|
+
// The thread reported is the one the post landed in, which is the one
|
|
847
|
+
// requested, since chat.postMessage threads exactly where it is told.
|
|
848
|
+
return {
|
|
849
|
+
id: resp.ts,
|
|
850
|
+
timestamp: parseFloat(resp.ts) * 1000,
|
|
851
|
+
conversationId: resp.channel,
|
|
852
|
+
...(options?.threadId ? { threadId: options.threadId } : {}),
|
|
853
|
+
};
|
|
854
|
+
},
|
|
855
|
+
|
|
819
856
|
async getThreadReplies(
|
|
820
857
|
connection: OAuthConnection | undefined,
|
|
821
858
|
conversationId: string,
|
|
@@ -3,7 +3,6 @@ import { ChannelDeliveryError } from "@vellumai/gateway-client/http-delivery";
|
|
|
3
3
|
|
|
4
4
|
import { extractThreadTsFromCallbackUrl } from "../../../channels/slack-callback-url.js";
|
|
5
5
|
import { getLogger } from "../../../util/logger.js";
|
|
6
|
-
import { directDeliveryContext } from "../callback-routing.js";
|
|
7
6
|
import type { ChannelTransport } from "../channel-transport.js";
|
|
8
7
|
import { SLACK_STREAM_MARKDOWN_LIMIT } from "./api.js";
|
|
9
8
|
import {
|
|
@@ -26,21 +25,6 @@ function mutedBlocks(text: string): KnownBlock[] {
|
|
|
26
25
|
export const slackTransport: ChannelTransport = {
|
|
27
26
|
channel: "slack",
|
|
28
27
|
|
|
29
|
-
/**
|
|
30
|
-
* A chat is a channel or DM id, with `threadTs` naming the thread to post
|
|
31
|
-
* under. Reaching a person who has not been named as a chat would mean
|
|
32
|
-
* opening the DM first (`conversations.open`), a platform call this
|
|
33
|
-
* resolution does not make.
|
|
34
|
-
*/
|
|
35
|
-
addressFor(target) {
|
|
36
|
-
const threadTs = target.threadId?.trim();
|
|
37
|
-
return {
|
|
38
|
-
ctx: directDeliveryContext("slack", threadTs ? { threadTs } : {}),
|
|
39
|
-
chatId: target.chatId,
|
|
40
|
-
...(threadTs ? { threadId: threadTs } : {}),
|
|
41
|
-
};
|
|
42
|
-
},
|
|
43
|
-
|
|
44
28
|
describeReactionEmoji: describeSlackReactionEmoji,
|
|
45
29
|
|
|
46
30
|
async react(target) {
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import type { OAuthConnection } from "../../../oauth/connection.js";
|
|
8
8
|
import { getConnectionByProvider } from "../../../oauth/oauth-store.js";
|
|
9
|
+
import { getOrCreateConversation } from "../../../persistence/conversation-key-store.js";
|
|
10
|
+
import { buildScopedConversationKey } from "../../../persistence/delivery-crud.js";
|
|
11
|
+
import { upsertOutboundBinding } from "../../../persistence/external-conversation-store.js";
|
|
9
12
|
import { credentialKey } from "../../../security/credential-key.js";
|
|
10
13
|
import { getSecureKeyAsync } from "../../../security/secure-keys.js";
|
|
11
14
|
import type { MessagingProvider } from "../../provider.js";
|
|
@@ -17,6 +20,8 @@ import type {
|
|
|
17
20
|
Message,
|
|
18
21
|
SearchOptions,
|
|
19
22
|
SearchResult,
|
|
23
|
+
SendOptions,
|
|
24
|
+
SendResult,
|
|
20
25
|
} from "../../provider-types.js";
|
|
21
26
|
import * as telegram from "./client.js";
|
|
22
27
|
|
|
@@ -29,7 +34,7 @@ export const telegramBotMessagingProvider: MessagingProvider = {
|
|
|
29
34
|
id: "telegram",
|
|
30
35
|
displayName: "Telegram",
|
|
31
36
|
credentialService: "telegram",
|
|
32
|
-
capabilities: new Set(),
|
|
37
|
+
capabilities: new Set(["send"]),
|
|
33
38
|
|
|
34
39
|
async isConnected(): Promise<boolean> {
|
|
35
40
|
const conn = getConnectionByProvider("telegram");
|
|
@@ -90,6 +95,48 @@ export const telegramBotMessagingProvider: MessagingProvider = {
|
|
|
90
95
|
}
|
|
91
96
|
},
|
|
92
97
|
|
|
98
|
+
async sendMessage(
|
|
99
|
+
_connection: OAuthConnection | undefined,
|
|
100
|
+
conversationId: string,
|
|
101
|
+
text: string,
|
|
102
|
+
_options?: SendOptions,
|
|
103
|
+
): Promise<SendResult> {
|
|
104
|
+
const sent = await telegram.sendMessage(conversationId, text);
|
|
105
|
+
if (!sent.lastMessageId) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
"Telegram accepted the message but returned no message id",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Upsert external conversation binding so deleted/reset syncs are
|
|
112
|
+
// resurrected when an outbound message is sent. This ensures the
|
|
113
|
+
// conversation key mapping and binding exist for the next inbound.
|
|
114
|
+
try {
|
|
115
|
+
const sourceChannel = "telegram";
|
|
116
|
+
const conversationKey = buildScopedConversationKey(
|
|
117
|
+
sourceChannel,
|
|
118
|
+
conversationId,
|
|
119
|
+
);
|
|
120
|
+
const { conversationId: internalId } =
|
|
121
|
+
getOrCreateConversation(conversationKey);
|
|
122
|
+
upsertOutboundBinding({
|
|
123
|
+
conversationId: internalId,
|
|
124
|
+
sourceChannel,
|
|
125
|
+
externalChatId: conversationId,
|
|
126
|
+
});
|
|
127
|
+
} catch {
|
|
128
|
+
// Best-effort — don't fail the send if binding upsert fails
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// The id is the one Telegram assigned, so a later reaction, edit, or
|
|
132
|
+
// delete carrying that id resolves to this send.
|
|
133
|
+
return {
|
|
134
|
+
id: sent.lastMessageId,
|
|
135
|
+
timestamp: Date.now(),
|
|
136
|
+
conversationId,
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
|
|
93
140
|
async listConversations(
|
|
94
141
|
_connection?: OAuthConnection,
|
|
95
142
|
_options?: ListOptions,
|
|
@@ -41,3 +41,29 @@ export async function getMe(botToken: string): Promise<TelegramGetMeResponse> {
|
|
|
41
41
|
|
|
42
42
|
return resp.json() as Promise<TelegramGetMeResponse>;
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
/** Result returned by sendMessage. */
|
|
46
|
+
export interface TelegramSendResult {
|
|
47
|
+
ok: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The `message_id` Telegram assigned to the last chunk sent. Telegram
|
|
50
|
+
* returns the sent Message on every successful `sendMessage`, so this is
|
|
51
|
+
* absent only when the API answered with an unexpected shape.
|
|
52
|
+
*/
|
|
53
|
+
lastMessageId?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Send a Telegram text message via the Bot API directly.
|
|
58
|
+
*
|
|
59
|
+
* Delegates to sendTelegramReply which handles text splitting (Telegram's
|
|
60
|
+
* sendMessage API has a 4096-char limit per call).
|
|
61
|
+
*/
|
|
62
|
+
export async function sendMessage(
|
|
63
|
+
chatId: string,
|
|
64
|
+
text: string,
|
|
65
|
+
): Promise<TelegramSendResult> {
|
|
66
|
+
const { sendTelegramReply } = await import("./send.js");
|
|
67
|
+
const sent = await sendTelegramReply(chatId, text);
|
|
68
|
+
return { ok: true, lastMessageId: sent.lastMessageId };
|
|
69
|
+
}
|