@vellumai/assistant 0.12.1 → 0.12.2-staging.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 +4 -0
- package/ARCHITECTURE.md +20 -21
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/openapi.yaml +327 -52
- 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-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- 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 +212 -3
- package/src/__tests__/credential-security-invariants.test.ts +4 -2
- package/src/__tests__/cu-unified-flow.test.ts +8 -2
- 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 +539 -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 +113 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +18 -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__/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 +19 -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__/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__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -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__/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/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/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/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 +58 -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 +37 -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 +53 -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 +59 -46
- package/src/calls/voice-triage-escalate.ts +175 -56
- 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/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 +20 -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 +24 -12
- package/src/config/loader.ts +1 -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 +8 -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 +2 -1
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +15 -9
- package/src/daemon/conversation-tool-setup.ts +37 -43
- 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 +186 -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-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -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__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/live-voice-photo.ts +25 -6
- package/src/live-voice/live-voice-session.ts +213 -693
- package/src/live-voice/protocol.ts +0 -2
- 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 +54 -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 +73 -0
- package/src/oauth/connection-resolver.ts +95 -2
- package/src/oauth/scope-utils.ts +24 -0
- package/src/oauth/seed-providers.ts +204 -25
- 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-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -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-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +73 -0
- 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 +19 -10
- 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 +36 -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/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-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
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
// The addressing capability resolves a named chat into exactly what each
|
|
3
|
+
// transport's own operations read: the callback URL `channelForCallback`
|
|
4
|
+
// resolves and the params `deliver` consumes. Resolution is local, so these
|
|
5
|
+
// assertions make no platform call; the tests import the transports'
|
|
6
|
+
// modules, which pull in their provider-API senders, so the senders'
|
|
7
|
+
// credential lookups are stubbed to keep the import inert.
|
|
8
|
+
import { mock } from "bun:test";
|
|
9
|
+
|
|
10
|
+
const actualSecureKeys = await import("../../../security/secure-keys.js");
|
|
11
|
+
mock.module("../../../security/secure-keys.js", () => ({
|
|
12
|
+
...actualSecureKeys,
|
|
13
|
+
getSecureKeyAsync: async () => null,
|
|
14
|
+
getSecureKeyResultAsync: async () => ({ value: null }),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const { channelForCallback } = await import("../callback-routing.js");
|
|
18
|
+
const { discordTransport } = await import("../discord/transport.js");
|
|
19
|
+
const { slackTransport } = await import("../slack/transport.js");
|
|
20
|
+
const { telegramTransport } = await import("../telegram-bot/transport.js");
|
|
21
|
+
const { whatsappTransport } = await import("../whatsapp/transport.js");
|
|
22
|
+
|
|
23
|
+
describe("addressFor resolves to the channel's own routing state", () => {
|
|
24
|
+
test("slack: a chat, optionally under a thread", () => {
|
|
25
|
+
const room = slackTransport.addressFor!({ kind: "chat", chatId: "C123" });
|
|
26
|
+
expect(room).toEqual({
|
|
27
|
+
ctx: { callbackUrl: "/deliver/slack", params: {} },
|
|
28
|
+
chatId: "C123",
|
|
29
|
+
});
|
|
30
|
+
const thread = slackTransport.addressFor!({
|
|
31
|
+
kind: "chat",
|
|
32
|
+
chatId: "C123",
|
|
33
|
+
threadId: "1690000000.000001",
|
|
34
|
+
});
|
|
35
|
+
expect(thread).toEqual({
|
|
36
|
+
ctx: {
|
|
37
|
+
callbackUrl: "/deliver/slack?threadTs=1690000000.000001",
|
|
38
|
+
params: { threadTs: "1690000000.000001" },
|
|
39
|
+
},
|
|
40
|
+
chatId: "C123",
|
|
41
|
+
threadId: "1690000000.000001",
|
|
42
|
+
});
|
|
43
|
+
expect(channelForCallback(thread!.ctx.callbackUrl)).toBe("slack");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("telegram: a chat with a topic; binds on send", () => {
|
|
47
|
+
expect(
|
|
48
|
+
telegramTransport.addressFor!({
|
|
49
|
+
kind: "chat",
|
|
50
|
+
chatId: "123",
|
|
51
|
+
threadId: "42",
|
|
52
|
+
}),
|
|
53
|
+
).toEqual({
|
|
54
|
+
ctx: {
|
|
55
|
+
callbackUrl: "/deliver/telegram?threadId=42",
|
|
56
|
+
params: { threadId: "42" },
|
|
57
|
+
},
|
|
58
|
+
chatId: "123",
|
|
59
|
+
threadId: "42",
|
|
60
|
+
});
|
|
61
|
+
expect(telegramTransport.bindsChatOnProactiveSend).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("discord: a chat with a thread, which is its own channel", () => {
|
|
65
|
+
const thread = discordTransport.addressFor!({
|
|
66
|
+
kind: "chat",
|
|
67
|
+
chatId: "C1",
|
|
68
|
+
threadId: "T1",
|
|
69
|
+
});
|
|
70
|
+
expect(thread).toEqual({
|
|
71
|
+
ctx: {
|
|
72
|
+
callbackUrl: "/deliver/discord?threadId=T1",
|
|
73
|
+
params: { threadId: "T1" },
|
|
74
|
+
},
|
|
75
|
+
chatId: "C1",
|
|
76
|
+
threadId: "T1",
|
|
77
|
+
});
|
|
78
|
+
expect(channelForCallback(thread!.ctx.callbackUrl)).toBe("discord");
|
|
79
|
+
expect(discordTransport.bindsChatOnProactiveSend).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("whatsapp: the number is the chat; no threads; binds on send", () => {
|
|
83
|
+
expect(
|
|
84
|
+
whatsappTransport.addressFor!({
|
|
85
|
+
kind: "chat",
|
|
86
|
+
chatId: "12125550100",
|
|
87
|
+
threadId: "ignored",
|
|
88
|
+
}),
|
|
89
|
+
).toEqual({
|
|
90
|
+
ctx: { callbackUrl: "/deliver/whatsapp", params: {} },
|
|
91
|
+
chatId: "12125550100",
|
|
92
|
+
});
|
|
93
|
+
expect(whatsappTransport.bindsChatOnProactiveSend).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ChannelId } from "../../channels/types.js";
|
|
2
|
+
import type { CallbackContext } from "./channel-transport.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Channels whose outbound replies the assistant delivers directly to the
|
|
@@ -22,6 +23,23 @@ export type DirectDeliveryChannel = (typeof DIRECT_DELIVERY_CHANNELS)[number];
|
|
|
22
23
|
|
|
23
24
|
const CALLBACK_PREFIX = "/deliver/";
|
|
24
25
|
|
|
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
|
+
|
|
25
43
|
/**
|
|
26
44
|
* Resolve a gateway callback URL to the direct-delivery channel that owns it, or
|
|
27
45
|
* `undefined` when no channel delivers it directly.
|
|
@@ -97,6 +97,35 @@ 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
|
+
|
|
100
129
|
/**
|
|
101
130
|
* Direct outbound delivery for one channel, wrapping the channel's provider-API
|
|
102
131
|
* send functions behind a uniform surface. Transports are registered statically
|
|
@@ -111,6 +140,33 @@ export interface ChannelTransport {
|
|
|
111
140
|
/** Canonical source channel id, e.g. `"slack"`. */
|
|
112
141
|
readonly channel: ChannelId;
|
|
113
142
|
|
|
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
|
+
|
|
114
170
|
/** Deliver a rendered reply (text / approval / attachments). */
|
|
115
171
|
deliver(
|
|
116
172
|
ctx: CallbackContext,
|
|
@@ -1,6 +1,7 @@
|
|
|
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";
|
|
4
5
|
import type {
|
|
5
6
|
CallbackContext,
|
|
6
7
|
ChannelTransport,
|
|
@@ -51,6 +52,22 @@ async function sendTarget(
|
|
|
51
52
|
export const discordTransport: ChannelTransport = {
|
|
52
53
|
channel: "discord",
|
|
53
54
|
|
|
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
|
+
|
|
54
71
|
// Discord clears a typing indicator after ten seconds.
|
|
55
72
|
activityRefreshMs: 8_000,
|
|
56
73
|
|
|
@@ -162,10 +162,11 @@ describe("Slack adapter token routing", () => {
|
|
|
162
162
|
globalThis.fetch = originalFetch;
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
-
test("bot-token only: reads
|
|
165
|
+
test("bot-token only: reads 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
|
|
168
|
+
// the user scope. Writes are not the adapter's: they go through the
|
|
169
|
+
// channel transport's senders, which always post as the bot.
|
|
169
170
|
const resolved = await slackProvider.resolveConnection!();
|
|
170
171
|
expect(resolved).toBeUndefined();
|
|
171
172
|
|
|
@@ -176,18 +177,12 @@ describe("Slack adapter token routing", () => {
|
|
|
176
177
|
);
|
|
177
178
|
expect(readCall).toBeDefined();
|
|
178
179
|
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}`);
|
|
185
180
|
});
|
|
186
181
|
|
|
187
|
-
test("bot + user tokens: reads authenticate with the user token
|
|
182
|
+
test("bot + user tokens: reads authenticate with the user token", async () => {
|
|
188
183
|
// With both tokens stored, reads MUST flip to the user token so the
|
|
189
|
-
// adapter can see channels the user is in but the bot isn't.
|
|
190
|
-
//
|
|
184
|
+
// adapter can see channels the user is in but the bot isn't. Posts are
|
|
185
|
+
// the transport's and come from the bot identity there.
|
|
191
186
|
getSecureKeyAsyncMock.mockImplementation(async (key: string) => {
|
|
192
187
|
if (key === credentialKey("slack_channel", "bot_token")) {
|
|
193
188
|
return BOT_TOKEN;
|
|
@@ -217,12 +212,6 @@ describe("Slack adapter token routing", () => {
|
|
|
217
212
|
expect(historyCall).toBeDefined();
|
|
218
213
|
expect(historyCall!.authorization).toBe(`Bearer ${USER_TOKEN}`);
|
|
219
214
|
|
|
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
|
-
|
|
226
215
|
// markRead → user token. conversations.mark sets the read cursor for
|
|
227
216
|
// the authenticated identity, so it must use the same token whose unread
|
|
228
217
|
// counts the adapter exposes.
|
|
@@ -28,8 +28,6 @@ import type {
|
|
|
28
28
|
Message,
|
|
29
29
|
SearchOptions,
|
|
30
30
|
SearchResult,
|
|
31
|
-
SendOptions,
|
|
32
|
-
SendResult,
|
|
33
31
|
} from "../../provider-types.js";
|
|
34
32
|
import { resolveSlackAuth } from "./auth.js";
|
|
35
33
|
import * as slack from "./client.js";
|
|
@@ -144,24 +142,12 @@ function getReadAuth(connection?: OAuthConnection): OAuthConnection | string {
|
|
|
144
142
|
return getSlackAuth(connection);
|
|
145
143
|
}
|
|
146
144
|
|
|
147
|
-
// SAFETY: content-creating writes (
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// identity's own state (e.g. conversations.mark)
|
|
151
|
-
// the cursor matches the perspective the adapter
|
|
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
|
-
}
|
|
145
|
+
// SAFETY: content-creating writes (posts, updates, deletes, reactions) go
|
|
146
|
+
// through the channel transport's senders, which authenticate with the bot
|
|
147
|
+
// token so posts come from the bot identity. State-changing methods that
|
|
148
|
+
// target the authenticated identity's own state (e.g. conversations.mark)
|
|
149
|
+
// use the read auth so the cursor matches the perspective the adapter
|
|
150
|
+
// exposes.
|
|
165
151
|
|
|
166
152
|
/**
|
|
167
153
|
* Resolve the bot token (raw string) and pass it to `fn`. Returns the
|
|
@@ -171,7 +157,7 @@ function getWriteAuth(connection?: OAuthConnection): OAuthConnection | string {
|
|
|
171
157
|
* (`OAuthConnection.withToken`) for callers that need a raw token to hand
|
|
172
158
|
* to a non-Slack-client API call — currently `downloadSlackFile` for inline
|
|
173
159
|
* file/image fetches. Slack-client method calls should keep going through
|
|
174
|
-
* `getReadAuth`
|
|
160
|
+
* `getReadAuth` and pass the union through.
|
|
175
161
|
*/
|
|
176
162
|
export async function withSlackBotToken<T>(
|
|
177
163
|
account: string | undefined,
|
|
@@ -830,29 +816,6 @@ export const slackProvider: MessagingProvider = {
|
|
|
830
816
|
};
|
|
831
817
|
},
|
|
832
818
|
|
|
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
|
-
|
|
856
819
|
async getThreadReplies(
|
|
857
820
|
connection: OAuthConnection | undefined,
|
|
858
821
|
conversationId: string,
|
|
@@ -3,6 +3,7 @@ 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";
|
|
6
7
|
import type { ChannelTransport } from "../channel-transport.js";
|
|
7
8
|
import { SLACK_STREAM_MARKDOWN_LIMIT } from "./api.js";
|
|
8
9
|
import {
|
|
@@ -25,6 +26,21 @@ function mutedBlocks(text: string): KnownBlock[] {
|
|
|
25
26
|
export const slackTransport: ChannelTransport = {
|
|
26
27
|
channel: "slack",
|
|
27
28
|
|
|
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
|
+
|
|
28
44
|
describeReactionEmoji: describeSlackReactionEmoji,
|
|
29
45
|
|
|
30
46
|
async react(target) {
|
|
@@ -6,9 +6,6 @@
|
|
|
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";
|
|
12
9
|
import { credentialKey } from "../../../security/credential-key.js";
|
|
13
10
|
import { getSecureKeyAsync } from "../../../security/secure-keys.js";
|
|
14
11
|
import type { MessagingProvider } from "../../provider.js";
|
|
@@ -20,8 +17,6 @@ import type {
|
|
|
20
17
|
Message,
|
|
21
18
|
SearchOptions,
|
|
22
19
|
SearchResult,
|
|
23
|
-
SendOptions,
|
|
24
|
-
SendResult,
|
|
25
20
|
} from "../../provider-types.js";
|
|
26
21
|
import * as telegram from "./client.js";
|
|
27
22
|
|
|
@@ -34,7 +29,7 @@ export const telegramBotMessagingProvider: MessagingProvider = {
|
|
|
34
29
|
id: "telegram",
|
|
35
30
|
displayName: "Telegram",
|
|
36
31
|
credentialService: "telegram",
|
|
37
|
-
capabilities: new Set(
|
|
32
|
+
capabilities: new Set(),
|
|
38
33
|
|
|
39
34
|
async isConnected(): Promise<boolean> {
|
|
40
35
|
const conn = getConnectionByProvider("telegram");
|
|
@@ -95,48 +90,6 @@ export const telegramBotMessagingProvider: MessagingProvider = {
|
|
|
95
90
|
}
|
|
96
91
|
},
|
|
97
92
|
|
|
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
|
-
|
|
140
93
|
async listConversations(
|
|
141
94
|
_connection?: OAuthConnection,
|
|
142
95
|
_options?: ListOptions,
|
|
@@ -41,29 +41,3 @@ 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
|
-
}
|
|
@@ -2,6 +2,7 @@ import type { StreamPlan, StreamPlanStep } from "@vellumai/gateway-client";
|
|
|
2
2
|
import { ChannelDeliveryError } from "@vellumai/gateway-client/http-delivery";
|
|
3
3
|
|
|
4
4
|
import { getLogger } from "../../../util/logger.js";
|
|
5
|
+
import { directDeliveryContext } from "../callback-routing.js";
|
|
5
6
|
import type {
|
|
6
7
|
CallbackContext,
|
|
7
8
|
ChannelTransport,
|
|
@@ -66,6 +67,25 @@ function mintDraftId(): number {
|
|
|
66
67
|
export const telegramTransport: ChannelTransport = {
|
|
67
68
|
channel: "telegram",
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* A chat is a chat id, with `threadId` naming a forum topic. A person's DM
|
|
72
|
+
* chat id is their user id on Telegram, so reaching one is naming that
|
|
73
|
+
* chat and needs no resolution of its own.
|
|
74
|
+
*/
|
|
75
|
+
addressFor(target) {
|
|
76
|
+
const threadId = target.threadId?.trim();
|
|
77
|
+
return {
|
|
78
|
+
ctx: directDeliveryContext("telegram", threadId ? { threadId } : {}),
|
|
79
|
+
chatId: target.chatId,
|
|
80
|
+
...(threadId ? { threadId } : {}),
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
// A Telegram chat's inbound conversation is keyed per chat and can be
|
|
85
|
+
// reset or deleted between sends; a proactive post re-binds it so the
|
|
86
|
+
// next inbound from the chat lands where the post lives.
|
|
87
|
+
bindsChatOnProactiveSend: true,
|
|
88
|
+
|
|
69
89
|
// Telegram clears a chat action after about five seconds.
|
|
70
90
|
activityRefreshMs: 4_000,
|
|
71
91
|
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { OAuthConnection } from "../../../oauth/connection.js";
|
|
8
|
-
import { getOrCreateConversation } from "../../../persistence/conversation-key-store.js";
|
|
9
|
-
import { upsertOutboundBinding } from "../../../persistence/external-conversation-store.js";
|
|
10
8
|
import { credentialKey } from "../../../security/credential-key.js";
|
|
11
9
|
import { getSecureKeyAsync } from "../../../security/secure-keys.js";
|
|
12
10
|
import type { MessagingProvider } from "../../provider.js";
|
|
@@ -18,10 +16,7 @@ import type {
|
|
|
18
16
|
Message,
|
|
19
17
|
SearchOptions,
|
|
20
18
|
SearchResult,
|
|
21
|
-
SendOptions,
|
|
22
|
-
SendResult,
|
|
23
19
|
} from "../../provider-types.js";
|
|
24
|
-
import * as whatsapp from "./client.js";
|
|
25
20
|
|
|
26
21
|
/** Check whether WhatsApp credentials are stored. */
|
|
27
22
|
async function hasWhatsAppCredentials(): Promise<boolean> {
|
|
@@ -41,7 +36,7 @@ export const whatsappMessagingProvider: MessagingProvider = {
|
|
|
41
36
|
id: "whatsapp",
|
|
42
37
|
displayName: "WhatsApp",
|
|
43
38
|
credentialService: "whatsapp",
|
|
44
|
-
capabilities: new Set(
|
|
39
|
+
capabilities: new Set(),
|
|
45
40
|
|
|
46
41
|
async isConnected(): Promise<boolean> {
|
|
47
42
|
return hasWhatsAppCredentials();
|
|
@@ -74,48 +69,6 @@ export const whatsappMessagingProvider: MessagingProvider = {
|
|
|
74
69
|
};
|
|
75
70
|
},
|
|
76
71
|
|
|
77
|
-
async sendMessage(
|
|
78
|
-
_connection: OAuthConnection | undefined,
|
|
79
|
-
conversationId: string,
|
|
80
|
-
text: string,
|
|
81
|
-
options?: SendOptions,
|
|
82
|
-
): Promise<SendResult> {
|
|
83
|
-
const assistantId = options?.assistantId;
|
|
84
|
-
|
|
85
|
-
const sent = await whatsapp.sendMessage(conversationId, text);
|
|
86
|
-
if (!sent.messageId) {
|
|
87
|
-
throw new Error(
|
|
88
|
-
"WhatsApp accepted the message but returned no message id",
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Upsert external conversation binding so the conversation key mapping
|
|
93
|
-
// exists for the next inbound WhatsApp message from this number.
|
|
94
|
-
try {
|
|
95
|
-
const sourceChannel = "whatsapp";
|
|
96
|
-
const conversationKey = `asst:${assistantId ?? "self"}:${sourceChannel}:${conversationId}`;
|
|
97
|
-
const { conversationId: internalId } =
|
|
98
|
-
getOrCreateConversation(conversationKey);
|
|
99
|
-
if (!assistantId || assistantId === "self") {
|
|
100
|
-
upsertOutboundBinding({
|
|
101
|
-
conversationId: internalId,
|
|
102
|
-
sourceChannel,
|
|
103
|
-
externalChatId: conversationId,
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
} catch {
|
|
107
|
-
// Best-effort — don't fail the send if binding upsert fails
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// The id is the one the Cloud API assigned, so a later reaction or
|
|
111
|
-
// delete carrying that id resolves to this send.
|
|
112
|
-
return {
|
|
113
|
-
id: sent.messageId,
|
|
114
|
-
timestamp: Date.now(),
|
|
115
|
-
conversationId,
|
|
116
|
-
};
|
|
117
|
-
},
|
|
118
|
-
|
|
119
72
|
async listConversations(
|
|
120
73
|
_connection?: OAuthConnection,
|
|
121
74
|
_options?: ListOptions,
|
|
@@ -1,6 +1,7 @@
|
|
|
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";
|
|
4
5
|
import type { ChannelTransport } from "../channel-transport.js";
|
|
5
6
|
import { sendWhatsAppAttachments, sendWhatsAppReply } from "./send.js";
|
|
6
7
|
|
|
@@ -9,6 +10,23 @@ const log = getLogger("whatsapp-transport");
|
|
|
9
10
|
export const whatsappTransport: ChannelTransport = {
|
|
10
11
|
channel: "whatsapp",
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* A chat is the phone number, which is also how a person is named, so
|
|
15
|
+
* reaching one needs no resolution of its own. There are no threads, so a
|
|
16
|
+
* target's thread is not carried and the post lands in the chat itself.
|
|
17
|
+
*/
|
|
18
|
+
addressFor(target) {
|
|
19
|
+
return {
|
|
20
|
+
ctx: directDeliveryContext("whatsapp"),
|
|
21
|
+
chatId: target.chatId,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
// A WhatsApp number's inbound conversation is keyed per chat and can be
|
|
26
|
+
// reset between sends; a proactive post re-binds it so the next inbound
|
|
27
|
+
// from the number lands where the post lives.
|
|
28
|
+
bindsChatOnProactiveSend: true,
|
|
29
|
+
|
|
12
30
|
async deliver(_ctx, payload) {
|
|
13
31
|
const { chatId, text, attachments, approval } = payload;
|
|
14
32
|
|
|
@@ -54,4 +54,12 @@ describe("workspace git heartbeat on the monitoring worker", () => {
|
|
|
54
54
|
expect(worker).toContain("await workspaceGitHeartbeat?.stop()");
|
|
55
55
|
expect(lifecycle).not.toContain("startWorkspaceHeartbeatService");
|
|
56
56
|
});
|
|
57
|
+
|
|
58
|
+
test("daemon lifecycle does not stop the in-process heartbeat singleton", () => {
|
|
59
|
+
const shutdown = readFileSync(
|
|
60
|
+
join(SRC_ROOT, "daemon/shutdown-handlers.ts"),
|
|
61
|
+
"utf8",
|
|
62
|
+
);
|
|
63
|
+
expect(shutdown).not.toContain("stopWorkspaceHeartbeatService");
|
|
64
|
+
});
|
|
57
65
|
});
|