@vellumai/assistant 0.11.8 → 0.11.9-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +2 -2
- package/Dockerfile +8 -48
- package/docker-entrypoint.sh +5 -1
- package/docker-kata-apt-env.sh +3 -0
- package/docker-kata-apt-shims.sh +127 -0
- package/docker-kata-apt-wrapper.sh +45 -0
- package/docker-kata-chroot-exec.sh +35 -0
- package/docker-kata-pip.sh +8 -2
- package/docs/architecture/memory.md +8 -4
- package/docs/guardian-request-flow.md +18 -14
- package/docs/trusted-contact-access.md +1 -7
- package/node_modules/@vellumai/avatar-catalog/src/catalog.ts +7 -1
- package/node_modules/@vellumai/avatar-catalog/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +0 -18
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +110 -5
- package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +5 -33
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +3 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +100 -9
- package/node_modules/@vellumai/gateway-client/src/index.ts +1 -2
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +9 -0
- package/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/openapi.yaml +193 -4
- package/package.json +2 -2
- package/src/__tests__/access-request-card-view.test.ts +6 -5
- package/src/__tests__/access-request-seed-content-blocks.test.ts +5 -2
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +43 -82
- package/src/__tests__/always-loaded-tools-guard.test.ts +8 -2
- package/src/__tests__/attachment-stored-path-annotation.test.ts +80 -0
- package/src/__tests__/attachments-store.test.ts +27 -0
- package/src/__tests__/attachments.test.ts +43 -0
- package/src/__tests__/canned-reply-release.test.ts +48 -5
- package/src/__tests__/channel-delivery-store.test.ts +129 -18
- package/src/__tests__/channel-reply-delivery.test.ts +553 -93
- package/src/__tests__/channel-retry-sweep.test.ts +199 -0
- package/src/__tests__/client-os-metadata-persistence.test.ts +32 -3
- package/src/__tests__/conversation-error.test.ts +15 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +118 -0
- package/src/__tests__/conversation-pairing.test.ts +141 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +28 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +125 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +79 -0
- package/src/__tests__/conversation-store-ephemeral.test.ts +323 -10
- package/src/__tests__/conversation-sync-tags.test.ts +2 -37
- package/src/__tests__/credential-health-service.test.ts +98 -10
- package/src/__tests__/delete-propagation.test.ts +469 -0
- package/src/__tests__/dm-persistence.test.ts +16 -0
- package/src/__tests__/docker-kata-apt-shims.test.ts +135 -0
- package/src/__tests__/forbidden-legacy-symbols.test.ts +12 -0
- package/src/__tests__/gemini-provider.test.ts +46 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +0 -22
- package/src/__tests__/guardian-gateway-sim.ts +0 -23
- package/src/__tests__/guardian-question-mode.test.ts +108 -0
- package/src/__tests__/guardian-reply-router-answer-mode.test.ts +0 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +26 -12
- package/src/__tests__/host-proxy-interface.test.ts +10 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +16 -0
- package/src/__tests__/injector-v3-suppression.test.ts +43 -9
- package/src/__tests__/list-messages-page-latest.test.ts +127 -0
- package/src/__tests__/list-messages-system-card.test.ts +103 -0
- package/src/__tests__/media-resolve-image-validation.test.ts +77 -0
- package/src/__tests__/notification-decision-fallback.test.ts +199 -77
- package/src/__tests__/notification-decision-strategy.test.ts +198 -213
- package/src/__tests__/notification-discord-adapter.test.ts +25 -0
- package/src/__tests__/notification-slack-adapter.test.ts +133 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +36 -0
- package/src/__tests__/openai-provider.test.ts +5 -5
- package/src/__tests__/outbound-slack-persistence.test.ts +85 -72
- package/src/__tests__/persist-user-message-set-processing-failure.test.ts +83 -65
- package/src/__tests__/platform-client-verify-credential.test.ts +100 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -1
- package/src/__tests__/pricing.test.ts +11 -0
- package/src/__tests__/process-message-display-content.test.ts +20 -9
- package/src/__tests__/processing-acquire-fenced-guard.test.ts +56 -0
- package/src/__tests__/provider-meta-persistence.test.ts +67 -0
- package/src/__tests__/reaction-persistence.test.ts +22 -198
- package/src/__tests__/run-conversation-turn-persistence.test.ts +5 -2
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +16 -0
- package/src/__tests__/skill-load-tool.test.ts +27 -0
- package/src/__tests__/skills.test.ts +34 -1
- package/src/__tests__/strip-memory-injections.test.ts +3 -4
- package/src/__tests__/terminal-tools.test.ts +9 -0
- package/src/__tests__/thread-backfill.test.ts +5 -3
- package/src/__tests__/unified-turn-context-location.test.ts +76 -0
- package/src/__tests__/voice-session-bridge.test.ts +18 -0
- package/src/__tests__/watch-retro-report-payload.test.ts +185 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +82 -0
- package/src/__tests__/workspace-migration-151-repair-renamed-fireworks-deepseek-pro-model-id.test.ts +235 -0
- package/src/__tests__/workspace-migration-152-repair-retired-fireworks-minimax-m2p7-model-id.test.ts +233 -0
- package/src/agent/attachments.ts +13 -2
- package/src/agent/loop.ts +3 -19
- package/src/api/README.md +9 -5
- package/src/api/index.ts +9 -8
- package/src/api/package.json +1 -0
- package/src/api/responses/conversation-message.ts +8 -0
- package/src/api/responses/home.ts +7 -18
- package/src/api/surfaces.ts +114 -7
- package/src/approvals/AGENTS.md +1 -1
- package/src/channels/__tests__/gateway-guardian-requests.test.ts +1 -23
- package/src/channels/__tests__/types.test.ts +22 -1
- package/src/channels/gateway-guardian-requests.ts +0 -22
- package/src/channels/types.ts +8 -6
- package/src/cli/__tests__/catalog-search-help.test.ts +18 -0
- package/src/cli/commands/channels/__tests__/channels.test.ts +26 -0
- package/src/cli/commands/channels/index.help.ts +19 -6
- package/src/cli/commands/channels/index.ts +6 -2
- package/src/cli/commands/db/__tests__/status.test.ts +22 -0
- package/src/cli/commands/db/index.help.ts +1 -1
- package/src/cli/commands/db/status.ts +172 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +29 -0
- package/src/cli/commands/platform/connect.ts +14 -5
- package/src/cli/commands/plugins.help.ts +6 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +0 -8
- package/src/cli/lib/__tests__/install-from-platform.test.ts +72 -0
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +33 -0
- package/src/cli/lib/bundled-marketplace.json +14 -0
- package/src/cli/lib/install-from-github.ts +9 -5
- package/src/cli/lib/install-from-platform.ts +12 -1
- package/src/config/__tests__/assistant-initiated-threads-gate.test.ts +61 -0
- package/src/config/assistant-initiated-threads-gate.ts +50 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -3
- package/src/config/bundled-skills/schedule/SKILL.md +25 -11
- package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +3 -1
- package/src/config/call-site-defaults.ts +4 -1
- package/src/config/feature-flag-registry.json +21 -4
- package/src/config/schemas/memory-v3.ts +4 -3
- package/src/context/strip-injections.ts +17 -56
- package/src/conversations/__tests__/message-consolidation.test.ts +39 -0
- package/src/conversations/message-consolidation.ts +4 -3
- package/src/credential-health/credential-health-service.ts +130 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +31 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +55 -59
- package/src/daemon/conversation-error.ts +2 -0
- package/src/daemon/conversation-messaging.ts +195 -47
- package/src/daemon/conversation-process.ts +20 -5
- package/src/daemon/conversation-runtime-assembly.ts +48 -39
- package/src/daemon/conversation-store.ts +159 -11
- package/src/daemon/conversation-tool-setup.ts +9 -2
- package/src/daemon/conversation.ts +290 -19
- package/src/daemon/dictation-text-processing.ts +2 -7
- package/src/daemon/handlers/config-channels.ts +33 -3
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/shared.ts +9 -0
- package/src/daemon/port-oversized-content.test.ts +117 -0
- package/src/daemon/port-oversized-content.ts +110 -0
- package/src/daemon/process-message.ts +87 -16
- package/src/daemon/reaction-record.test.ts +23 -11
- package/src/daemon/reaction-record.ts +6 -9
- package/src/documents/document-store.ts +1 -5
- package/src/home/conversation-starter-validation.ts +1 -5
- package/src/live-voice/__tests__/live-voice-photo.test.ts +910 -50
- package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +31 -25
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +70 -1
- package/src/live-voice/live-voice-photo.ts +544 -61
- package/src/live-voice/live-voice-session.ts +6 -2
- package/src/live-voice/protocol.ts +20 -1
- package/src/messaging/provider-message-metadata.ts +40 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +10 -2
- package/src/messaging/providers/channel-transport.ts +28 -0
- package/src/messaging/providers/discord/send.ts +3 -2
- package/src/messaging/providers/slack/api.ts +11 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +133 -0
- package/src/messaging/providers/slack/message-metadata.ts +141 -1
- package/src/messaging/providers/slack/send.test.ts +58 -0
- package/src/messaging/providers/slack/send.ts +4 -4
- package/src/messaging/providers/slack/transport.ts +28 -2
- package/src/messaging/providers/telegram-bot/send.test.ts +159 -0
- package/src/messaging/providers/telegram-bot/send.ts +93 -0
- package/src/messaging/providers/telegram-bot/transport.ts +71 -0
- package/src/messaging/reaction-envelopes.test.ts +73 -0
- package/src/messaging/reaction-envelopes.ts +33 -5
- package/src/messaging/read-provider-metadata.ts +4 -3
- package/src/monitoring/recovery/__tests__/stranded-delivery-events.test.ts +208 -0
- package/src/monitoring/recovery/db.ts +35 -0
- package/src/monitoring/recovery/orphaned-channel-events.ts +3 -22
- package/src/monitoring/recovery/run-recovery.ts +6 -2
- package/src/monitoring/recovery/stale-processing.ts +3 -20
- package/src/monitoring/recovery/stranded-delivery-events.ts +68 -0
- package/src/notifications/AGENTS.md +2 -2
- package/src/notifications/README.md +2 -2
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +11 -9
- package/src/notifications/__tests__/broadcaster.test.ts +35 -4
- package/src/notifications/__tests__/notification-utils.test.ts +31 -0
- package/src/notifications/access-request-copy.ts +126 -114
- package/src/notifications/adapters/discord.ts +9 -5
- package/src/notifications/adapters/shared.ts +17 -4
- package/src/notifications/adapters/slack.ts +14 -4
- package/src/notifications/adapters/telegram.ts +8 -4
- package/src/notifications/approval-card-data.ts +5 -2
- package/src/notifications/assistant-reply-producer.ts +7 -7
- package/src/notifications/broadcaster.ts +57 -25
- package/src/notifications/conversation-pairing.ts +68 -2
- package/src/notifications/copy-composer.ts +13 -32
- package/src/notifications/decision-engine.ts +56 -237
- package/src/notifications/guardian-delivery-recorder.ts +5 -9
- package/src/notifications/guardian-feed-projection.ts +0 -15
- package/src/notifications/guardian-question-mode.ts +147 -6
- package/src/notifications/notification-utils.ts +117 -1
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/prompter.ts +1 -1
- package/src/persistence/conversation-crud.ts +93 -14
- package/src/persistence/conversation-queries.ts +144 -17
- package/src/persistence/conversation-types.ts +39 -5
- package/src/persistence/delivery-crud.ts +224 -28
- package/src/persistence/delivery-status.ts +21 -2
- package/src/persistence/migrations/374-channel-inbound-message-id-index.ts +26 -0
- package/src/persistence/migrations/375-create-channel-outbound-posts.ts +52 -0
- package/src/persistence/migrations/__tests__/375-create-channel-outbound-posts.test.ts +84 -0
- package/src/persistence/schema/conversations.ts +71 -2
- package/src/persistence/schema-contract.test.ts +78 -0
- package/src/persistence/schema-contract.ts +96 -0
- package/src/persistence/steps.ts +4 -0
- package/src/platform/client.ts +55 -0
- package/src/plugins/__tests__/mcp-servers.test.ts +46 -0
- package/src/plugins/defaults/injector-order.ts +2 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +2 -2
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +13 -15
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +10 -0
- package/src/plugins/defaults/memory/injectors.ts +1 -1
- package/src/plugins/defaults/memory/memory-marker.ts +17 -7
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +1 -5
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +17 -0
- package/src/plugins/defaults/memory/substrate/skill-content.ts +7 -0
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +54 -41
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +3 -3
- package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +22 -0
- package/src/plugins/defaults/memory/v3/injector.ts +14 -15
- package/src/plugins/defaults/memory/v3/prune.test.ts +20 -2
- package/src/plugins/defaults/memory/v3/render-injection.ts +10 -1
- package/src/plugins/defaults/memory/v3/types.ts +15 -4
- package/src/plugins/defaults/turn-context/injectors.ts +3 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +24 -0
- package/src/plugins/mcp-servers.ts +14 -2
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +50 -0
- package/src/providers/__tests__/registry-native-web-search.test.ts +49 -2
- package/src/providers/anthropic/client.ts +21 -24
- package/src/providers/call-site-routing.ts +5 -4
- package/src/providers/connection-resolution.ts +29 -1
- package/src/providers/content-block-size.test.ts +82 -0
- package/src/providers/content-block-size.ts +99 -0
- package/src/providers/file-block-text.test.ts +64 -0
- package/src/providers/file-block-text.ts +28 -0
- package/src/providers/gemini/client.ts +15 -9
- package/src/providers/inference/auth.ts +6 -6
- package/src/providers/media-resolve.ts +14 -0
- package/src/providers/model-catalog.ts +47 -34
- package/src/providers/openai/chat-completions-provider.ts +9 -25
- package/src/providers/openai/responses-provider.ts +11 -18
- package/src/providers/registry.ts +10 -7
- package/src/providers/routing-identity.ts +2 -1
- package/src/providers/types.ts +1 -4
- package/src/providers/vellum-model-routing.ts +3 -2
- package/src/runtime/AGENTS.md +40 -15
- package/src/runtime/approval-message-composer.ts +1 -6
- package/src/runtime/assistant-event-hub.ts +2 -39
- package/src/runtime/channel-approval-types.ts +1 -5
- package/src/runtime/channel-reply-delivery.ts +182 -115
- package/src/runtime/{slack-reply-session.test.ts → channel-reply-session.test.ts} +294 -156
- package/src/runtime/{slack-reply-session.ts → channel-reply-session.ts} +107 -86
- package/src/runtime/channel-retry-sweep.ts +102 -1
- package/src/runtime/finalize-event-delivery.ts +4 -4
- package/src/runtime/guardian-action-message-composer.ts +1 -4
- package/src/runtime/guardian-reply-router.ts +4 -75
- package/src/runtime/http-router.ts +1 -5
- package/src/runtime/question-request-guardian-bridge.ts +2 -3
- package/src/runtime/routes/__tests__/conversation-list-assistant-section.test.ts +359 -0
- package/src/runtime/routes/__tests__/dictation-command-mode.test.ts +120 -0
- package/src/runtime/routes/__tests__/sight-frame-routes.test.ts +487 -0
- package/src/runtime/routes/acp-routes.ts +1 -1
- package/src/runtime/routes/canned-reply-release.ts +19 -9
- package/src/runtime/routes/channel-route-shared.ts +0 -39
- package/src/runtime/routes/channel-verification-routes.ts +4 -1
- package/src/runtime/routes/conversation-list-routes.ts +44 -3
- package/src/runtime/routes/conversation-management-routes.ts +2 -1
- package/src/runtime/routes/conversation-routes.ts +138 -59
- package/src/runtime/routes/diagnostics-routes.ts +111 -60
- package/src/runtime/routes/guardian-action-routes.ts +10 -14
- package/src/runtime/routes/guardian-approval-interception.ts +0 -5
- package/src/runtime/routes/inbound-message-handler.ts +170 -48
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +4 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +169 -8
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +82 -14
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +23 -48
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +454 -94
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +309 -102
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/platform-routes.ts +99 -10
- package/src/runtime/routes/sight-frame-routes.ts +136 -0
- package/src/runtime/sync/resource-sync-events.ts +14 -37
- package/src/runtime/sync/sync-publisher.test.ts +5 -4
- package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
- package/src/tools/client-os.ts +11 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.ts +2 -2
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/terminal/safe-env.ts +4 -0
- package/src/tools/tool-input-schemas.ts +2 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/tools/ui-surface/definitions.ts +4 -3
- package/src/tools/watch/watch-retro-report.ts +205 -0
- package/src/watch/__tests__/watch-retro.test.ts +268 -40
- package/src/watch/watch-retro.ts +190 -77
- package/src/workspace/migrations/151-repair-renamed-fireworks-deepseek-pro-model-id.ts +195 -0
- package/src/workspace/migrations/152-repair-retired-fireworks-minimax-m2p7-model-id.ts +198 -0
- package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +4 -0
- package/docker-kata-pip-chroot.sh +0 -22
- package/src/__tests__/slack-reaction-approvals.test.ts +0 -97
- package/src/__tests__/slack-reaction-guardian-approval.test.ts +0 -307
- package/src/api/events/conversation-list-invalidated.ts +0 -38
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
|
|
15
15
|
import { CHANNEL_IDS } from "../../channels/types.js";
|
|
16
|
+
import { isAssistantInitiatedThreadsEnabled } from "../../config/assistant-initiated-threads-gate.js";
|
|
16
17
|
import { channelBindingSchema } from "../../messaging/channel-binding-schema.js";
|
|
17
18
|
import {
|
|
18
19
|
type Confidence,
|
|
@@ -35,7 +36,10 @@ import {
|
|
|
35
36
|
listConversations,
|
|
36
37
|
listPinnedConversations,
|
|
37
38
|
} from "../../persistence/conversation-queries.js";
|
|
38
|
-
import
|
|
39
|
+
import {
|
|
40
|
+
ASSISTANT_INITIATED_GROUP_ID,
|
|
41
|
+
type ConversationType,
|
|
42
|
+
} from "../../persistence/conversation-types.js";
|
|
39
43
|
import { getBindingsForConversations } from "../../persistence/external-conversation-store.js";
|
|
40
44
|
import { listGroups } from "../../persistence/group-crud.js";
|
|
41
45
|
import { UserError } from "../../util/errors.js";
|
|
@@ -170,9 +174,16 @@ const sectionCountFields = {
|
|
|
170
174
|
* `chats` is the ungrouped-native bucket. In a view without channel sections
|
|
171
175
|
* the Chats card holds every ungrouped row: the buckets are disjoint, so
|
|
172
176
|
* that reading is `chats` plus the sum of `channel` entries.
|
|
177
|
+
*
|
|
178
|
+
* `assistant` is emitted only under the `assistant-initiated-threads` flag,
|
|
179
|
+
* and while it is emitted its rows are withheld from `chats`, so the
|
|
180
|
+
* disjointness above holds in both states. A client that does not know the
|
|
181
|
+
* kind ignores it, which is the same thing an older client does with a
|
|
182
|
+
* `channel` section for a channel it has no label for.
|
|
173
183
|
*/
|
|
174
184
|
const conversationSectionSchema = z.discriminatedUnion("kind", [
|
|
175
185
|
z.object({ kind: z.literal("pinned"), ...sectionCountFields }),
|
|
186
|
+
z.object({ kind: z.literal("assistant"), ...sectionCountFields }),
|
|
176
187
|
z.object({
|
|
177
188
|
kind: z.literal("group"),
|
|
178
189
|
groupId: z.string(),
|
|
@@ -274,6 +285,23 @@ function handleListConversations({ queryParams = {} }: RouteHandlerArgs) {
|
|
|
274
285
|
? true
|
|
275
286
|
: undefined;
|
|
276
287
|
|
|
288
|
+
/* The list half of the section split. A read already scoped to the
|
|
289
|
+
assistant section obviously keeps its rows; every other standard-listing
|
|
290
|
+
read withholds them, so they appear in their own section and nowhere
|
|
291
|
+
else. Confined to `standard`: the background and scheduled buckets are
|
|
292
|
+
back-compat umbrellas addressed by conversation type, and narrowing them
|
|
293
|
+
by source would drop rows their callers still page through. Confined to
|
|
294
|
+
active reads too: the Archive view asks `archiveStatus=archived` with no
|
|
295
|
+
`groupId`, and the section only ever lists active rows, so withholding
|
|
296
|
+
there would leave an archived section thread visible nowhere. */
|
|
297
|
+
const excludeAssistantInitiated =
|
|
298
|
+
isAssistantInitiatedThreadsEnabled() &&
|
|
299
|
+
conversationType === "standard" &&
|
|
300
|
+
archiveStatus === "active" &&
|
|
301
|
+
groupId !== ASSISTANT_INITIATED_GROUP_ID
|
|
302
|
+
? (true as const)
|
|
303
|
+
: undefined;
|
|
304
|
+
|
|
277
305
|
const filter: ConversationListFilter = {
|
|
278
306
|
conversationType,
|
|
279
307
|
archiveStatus,
|
|
@@ -281,6 +309,7 @@ function handleListConversations({ queryParams = {} }: RouteHandlerArgs) {
|
|
|
281
309
|
groupId,
|
|
282
310
|
needsAttention,
|
|
283
311
|
foregroundOnly,
|
|
312
|
+
excludeAssistantInitiated,
|
|
284
313
|
};
|
|
285
314
|
let rows = listConversations({ ...filter, limit, offset });
|
|
286
315
|
const totalCount = countConversations(filter);
|
|
@@ -371,10 +400,22 @@ function handleGetUnreadConversationCount() {
|
|
|
371
400
|
* dangling id is a transient state around group deletion, not a section.
|
|
372
401
|
*/
|
|
373
402
|
function handleGetConversationSections() {
|
|
374
|
-
const
|
|
403
|
+
const splitAssistantInitiated = isAssistantInitiatedThreadsEnabled();
|
|
404
|
+
const counts = countConversationSections({ splitAssistantInitiated });
|
|
375
405
|
const groupsById = new Map(listGroups().map((g) => [g.id, g]));
|
|
376
406
|
const sections: Array<z.infer<typeof conversationSectionSchema>> = [];
|
|
377
407
|
|
|
408
|
+
/* Unlike every other section this one renders at zero, the way Chats does:
|
|
409
|
+
it is where the assistant's own threads accumulate, and a user who has
|
|
410
|
+
none yet is exactly who the section's empty state is written for. */
|
|
411
|
+
if (counts.assistantInitiated) {
|
|
412
|
+
sections.push({
|
|
413
|
+
kind: "assistant",
|
|
414
|
+
total: counts.assistantInitiated.total,
|
|
415
|
+
unread: counts.assistantInitiated.unread,
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
378
419
|
for (const row of counts.groups) {
|
|
379
420
|
if (row.groupId === "system:pinned") {
|
|
380
421
|
sections.push({ kind: "pinned", total: row.total, unread: row.unread });
|
|
@@ -655,7 +696,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
655
696
|
},
|
|
656
697
|
summary: "Sidebar section index",
|
|
657
698
|
description:
|
|
658
|
-
"One row per renderable sidebar section (Pinned, each non-empty custom group, each origin channel with unassigned conversations, Chats) with total and unread counts and no conversation rows. Lets a client know which sections exist, and what their badges say, without fetching any conversation list. Totals follow the standard listing visibility; unread applies the same rules as GET /v1/conversations/unread-count scoped to the section. Chats is always present, even at zero: it is the leftover bucket and renders regardless.",
|
|
699
|
+
"One row per renderable sidebar section (Pinned, each non-empty custom group, each origin channel with unassigned conversations, Chats) with total and unread counts and no conversation rows. Lets a client know which sections exist, and what their badges say, without fetching any conversation list. Totals follow the standard listing visibility; unread applies the same rules as GET /v1/conversations/unread-count scoped to the section. Chats is always present, even at zero: it is the leftover bucket and renders regardless. Under the `assistant-initiated-threads` flag an `assistant` section is also present at every count — the threads the assistant started on its own, selectable as a list via `groupId=system:assistant` — and its rows are withheld from Chats so the buckets stay disjoint. With the flag off no `assistant` row is emitted and those conversations are counted in Chats as before.",
|
|
659
700
|
tags: ["conversations"],
|
|
660
701
|
responseBody: conversationSectionsResponseSchema,
|
|
661
702
|
handler: handleGetConversationSections,
|
|
@@ -107,7 +107,8 @@ const createConversationTypeSchema = z.enum([
|
|
|
107
107
|
"background",
|
|
108
108
|
] as const satisfies readonly NonScheduledConversationType[]);
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
/** The conversation an id or key names, or a 404 naming what the caller asked for. */
|
|
111
|
+
export function resolveOrThrow(rawId: string): string {
|
|
111
112
|
const id = resolveConversationId(rawId);
|
|
112
113
|
if (!id) {
|
|
113
114
|
throw new NotFoundError(`Conversation ${rawId} not found`);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type ClientMetadataField,
|
|
9
9
|
sanitizeClientMetadataValue,
|
|
10
10
|
} from "@vellumai/service-contracts/client-metadata";
|
|
11
|
+
import { pickReactionEmojiFields } from "@vellumai/service-contracts/reactions";
|
|
11
12
|
import { v7 as uuidv7 } from "uuid";
|
|
12
13
|
import { z } from "zod";
|
|
13
14
|
|
|
@@ -49,7 +50,10 @@ import {
|
|
|
49
50
|
} from "../../conversations/message-consolidation.js";
|
|
50
51
|
import { createApprovalConversationGenerator } from "../../daemon/approval-generators.js";
|
|
51
52
|
import type { Conversation } from "../../daemon/conversation.js";
|
|
52
|
-
import {
|
|
53
|
+
import {
|
|
54
|
+
isConversationBusyError,
|
|
55
|
+
persistQueuedMessageBody,
|
|
56
|
+
} from "../../daemon/conversation-messaging.js";
|
|
53
57
|
import {
|
|
54
58
|
buildModelInfoEvent,
|
|
55
59
|
formatCleanResult,
|
|
@@ -599,7 +603,6 @@ async function tryConsumeGuardianReply(params: {
|
|
|
599
603
|
|
|
600
604
|
const routerResult = await routeGuardianReply({
|
|
601
605
|
messageText: trimmedContent,
|
|
602
|
-
channel: sourceChannel,
|
|
603
606
|
actor: {
|
|
604
607
|
actorPrincipalId: verifiedActorPrincipalId,
|
|
605
608
|
actorExternalUserId: verifiedActorExternalUserId,
|
|
@@ -1002,6 +1005,7 @@ export async function handleListMessages({
|
|
|
1002
1005
|
let noResponse: boolean | undefined;
|
|
1003
1006
|
let reaction: ConversationMessage["reaction"];
|
|
1004
1007
|
let providerError: ConversationMessage["providerError"];
|
|
1008
|
+
let deletedAt: number | undefined;
|
|
1005
1009
|
if (msg.metadata) {
|
|
1006
1010
|
try {
|
|
1007
1011
|
const meta = JSON.parse(msg.metadata) as Record<string, unknown>;
|
|
@@ -1016,21 +1020,34 @@ export async function handleListMessages({
|
|
|
1016
1020
|
if (isNoResponseMetadata(meta)) {
|
|
1017
1021
|
noResponse = true;
|
|
1018
1022
|
}
|
|
1019
|
-
//
|
|
1020
|
-
//
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1023
|
+
// Channel facts live in the row's provider envelope. A reaction row,
|
|
1024
|
+
// either direction, projects its structured fact so clients never
|
|
1025
|
+
// render the stored "[reaction]" sentinel; a row deleted on its
|
|
1026
|
+
// channel projects the deletion so clients render a tombstone over
|
|
1027
|
+
// the content the row keeps for audit. The substring guard keeps the
|
|
1028
|
+
// envelope parse off rows that can carry neither fact: both
|
|
1029
|
+
// envelopes spell these keys literally.
|
|
1030
|
+
if (
|
|
1031
|
+
msg.metadata.includes("reaction") ||
|
|
1032
|
+
msg.metadata.includes("deletedAt")
|
|
1033
|
+
) {
|
|
1034
|
+
const providerMeta = readProviderMetadata(msg.metadata);
|
|
1035
|
+
if (providerMeta?.eventKind === "reaction" && providerMeta.reaction) {
|
|
1036
|
+
const r = providerMeta.reaction;
|
|
1024
1037
|
reaction = {
|
|
1025
|
-
emoji:
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1038
|
+
emoji: r.emoji,
|
|
1039
|
+
...pickReactionEmojiFields(r),
|
|
1040
|
+
op: r.op,
|
|
1041
|
+
targetMessageId: r.targetMessageId,
|
|
1042
|
+
...(r.actorDisplayName
|
|
1043
|
+
? { actorDisplayName: r.actorDisplayName }
|
|
1030
1044
|
: {}),
|
|
1031
1045
|
...(msg.role === "assistant" ? { selfAuthored: true } : {}),
|
|
1032
1046
|
};
|
|
1033
1047
|
}
|
|
1048
|
+
if (providerMeta?.deletedAt !== undefined) {
|
|
1049
|
+
deletedAt = providerMeta.deletedAt;
|
|
1050
|
+
}
|
|
1034
1051
|
}
|
|
1035
1052
|
// Daemon-persisted provider-failure notices carry the classified
|
|
1036
1053
|
// error code/category so clients can render a themed card instead
|
|
@@ -1088,6 +1105,7 @@ export async function handleListMessages({
|
|
|
1088
1105
|
reaction,
|
|
1089
1106
|
providerError,
|
|
1090
1107
|
slackMessage,
|
|
1108
|
+
deletedAt,
|
|
1091
1109
|
clientMessageId: msg.clientMessageId ?? undefined,
|
|
1092
1110
|
};
|
|
1093
1111
|
});
|
|
@@ -1298,6 +1316,7 @@ export async function handleListMessages({
|
|
|
1298
1316
|
...(m.reaction ? { reaction: m.reaction } : {}),
|
|
1299
1317
|
...(m.providerError ? { providerError: m.providerError } : {}),
|
|
1300
1318
|
...(m.slackMessage ? { slackMessage: m.slackMessage } : {}),
|
|
1319
|
+
...(m.deletedAt !== undefined ? { deletedAt: m.deletedAt } : {}),
|
|
1301
1320
|
};
|
|
1302
1321
|
}),
|
|
1303
1322
|
);
|
|
@@ -2043,23 +2062,15 @@ export async function handleSendMessage(
|
|
|
2043
2062
|
? buildSelfIntroMessage(body.onboarding ?? undefined)
|
|
2044
2063
|
: undefined;
|
|
2045
2064
|
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
} else if (isWakeUp && selfIntro) {
|
|
2056
|
-
// Rewrite to the self-introduction and fall through to real inference
|
|
2057
|
-
// (mirrors the scan path above).
|
|
2058
|
-
effectiveContent = selfIntro;
|
|
2059
|
-
} else if (isWakeUp) {
|
|
2060
|
-
const cannedGreeting = getCannedFirstGreeting(body.onboarding ?? undefined);
|
|
2061
|
-
|
|
2062
|
-
conversation.setProcessing(true);
|
|
2065
|
+
/**
|
|
2066
|
+
* Answer a first wake-up with the canned greeting, under a claim the caller
|
|
2067
|
+
* already holds. Its own function so the branch below can decline the claim
|
|
2068
|
+
* and fall through to the ordinary path instead.
|
|
2069
|
+
*/
|
|
2070
|
+
const serveCannedFirstGreeting = async (
|
|
2071
|
+
cannedGreeting: string,
|
|
2072
|
+
greetingOwner: number,
|
|
2073
|
+
) => {
|
|
2063
2074
|
let cleanupDeferred = false;
|
|
2064
2075
|
try {
|
|
2065
2076
|
const rawContent = content ?? "";
|
|
@@ -2122,6 +2133,7 @@ export async function handleSendMessage(
|
|
|
2122
2133
|
|
|
2123
2134
|
scheduleCannedReplyRelease({
|
|
2124
2135
|
conversation,
|
|
2136
|
+
owner: greetingOwner,
|
|
2125
2137
|
origin: "canned_greeting",
|
|
2126
2138
|
emit: () => {
|
|
2127
2139
|
broadcastMessage({
|
|
@@ -2159,11 +2171,41 @@ export async function handleSendMessage(
|
|
|
2159
2171
|
cleanupDeferred = true;
|
|
2160
2172
|
return response;
|
|
2161
2173
|
} finally {
|
|
2162
|
-
if (!cleanupDeferred && conversation.
|
|
2163
|
-
conversation.setProcessing(false);
|
|
2174
|
+
if (!cleanupDeferred && conversation.releaseProcessing(greetingOwner)) {
|
|
2164
2175
|
void conversation.kickDrainQueue("loop_complete", "send_error_path");
|
|
2165
2176
|
}
|
|
2166
2177
|
}
|
|
2178
|
+
};
|
|
2179
|
+
|
|
2180
|
+
let effectiveContent: string | undefined;
|
|
2181
|
+
if (isScanPath) {
|
|
2182
|
+
const scanVariant = websiteUrl
|
|
2183
|
+
? ("website" as const)
|
|
2184
|
+
: ("content-source" as const);
|
|
2185
|
+
effectiveContent = buildScanFirstMessage(scanUrl, scanVariant);
|
|
2186
|
+
// Fall through to normal inference path below
|
|
2187
|
+
} else if (selfIntroGreetingEnabled && body.onboarding?.initialMessage) {
|
|
2188
|
+
effectiveContent = body.onboarding.initialMessage;
|
|
2189
|
+
} else if (isWakeUp && selfIntro) {
|
|
2190
|
+
// Rewrite to the self-introduction and fall through to real inference
|
|
2191
|
+
// (mirrors the scan path above).
|
|
2192
|
+
effectiveContent = selfIntro;
|
|
2193
|
+
} else if (isWakeUp) {
|
|
2194
|
+
const cannedGreeting = getCannedFirstGreeting(body.onboarding ?? undefined);
|
|
2195
|
+
|
|
2196
|
+
// Taken rather than set. The idle read behind this branch is several
|
|
2197
|
+
// awaits old, so a hold taken since belongs to a real turn, and greeting
|
|
2198
|
+
// over it would run two operations into one history. Declining and
|
|
2199
|
+
// falling through is what the sibling branches above already do, and the
|
|
2200
|
+
// gate below turns that into the queued answer a busy conversation owes.
|
|
2201
|
+
const greetingOwner = await conversation.acquireProcessingFenced();
|
|
2202
|
+
if (greetingOwner !== null) {
|
|
2203
|
+
return serveCannedFirstGreeting(cannedGreeting, greetingOwner);
|
|
2204
|
+
}
|
|
2205
|
+
log.info(
|
|
2206
|
+
{ conversationId: mapping.conversationId },
|
|
2207
|
+
"Canned first greeting yielded: the conversation is already held",
|
|
2208
|
+
);
|
|
2167
2209
|
}
|
|
2168
2210
|
|
|
2169
2211
|
if (isFirstOnboarding) {
|
|
@@ -2236,11 +2278,23 @@ export async function handleSendMessage(
|
|
|
2236
2278
|
);
|
|
2237
2279
|
}
|
|
2238
2280
|
|
|
2239
|
-
|
|
2281
|
+
/**
|
|
2282
|
+
* Put the message on the conversation's queue and answer with the
|
|
2283
|
+
* accepted-and-queued shape.
|
|
2284
|
+
*
|
|
2285
|
+
* Reached from the check just below, and again from every point past it that
|
|
2286
|
+
* finds the conversation busy after all. That check and the acquire which
|
|
2287
|
+
* actually starts a turn are separated by guardian cleanup, history scoping
|
|
2288
|
+
* and slash resolution, so anything taking the flag inside those awaits
|
|
2289
|
+
* leaves a send that has to queue rather than fail. Answering the same way
|
|
2290
|
+
* from both means a client cannot tell which side of the awaits the
|
|
2291
|
+
* conversation went busy on.
|
|
2292
|
+
*/
|
|
2293
|
+
const queueSend = async (content: string) => {
|
|
2240
2294
|
// Queue the message so it's processed when the current turn completes
|
|
2241
2295
|
const requestId = uuidv7();
|
|
2242
2296
|
const enqueueResult = conversation.enqueueMessage({
|
|
2243
|
-
content
|
|
2297
|
+
content,
|
|
2244
2298
|
attachments,
|
|
2245
2299
|
onEvent: broadcastMessage,
|
|
2246
2300
|
requestId,
|
|
@@ -2325,6 +2379,10 @@ export async function handleSendMessage(
|
|
|
2325
2379
|
conversationId: mapping.conversationId,
|
|
2326
2380
|
requestId,
|
|
2327
2381
|
};
|
|
2382
|
+
};
|
|
2383
|
+
|
|
2384
|
+
if (conversation.isProcessing()) {
|
|
2385
|
+
return queueSend(contentAfterScan);
|
|
2328
2386
|
}
|
|
2329
2387
|
|
|
2330
2388
|
// Auto-deny pending confirmations for idle conversations. The legacy
|
|
@@ -2405,7 +2463,10 @@ export async function handleSendMessage(
|
|
|
2405
2463
|
const slashResult = await resolveSlash(rawContent, slashContext);
|
|
2406
2464
|
|
|
2407
2465
|
if (slashResult.kind === "unknown") {
|
|
2408
|
-
conversation.
|
|
2466
|
+
const slashOwner = await conversation.acquireProcessingFenced();
|
|
2467
|
+
if (slashOwner === null) {
|
|
2468
|
+
return queueSend(rawContent);
|
|
2469
|
+
}
|
|
2409
2470
|
let cleanupDeferred = false;
|
|
2410
2471
|
try {
|
|
2411
2472
|
const slashMeta = {
|
|
@@ -2470,6 +2531,7 @@ export async function handleSendMessage(
|
|
|
2470
2531
|
const message = slashResult.message;
|
|
2471
2532
|
scheduleCannedReplyRelease({
|
|
2472
2533
|
conversation,
|
|
2534
|
+
owner: slashOwner,
|
|
2473
2535
|
origin: "slash_command",
|
|
2474
2536
|
emit: () => {
|
|
2475
2537
|
broadcastMessage({
|
|
@@ -2503,15 +2565,17 @@ export async function handleSendMessage(
|
|
|
2503
2565
|
} finally {
|
|
2504
2566
|
// No-op for the slash-command early-return path (handled inside
|
|
2505
2567
|
// setTimeout above), but still needed for error paths.
|
|
2506
|
-
if (!cleanupDeferred && conversation.
|
|
2507
|
-
conversation.setProcessing(false);
|
|
2568
|
+
if (!cleanupDeferred && conversation.releaseProcessing(slashOwner)) {
|
|
2508
2569
|
void conversation.kickDrainQueue("loop_complete", "send_error_path");
|
|
2509
2570
|
}
|
|
2510
2571
|
}
|
|
2511
2572
|
}
|
|
2512
2573
|
|
|
2513
2574
|
if (slashResult.kind === "compact") {
|
|
2514
|
-
conversation.
|
|
2575
|
+
const compactOwner = await conversation.acquireProcessingFenced();
|
|
2576
|
+
if (compactOwner === null) {
|
|
2577
|
+
return queueSend(rawContent);
|
|
2578
|
+
}
|
|
2515
2579
|
const slashMeta = {
|
|
2516
2580
|
userMessageChannel: sourceChannel,
|
|
2517
2581
|
assistantMessageChannel: sourceChannel,
|
|
@@ -2532,12 +2596,12 @@ export async function handleSendMessage(
|
|
|
2532
2596
|
// The fire-and-forget compaction below owns clearing `processing`, but a
|
|
2533
2597
|
// throw from this initial persist never reaches it — reset here so the
|
|
2534
2598
|
// conversation isn't stranded in queued mode.
|
|
2535
|
-
conversation.
|
|
2599
|
+
conversation.releaseProcessing(compactOwner);
|
|
2536
2600
|
void conversation.kickDrainQueue("loop_complete", "compact_command");
|
|
2537
2601
|
throw err;
|
|
2538
2602
|
}
|
|
2539
2603
|
if (persisted.deduplicated) {
|
|
2540
|
-
conversation.
|
|
2604
|
+
conversation.releaseProcessing(compactOwner);
|
|
2541
2605
|
void conversation.kickDrainQueue("loop_complete", "compact_dedup");
|
|
2542
2606
|
return {
|
|
2543
2607
|
accepted: true,
|
|
@@ -2589,7 +2653,7 @@ export async function handleSendMessage(
|
|
|
2589
2653
|
retryable: true,
|
|
2590
2654
|
});
|
|
2591
2655
|
} finally {
|
|
2592
|
-
conversation.
|
|
2656
|
+
conversation.releaseProcessing(compactOwner);
|
|
2593
2657
|
void conversation.kickDrainQueue("loop_complete", "compact_command");
|
|
2594
2658
|
}
|
|
2595
2659
|
})();
|
|
@@ -2602,7 +2666,10 @@ export async function handleSendMessage(
|
|
|
2602
2666
|
}
|
|
2603
2667
|
|
|
2604
2668
|
if (slashResult.kind === "clean") {
|
|
2605
|
-
conversation.
|
|
2669
|
+
const cleanOwner = await conversation.acquireProcessingFenced();
|
|
2670
|
+
if (cleanOwner === null) {
|
|
2671
|
+
return queueSend(rawContent);
|
|
2672
|
+
}
|
|
2606
2673
|
const conversationId = mapping.conversationId;
|
|
2607
2674
|
// Outer try/finally guarantees the processing flag is cleared (and the
|
|
2608
2675
|
// queue drained) on every failure path — including a throw from the
|
|
@@ -2668,7 +2735,7 @@ export async function handleSendMessage(
|
|
|
2668
2735
|
conversationId,
|
|
2669
2736
|
};
|
|
2670
2737
|
} finally {
|
|
2671
|
-
conversation.
|
|
2738
|
+
conversation.releaseProcessing(cleanOwner);
|
|
2672
2739
|
void conversation.kickDrainQueue("loop_complete", "clean_command");
|
|
2673
2740
|
}
|
|
2674
2741
|
}
|
|
@@ -2676,23 +2743,35 @@ export async function handleSendMessage(
|
|
|
2676
2743
|
const resolvedContent = slashResult.content;
|
|
2677
2744
|
|
|
2678
2745
|
const requestId = uuidv7();
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2746
|
+
let persistResult: Awaited<
|
|
2747
|
+
ReturnType<typeof conversation.persistUserMessage>
|
|
2748
|
+
>;
|
|
2749
|
+
try {
|
|
2750
|
+
persistResult = await conversation.persistUserMessage({
|
|
2751
|
+
content: resolvedContent,
|
|
2752
|
+
attachments,
|
|
2753
|
+
requestId,
|
|
2754
|
+
metadata: withClientMetadata(
|
|
2755
|
+
body.automated === true || body.hidden === true
|
|
2756
|
+
? {
|
|
2757
|
+
...(body.automated === true ? { automated: true } : {}),
|
|
2758
|
+
...(body.hidden === true ? { hidden: true } : {}),
|
|
2759
|
+
}
|
|
2760
|
+
: undefined,
|
|
2761
|
+
clientMetadata,
|
|
2762
|
+
),
|
|
2763
|
+
scripted: body.scripted,
|
|
2764
|
+
clientMessageId,
|
|
2765
|
+
...(clientOs ? { requestClientOs: clientOs } : {}),
|
|
2766
|
+
});
|
|
2767
|
+
} catch (err) {
|
|
2768
|
+
if (isConversationBusyError(err)) {
|
|
2769
|
+
// The flag went to someone else inside the awaits above. This is the
|
|
2770
|
+
// same message the check at the top would have queued, so queue it.
|
|
2771
|
+
return queueSend(resolvedContent);
|
|
2772
|
+
}
|
|
2773
|
+
throw err;
|
|
2774
|
+
}
|
|
2696
2775
|
|
|
2697
2776
|
const messageId = persistResult.id;
|
|
2698
2777
|
|