@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
|
@@ -2,7 +2,6 @@ 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";
|
|
6
5
|
import type {
|
|
7
6
|
CallbackContext,
|
|
8
7
|
ChannelTransport,
|
|
@@ -67,25 +66,6 @@ function mintDraftId(): number {
|
|
|
67
66
|
export const telegramTransport: ChannelTransport = {
|
|
68
67
|
channel: "telegram",
|
|
69
68
|
|
|
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
|
-
|
|
89
69
|
// Telegram clears a chat action after about five seconds.
|
|
90
70
|
activityRefreshMs: 4_000,
|
|
91
71
|
|
|
@@ -5,6 +5,8 @@
|
|
|
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";
|
|
8
10
|
import { credentialKey } from "../../../security/credential-key.js";
|
|
9
11
|
import { getSecureKeyAsync } from "../../../security/secure-keys.js";
|
|
10
12
|
import type { MessagingProvider } from "../../provider.js";
|
|
@@ -16,7 +18,10 @@ import type {
|
|
|
16
18
|
Message,
|
|
17
19
|
SearchOptions,
|
|
18
20
|
SearchResult,
|
|
21
|
+
SendOptions,
|
|
22
|
+
SendResult,
|
|
19
23
|
} from "../../provider-types.js";
|
|
24
|
+
import * as whatsapp from "./client.js";
|
|
20
25
|
|
|
21
26
|
/** Check whether WhatsApp credentials are stored. */
|
|
22
27
|
async function hasWhatsAppCredentials(): Promise<boolean> {
|
|
@@ -36,7 +41,7 @@ export const whatsappMessagingProvider: MessagingProvider = {
|
|
|
36
41
|
id: "whatsapp",
|
|
37
42
|
displayName: "WhatsApp",
|
|
38
43
|
credentialService: "whatsapp",
|
|
39
|
-
capabilities: new Set(),
|
|
44
|
+
capabilities: new Set(["send"]),
|
|
40
45
|
|
|
41
46
|
async isConnected(): Promise<boolean> {
|
|
42
47
|
return hasWhatsAppCredentials();
|
|
@@ -69,6 +74,48 @@ export const whatsappMessagingProvider: MessagingProvider = {
|
|
|
69
74
|
};
|
|
70
75
|
},
|
|
71
76
|
|
|
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
|
+
|
|
72
119
|
async listConversations(
|
|
73
120
|
_connection?: OAuthConnection,
|
|
74
121
|
_options?: ListOptions,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level WhatsApp operations.
|
|
3
|
+
*
|
|
4
|
+
* Calls the Meta Cloud API directly via ./api.ts — no gateway proxy hop.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { sendWhatsAppTextMessage } from "./api.js";
|
|
8
|
+
|
|
9
|
+
/** Result returned by sendMessage. */
|
|
10
|
+
export interface WhatsAppSendResult {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* The id the Cloud API assigned to the message (`messages[0].id`). Meta
|
|
14
|
+
* returns it on every accepted send, so this is absent only when the API
|
|
15
|
+
* answered with an unexpected shape.
|
|
16
|
+
*/
|
|
17
|
+
messageId?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Send a WhatsApp text message via the Meta Cloud API.
|
|
22
|
+
*/
|
|
23
|
+
export async function sendMessage(
|
|
24
|
+
to: string,
|
|
25
|
+
text: string,
|
|
26
|
+
): Promise<WhatsAppSendResult> {
|
|
27
|
+
const result = await sendWhatsAppTextMessage(to, text);
|
|
28
|
+
return { ok: true, messageId: result.messages?.[0]?.id };
|
|
29
|
+
}
|
|
@@ -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 { ChannelTransport } from "../channel-transport.js";
|
|
6
5
|
import { sendWhatsAppAttachments, sendWhatsAppReply } from "./send.js";
|
|
7
6
|
|
|
@@ -10,23 +9,6 @@ const log = getLogger("whatsapp-transport");
|
|
|
10
9
|
export const whatsappTransport: ChannelTransport = {
|
|
11
10
|
channel: "whatsapp",
|
|
12
11
|
|
|
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
|
-
|
|
30
12
|
async deliver(_ctx, payload) {
|
|
31
13
|
const { chatId, text, attachments, approval } = payload;
|
|
32
14
|
|
|
@@ -54,12 +54,4 @@ 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
|
-
});
|
|
65
57
|
});
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
6
6
|
|
|
7
7
|
import { setOverridesForTesting } from "../../__tests__/feature-flag-test-helpers.js";
|
|
8
|
-
import { MESSAGE_KEYS } from "../../i18n/index.js";
|
|
9
8
|
import type { AttentionState } from "../../persistence/conversation-attention-store.js";
|
|
10
9
|
import type {
|
|
11
10
|
ConversationRow,
|
|
@@ -590,60 +589,6 @@ describe("emitAssistantReplyNotification", () => {
|
|
|
590
589
|
});
|
|
591
590
|
});
|
|
592
591
|
|
|
593
|
-
// A conversation whose title is still being written stores the message key
|
|
594
|
-
// itself, which is a non-empty string the sanitizer has no reason to reject.
|
|
595
|
-
// Sending it would put a raw key on the lock screen, so it counts as absent.
|
|
596
|
-
test("omits requestedTitle while the title is still generating", async () => {
|
|
597
|
-
conversationRow = makeConversation({
|
|
598
|
-
title: MESSAGE_KEYS.CONVERSATION_TITLE_GENERATING,
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
await run();
|
|
602
|
-
|
|
603
|
-
expect(emitCalls).toHaveLength(1);
|
|
604
|
-
expect(emitCalls[0].contextPayload).toEqual({
|
|
605
|
-
requestedMessage: "Sure, here is the plan.",
|
|
606
|
-
});
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
test("omits requestedTitle for the legacy generating placeholder", async () => {
|
|
610
|
-
conversationRow = makeConversation({ title: "Generating title..." });
|
|
611
|
-
|
|
612
|
-
await run();
|
|
613
|
-
|
|
614
|
-
expect(emitCalls).toHaveLength(1);
|
|
615
|
-
expect(emitCalls[0].contextPayload).toEqual({
|
|
616
|
-
requestedMessage: "Sure, here is the plan.",
|
|
617
|
-
});
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
test("omits requestedTitle for the untitled placeholder key", async () => {
|
|
621
|
-
conversationRow = makeConversation({
|
|
622
|
-
title: MESSAGE_KEYS.CONVERSATION_TITLE_UNTITLED,
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
await run();
|
|
626
|
-
|
|
627
|
-
expect(emitCalls).toHaveLength(1);
|
|
628
|
-
expect(emitCalls[0].contextPayload).toEqual({
|
|
629
|
-
requestedMessage: "Sure, here is the plan.",
|
|
630
|
-
});
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
// Only an exact key is a system constant. A title the user or the model
|
|
634
|
-
// wrote that happens to contain one is their copy and travels as written.
|
|
635
|
-
test("keeps a title that merely contains a message key", async () => {
|
|
636
|
-
conversationRow = makeConversation({
|
|
637
|
-
title: `notes on ${MESSAGE_KEYS.CONVERSATION_TITLE_GENERATING}`,
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
await run();
|
|
641
|
-
|
|
642
|
-
expect(emitCalls[0].contextPayload.requestedTitle).toBe(
|
|
643
|
-
"notes on conversation.title.generating",
|
|
644
|
-
);
|
|
645
|
-
});
|
|
646
|
-
|
|
647
592
|
test("caps the preview at 200 chars", async () => {
|
|
648
593
|
assistantRow = makeAssistantRow([
|
|
649
594
|
{ type: "text", text: "a".repeat(300) },
|
|
@@ -26,15 +26,6 @@ const messageRewrites: Array<{ messageId: string; content: string }> = [];
|
|
|
26
26
|
/** messageId -> the conversation it belongs to, for the scoped lookup. */
|
|
27
27
|
const messageOwners = new Map<string, string>();
|
|
28
28
|
let conversationRow: { conversationType: string } | null = null;
|
|
29
|
-
/**
|
|
30
|
-
* Rows for specific ids, consulted before the catch-all `conversationRow`,
|
|
31
|
-
* so a test can hand the signal's source and the delivery's paired
|
|
32
|
-
* conversation different shapes.
|
|
33
|
-
*/
|
|
34
|
-
const conversationRowsById = new Map<
|
|
35
|
-
string,
|
|
36
|
-
{ conversationType: string; source?: string }
|
|
37
|
-
>();
|
|
38
29
|
let conversationLookupShouldThrow = false;
|
|
39
30
|
let messageAppendShouldThrow = false;
|
|
40
31
|
let messageRewriteShouldThrow = false;
|
|
@@ -80,7 +71,7 @@ mock.module("../../persistence/conversation-crud.js", () => ({
|
|
|
80
71
|
if (conversationLookupShouldThrow) {
|
|
81
72
|
throw new Error("simulated conversation lookup failure");
|
|
82
73
|
}
|
|
83
|
-
return
|
|
74
|
+
return conversationRow;
|
|
84
75
|
},
|
|
85
76
|
addMessage: async (
|
|
86
77
|
conversationId: string,
|
|
@@ -173,7 +164,6 @@ beforeEach(() => {
|
|
|
173
164
|
messageRewrites.length = 0;
|
|
174
165
|
messageOwners.clear();
|
|
175
166
|
conversationRow = null;
|
|
176
|
-
conversationRowsById.clear();
|
|
177
167
|
conversationLookupShouldThrow = false;
|
|
178
168
|
messageAppendShouldThrow = false;
|
|
179
169
|
messageRewriteShouldThrow = false;
|
|
@@ -268,11 +258,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
268
258
|
},
|
|
269
259
|
});
|
|
270
260
|
|
|
271
|
-
const item = await writeHomeFeedItemForSignal(
|
|
272
|
-
signal,
|
|
273
|
-
decision,
|
|
274
|
-
makeVellumDelivery(),
|
|
275
|
-
);
|
|
261
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
276
262
|
|
|
277
263
|
expect(item).not.toBeNull();
|
|
278
264
|
expect(appendCalls).toHaveLength(1);
|
|
@@ -281,10 +267,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
281
267
|
"The deployment completed successfully.",
|
|
282
268
|
);
|
|
283
269
|
expect(appendCalls[0]!.conversationId).toBe("conv-source-1");
|
|
284
|
-
expect(
|
|
285
|
-
expect(messageAppends).toEqual([]);
|
|
286
|
-
expect(messagesInvalidated).toEqual([]);
|
|
287
|
-
expect(conversationLookups).toEqual(["conv-source-1", "conv-source-1"]);
|
|
270
|
+
expect(conversationLookups).toEqual(["conv-source-1"]);
|
|
288
271
|
});
|
|
289
272
|
|
|
290
273
|
test("isAsyncBackground hint writes even when sourceContextId does not resolve", async () => {
|
|
@@ -419,139 +402,6 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
419
402
|
expect(appendCalls[0]!.conversationId).toBe("paired-delivery-conv-id");
|
|
420
403
|
});
|
|
421
404
|
|
|
422
|
-
describe("assistant-initiated thread deliveries", () => {
|
|
423
|
-
// Under the assistant-initiated-threads flag, conversation pairing
|
|
424
|
-
// promotes a background assistant.share into a standard conversation
|
|
425
|
-
// stamped `assistant_initiated`, which the sidebar's assistant section
|
|
426
|
-
// renders. That thread is the share's surface; a feed item for the
|
|
427
|
-
// same signal put it in the bell a second time.
|
|
428
|
-
const assistantThreadDelivery = () =>
|
|
429
|
-
makeVellumDelivery({ conversationId: "assistant-thread-1" });
|
|
430
|
-
|
|
431
|
-
function stampAssistantThread(): void {
|
|
432
|
-
conversationRowsById.set("assistant-thread-1", {
|
|
433
|
-
conversationType: "standard",
|
|
434
|
-
source: "assistant_initiated",
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function shareFromHeartbeat(): NotificationSignal {
|
|
439
|
-
return makeSignal({
|
|
440
|
-
sourceChannel: "assistant_tool",
|
|
441
|
-
sourceEventName: "assistant.share",
|
|
442
|
-
sourceContextId: "conv-source-1",
|
|
443
|
-
contextPayload: { title: "Doctor checklist built" },
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
const shareDecision = () =>
|
|
448
|
-
makeDecision({
|
|
449
|
-
selectedChannels: ["vellum"],
|
|
450
|
-
renderedCopy: {
|
|
451
|
-
vellum: {
|
|
452
|
-
title: "Doctor checklist built",
|
|
453
|
-
body: "Your doctor-visit one-pager is ready.",
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
test("a background share promoted into an assistant thread does not mirror", async () => {
|
|
459
|
-
conversationRow = { conversationType: "background" };
|
|
460
|
-
stampAssistantThread();
|
|
461
|
-
|
|
462
|
-
const item = await writeHomeFeedItemForSignal(
|
|
463
|
-
shareFromHeartbeat(),
|
|
464
|
-
shareDecision(),
|
|
465
|
-
assistantThreadDelivery(),
|
|
466
|
-
);
|
|
467
|
-
|
|
468
|
-
expect(item).toBeNull();
|
|
469
|
-
expect(appendCalls).toHaveLength(0);
|
|
470
|
-
expect(messageAppends).toHaveLength(0);
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
test("the async-background hint does not override the assistant-thread suppression", async () => {
|
|
474
|
-
conversationRow = null;
|
|
475
|
-
stampAssistantThread();
|
|
476
|
-
const signal = makeSignal({
|
|
477
|
-
sourceChannel: "assistant_tool",
|
|
478
|
-
sourceEventName: "assistant.share",
|
|
479
|
-
sourceContextId: "heartbeat",
|
|
480
|
-
contextPayload: { title: "Bilbao solo trip is planned" },
|
|
481
|
-
attentionHints: {
|
|
482
|
-
requiresAction: false,
|
|
483
|
-
urgency: "medium",
|
|
484
|
-
isAsyncBackground: true,
|
|
485
|
-
visibleInSourceNow: false,
|
|
486
|
-
},
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
const item = await writeHomeFeedItemForSignal(
|
|
490
|
-
signal,
|
|
491
|
-
shareDecision(),
|
|
492
|
-
assistantThreadDelivery(),
|
|
493
|
-
);
|
|
494
|
-
|
|
495
|
-
expect(item).toBeNull();
|
|
496
|
-
expect(appendCalls).toHaveLength(0);
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
test("a share paired with an ordinary notification thread still mirrors", async () => {
|
|
500
|
-
// Flag off, or any producer that did not promote: pairing writes a
|
|
501
|
-
// `notification`-sourced row, which stays in Chats and the bell.
|
|
502
|
-
conversationRow = { conversationType: "background" };
|
|
503
|
-
conversationRowsById.set("assistant-thread-1", {
|
|
504
|
-
conversationType: "standard",
|
|
505
|
-
source: "notification",
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
const item = await writeHomeFeedItemForSignal(
|
|
509
|
-
shareFromHeartbeat(),
|
|
510
|
-
shareDecision(),
|
|
511
|
-
assistantThreadDelivery(),
|
|
512
|
-
);
|
|
513
|
-
|
|
514
|
-
expect(item).not.toBeNull();
|
|
515
|
-
expect(appendCalls).toHaveLength(1);
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
test("a guardian request keeps its bell item even when paired with an assistant thread", async () => {
|
|
519
|
-
// The bell is the request's canonical home: nothing else renders the
|
|
520
|
-
// pending card, so the assistant-thread suppression must not reach it.
|
|
521
|
-
conversationRow = { conversationType: "standard" };
|
|
522
|
-
stampAssistantThread();
|
|
523
|
-
const signal = makeSignal({
|
|
524
|
-
sourceChannel: "assistant_tool",
|
|
525
|
-
sourceEventName: "guardian.question",
|
|
526
|
-
contextPayload: {
|
|
527
|
-
title: "Approve this?",
|
|
528
|
-
questionText: "May I send the email?",
|
|
529
|
-
requestId: "req-1",
|
|
530
|
-
},
|
|
531
|
-
attentionHints: {
|
|
532
|
-
requiresAction: true,
|
|
533
|
-
urgency: "medium",
|
|
534
|
-
isAsyncBackground: false,
|
|
535
|
-
visibleInSourceNow: false,
|
|
536
|
-
},
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
const item = await writeHomeFeedItemForSignal(
|
|
540
|
-
signal,
|
|
541
|
-
makeDecision({
|
|
542
|
-
selectedChannels: ["vellum"],
|
|
543
|
-
renderedCopy: {
|
|
544
|
-
vellum: { title: "Approve this?", body: "May I send the email?" },
|
|
545
|
-
},
|
|
546
|
-
}),
|
|
547
|
-
assistantThreadDelivery(),
|
|
548
|
-
);
|
|
549
|
-
|
|
550
|
-
expect(item).not.toBeNull();
|
|
551
|
-
expect(appendCalls).toHaveLength(1);
|
|
552
|
-
});
|
|
553
|
-
});
|
|
554
|
-
|
|
555
405
|
test("source conversation id wins over the paired delivery fallback when both are available", async () => {
|
|
556
406
|
// When the producer's `sourceContextId` already points at a real
|
|
557
407
|
// conversation (the canonical "where the work happened"), prefer it
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
getMessageById,
|
|
23
23
|
parseMessageMetadata,
|
|
24
24
|
} from "../persistence/conversation-crud.js";
|
|
25
|
-
import { isReplaceableTitle } from "../persistence/conversation-title-placeholders.js";
|
|
26
25
|
import {
|
|
27
26
|
isDesktopOriginatedUserMessage,
|
|
28
27
|
isReplyPushIneligibleUserMessage,
|
|
@@ -248,13 +247,9 @@ export async function emitAssistantReplyNotification(params: {
|
|
|
248
247
|
// lock screen. Absent `requestedTitle` lets the decision branch derive a
|
|
249
248
|
// title from the body, which reads better than an empty or placeholder
|
|
250
249
|
// conversation title.
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const storedTitle = conversation.title?.trim() ?? "";
|
|
255
|
-
const requestedTitle = isReplaceableTitle(storedTitle)
|
|
256
|
-
? ""
|
|
257
|
-
: sanitizeNotificationTitle(flattenTitleWhitespace(storedTitle));
|
|
250
|
+
const requestedTitle = sanitizeNotificationTitle(
|
|
251
|
+
flattenTitleWhitespace(conversation.title ?? ""),
|
|
252
|
+
);
|
|
258
253
|
|
|
259
254
|
// Read as close to the emit as possible: nothing short-circuits on it.
|
|
260
255
|
// Presence only speaks for a turn the desktop itself opened, on that row's
|
|
@@ -130,7 +130,6 @@ export interface PairingOptions {
|
|
|
130
130
|
* transactional request or a system alert.
|
|
131
131
|
*/
|
|
132
132
|
const ASSISTANT_SHARE_EVENT = "assistant.share";
|
|
133
|
-
const ASSISTANT_REPLY_EVENT = "chat.assistant_reply";
|
|
134
133
|
|
|
135
134
|
/**
|
|
136
135
|
* Promote a background share into an assistant-initiated thread, under the
|
|
@@ -264,25 +263,7 @@ export async function pairDeliveryWithConversation(
|
|
|
264
263
|
// notification can appear. The home feed aims its "Go to Conversation"
|
|
265
264
|
// button at the same row whenever it mirrors the signal.
|
|
266
265
|
//
|
|
267
|
-
//
|
|
268
|
-
// Its notification body is a lock-screen preview, so appending it would
|
|
269
|
-
// create a second, truncated assistant row. Keep the conversation target
|
|
270
|
-
// for deep links without writing the preview into the transcript.
|
|
271
|
-
if (
|
|
272
|
-
strategy === "start_new_conversation" &&
|
|
273
|
-
!signal.requiresConversation &&
|
|
274
|
-
signal.sourceEventName === ASSISTANT_REPLY_EVENT
|
|
275
|
-
) {
|
|
276
|
-
return {
|
|
277
|
-
conversationId: resolveSourceConversationId(signal),
|
|
278
|
-
messageId: null,
|
|
279
|
-
strategy,
|
|
280
|
-
createdNewConversation: false,
|
|
281
|
-
conversationFallbackUsed: false,
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// This is deliberately not gated on home-feed eligibility: a signal the
|
|
266
|
+
// So this is deliberately not gated on home-feed eligibility: a signal the
|
|
286
267
|
// feed declines to mirror still reaches the user through the banner, and
|
|
287
268
|
// this row is what makes that landing honest.
|
|
288
269
|
if (strategy === "start_new_conversation" && !signal.requiresConversation) {
|
|
@@ -767,45 +748,44 @@ async function resolveChannelDeliveryHome(params: {
|
|
|
767
748
|
* Indexing is skipped for parity with the other notification write paths:
|
|
768
749
|
* notification copy is delivery audit, not conversational memory.
|
|
769
750
|
*/
|
|
770
|
-
function
|
|
771
|
-
|
|
751
|
+
async function appendBodyToSourceConversation(
|
|
752
|
+
signal: NotificationSignal,
|
|
753
|
+
channel: NotificationChannel,
|
|
754
|
+
messageContent: string,
|
|
755
|
+
): Promise<{ conversationId: string; messageId: string } | null> {
|
|
756
|
+
const sourceContextId = signal.sourceContextId;
|
|
757
|
+
if (!sourceContextId) {
|
|
772
758
|
return null;
|
|
773
759
|
}
|
|
760
|
+
|
|
761
|
+
let existing: ReturnType<typeof getConversation>;
|
|
774
762
|
try {
|
|
775
|
-
|
|
763
|
+
existing = getConversation(sourceContextId);
|
|
776
764
|
} catch {
|
|
777
765
|
return null;
|
|
778
766
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
async function appendBodyToSourceConversation(
|
|
782
|
-
signal: NotificationSignal,
|
|
783
|
-
channel: NotificationChannel,
|
|
784
|
-
messageContent: string,
|
|
785
|
-
): Promise<{ conversationId: string; messageId: string } | null> {
|
|
786
|
-
const conversationId = resolveSourceConversationId(signal);
|
|
787
|
-
if (!conversationId) {
|
|
767
|
+
if (!existing) {
|
|
788
768
|
return null;
|
|
789
769
|
}
|
|
790
770
|
|
|
791
|
-
const message = await addMessage(
|
|
771
|
+
const message = await addMessage(existing.id, "assistant", messageContent, {
|
|
792
772
|
skipIndexing: true,
|
|
793
773
|
});
|
|
794
774
|
// `addMessage` projects attention metadata alone, so a client with this
|
|
795
775
|
// conversation open needs the messages tag to refetch the transcript. A
|
|
796
776
|
// notification the user taps through to has every chance of landing on an
|
|
797
777
|
// already-open conversation.
|
|
798
|
-
publishConversationMessagesChanged(
|
|
778
|
+
publishConversationMessagesChanged(existing.id);
|
|
799
779
|
|
|
800
780
|
log.info(
|
|
801
781
|
{
|
|
802
782
|
signalId: signal.signalId,
|
|
803
783
|
channel,
|
|
804
|
-
conversationId,
|
|
784
|
+
conversationId: existing.id,
|
|
805
785
|
messageId: message.id,
|
|
806
786
|
},
|
|
807
787
|
"Appended notification body to producing conversation",
|
|
808
788
|
);
|
|
809
789
|
|
|
810
|
-
return { conversationId, messageId: message.id };
|
|
790
|
+
return { conversationId: existing.id, messageId: message.id };
|
|
811
791
|
}
|
|
@@ -39,12 +39,6 @@ export interface DeliveredChannelPost {
|
|
|
39
39
|
text: string;
|
|
40
40
|
/** The message id the channel assigned when it acknowledged the post. */
|
|
41
41
|
providerMessageId: string;
|
|
42
|
-
/**
|
|
43
|
-
* The ids of the further posts the same text became when the channel
|
|
44
|
-
* split it, in send order. Each is reconciled onto the row like the first,
|
|
45
|
-
* so a reaction, edit, or delete naming any chunk resolves to this row.
|
|
46
|
-
*/
|
|
47
|
-
additionalProviderMessageIds?: readonly string[];
|
|
48
42
|
/**
|
|
49
43
|
* The conversation whose turn made the post, when a different one from
|
|
50
44
|
* the home it is recorded in (the messaging tool sending from a scheduled
|
|
@@ -81,11 +75,7 @@ export async function recordDeliveredChannelPost(
|
|
|
81
75
|
: {}),
|
|
82
76
|
},
|
|
83
77
|
});
|
|
84
|
-
|
|
85
|
-
await reconcile(post.providerMessageId);
|
|
86
|
-
for (const id of post.additionalProviderMessageIds ?? []) {
|
|
87
|
-
await reconcile(id);
|
|
88
|
-
}
|
|
78
|
+
await makeSentMessageIdReconciler(row.id)(post.providerMessageId);
|
|
89
79
|
// A resident conversation reloads its history on the next turn so the
|
|
90
80
|
// post is in context; the client refetches the transcript.
|
|
91
81
|
findConversation(post.conversationId)?.markHistoryStale();
|