@vellumai/assistant 0.11.8 → 0.11.9-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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__/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__/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 +12 -0
- package/src/cli/commands/channels/__tests__/channels.test.ts +26 -0
- package/src/cli/commands/channels/index.help.ts +15 -1
- 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/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/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/injector.ts +14 -15
- 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 +12 -7
- package/src/providers/inference/auth.ts +6 -6
- package/src/providers/media-resolve.ts +14 -0
- package/src/providers/model-catalog.ts +31 -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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Reaction intercept stage, on any channel.
|
|
3
3
|
*
|
|
4
|
-
* Reactions are
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Reactions are not messages and not access attempts. They are dispatched
|
|
5
|
+
* here *before* the message pipeline (ACL, admission floor, disk-pressure
|
|
6
|
+
* block, conversation binding) so that:
|
|
7
7
|
*
|
|
8
8
|
* - a 👍 never triggers an ingress access challenge / verification handshake
|
|
9
9
|
* or an access-request notification (LUM-2489),
|
|
@@ -11,50 +11,64 @@
|
|
|
11
11
|
* row — it is dropped as channel noise,
|
|
12
12
|
* - a known contact's reaction is recorded as an inline transcript signal in
|
|
13
13
|
* the conversation of the message it was attached to, whether that message
|
|
14
|
-
* arrived from the channel or the assistant posted it
|
|
15
|
-
* - a guardian's reaction on an approval card is routed through the guardian
|
|
16
|
-
* decision pipeline (the same path as buttons and text replies).
|
|
14
|
+
* arrived from the channel or the assistant posted it.
|
|
17
15
|
*
|
|
18
16
|
* The reactor's trust is read solely from the gateway-stamped verdict on
|
|
19
17
|
* `sourceMetadata`; a missing/failed/contradictory verdict fails closed to
|
|
20
|
-
* `unknown` (drop).
|
|
18
|
+
* `unknown` (drop).
|
|
19
|
+
*
|
|
20
|
+
* One reaction shape addresses the assistant rather than annotating the
|
|
21
|
+
* room: an admitted actor ADDING a reaction to a message the assistant
|
|
22
|
+
* itself authored. That one wakes a discretion turn through the ordinary
|
|
23
|
+
* channel-turn machinery (`buildReactionWakeTurn`); every other reaction
|
|
24
|
+
* stays a passive transcript row and never drives a turn.
|
|
21
25
|
*/
|
|
22
26
|
import type { SourceMetadata } from "@vellumai/gateway-client";
|
|
23
27
|
import {
|
|
24
28
|
type InboundReactionPayload,
|
|
25
29
|
resolveInboundEventKind,
|
|
26
30
|
} from "@vellumai/gateway-client";
|
|
31
|
+
import { pickReactionEmojiFields } from "@vellumai/service-contracts/reactions";
|
|
27
32
|
|
|
28
33
|
import type { ChannelId, InterfaceId } from "../../../channels/types.js";
|
|
34
|
+
import { createApprovalCopyGenerator } from "../../../daemon/approval-generators.js";
|
|
29
35
|
import { findConversation } from "../../../daemon/conversation-registry.js";
|
|
30
36
|
import { getDiskPressureStatus } from "../../../daemon/disk-pressure-guard.js";
|
|
31
37
|
import { classifyDiskPressureTurnPolicy } from "../../../daemon/disk-pressure-policy.js";
|
|
38
|
+
import { processMessage } from "../../../daemon/process-message.js";
|
|
39
|
+
import { renderReactionHistoryText } from "../../../daemon/reaction-history-render.js";
|
|
32
40
|
import type { TrustContext } from "../../../daemon/trust-context-types.js";
|
|
33
41
|
import { writeSlackMetadata } from "../../../messaging/providers/slack/message-metadata.js";
|
|
34
42
|
import {
|
|
35
43
|
buildNeutralReactionMeta,
|
|
44
|
+
buildReactionRowEnvelope,
|
|
36
45
|
buildSlackReactionMeta,
|
|
37
46
|
} from "../../../messaging/reaction-envelopes.js";
|
|
47
|
+
import { readProviderMetadata } from "../../../messaging/read-provider-metadata.js";
|
|
48
|
+
import { isGuardianCardRow } from "../../../notifications/approval-card-data.js";
|
|
38
49
|
import {
|
|
39
50
|
addMessage,
|
|
51
|
+
getMessageById,
|
|
40
52
|
provenanceFromTrustContext,
|
|
41
53
|
} from "../../../persistence/conversation-crud.js";
|
|
42
54
|
import {
|
|
43
|
-
findConversationByProviderMessageId,
|
|
44
55
|
findInboundEvent,
|
|
56
|
+
findMessageByProviderMessageId,
|
|
45
57
|
findMessageBySourceId,
|
|
46
58
|
linkMessage,
|
|
47
59
|
recordInbound,
|
|
60
|
+
storePayload,
|
|
48
61
|
} from "../../../persistence/delivery-crud.js";
|
|
49
62
|
import { markProcessed } from "../../../persistence/delivery-status.js";
|
|
63
|
+
import { extractTextFromStoredMessageContent } from "../../../persistence/message-content.js";
|
|
50
64
|
import { getLogger } from "../../../util/logger.js";
|
|
51
65
|
import { toTrustContext } from "../../actor-trust-resolver.js";
|
|
52
|
-
import
|
|
66
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../assistant-scope.js";
|
|
53
67
|
import {
|
|
54
68
|
actorTrustContextFromVerdict,
|
|
55
69
|
verdictUsability,
|
|
56
70
|
} from "../../trust-verdict-consumer.js";
|
|
57
|
-
import {
|
|
71
|
+
import { processChannelMessageInBackground } from "./background-dispatch.js";
|
|
58
72
|
|
|
59
73
|
const log = getLogger("runtime-http");
|
|
60
74
|
|
|
@@ -87,7 +101,6 @@ export interface ReactionInterceptParams {
|
|
|
87
101
|
actorUsername: string | undefined;
|
|
88
102
|
replyCallbackUrl: string | undefined;
|
|
89
103
|
sourceMetadata: SourceMetadata | undefined;
|
|
90
|
-
approvalConversationGenerator: ApprovalConversationGenerator | undefined;
|
|
91
104
|
}
|
|
92
105
|
|
|
93
106
|
/**
|
|
@@ -110,7 +123,6 @@ export async function handleReactionIntercept(
|
|
|
110
123
|
actorUsername,
|
|
111
124
|
replyCallbackUrl,
|
|
112
125
|
sourceMetadata,
|
|
113
|
-
approvalConversationGenerator,
|
|
114
126
|
} = params;
|
|
115
127
|
|
|
116
128
|
// Classify the reactor from the gateway-stamped verdict — the same source
|
|
@@ -142,10 +154,9 @@ export async function handleReactionIntercept(
|
|
|
142
154
|
|
|
143
155
|
const reactedMessageTs = reaction.targetMessageId;
|
|
144
156
|
// Respect disk-pressure cleanup so reactions don't bypass storage
|
|
145
|
-
// protection.
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
// "storage is low, try again" notice is meaningless for an emoji.
|
|
157
|
+
// protection. Blocked silently and before any write: a reaction is a
|
|
158
|
+
// passive signal, so the message pipeline's "storage is low, try again"
|
|
159
|
+
// notice is meaningless for an emoji.
|
|
149
160
|
const diskPressure = classifyDiskPressureTurnPolicy(getDiskPressureStatus(), {
|
|
150
161
|
sourceChannel,
|
|
151
162
|
sourceInterface,
|
|
@@ -163,9 +174,9 @@ export async function handleReactionIntercept(
|
|
|
163
174
|
};
|
|
164
175
|
}
|
|
165
176
|
|
|
166
|
-
// A redelivery is answered before
|
|
167
|
-
//
|
|
168
|
-
//
|
|
177
|
+
// A redelivery is answered before the reacted message is resolved, so a
|
|
178
|
+
// repeat delivery costs no lookup. `recordInbound` below is the durable
|
|
179
|
+
// dedup; this is the early out in front of it.
|
|
169
180
|
const alreadyRecorded = findInboundEvent(
|
|
170
181
|
sourceChannel,
|
|
171
182
|
conversationExternalId,
|
|
@@ -179,34 +190,6 @@ export async function handleReactionIntercept(
|
|
|
179
190
|
};
|
|
180
191
|
}
|
|
181
192
|
|
|
182
|
-
// Guardian approval-by-reaction runs before the reacted message is resolved:
|
|
183
|
-
// an approval card is addressed by its own message id and is not always a
|
|
184
|
-
// stored message. Only `reaction:` (added) expresses intent;
|
|
185
|
-
// `reaction_removed:` never does. `handleGuardianReplyIntercept` self-gates
|
|
186
|
-
// on `trustClass === "guardian"`, so a contact's reaction returns no
|
|
187
|
-
// response and falls through to persistence.
|
|
188
|
-
if (reaction.op === "added" && replyCallbackUrl) {
|
|
189
|
-
const reactionIntercept = await handleGuardianReplyIntercept({
|
|
190
|
-
isDuplicate: false,
|
|
191
|
-
trimmedContent: "",
|
|
192
|
-
hasCallbackData: true,
|
|
193
|
-
callbackData: undefined,
|
|
194
|
-
reaction,
|
|
195
|
-
reactedMessageTs,
|
|
196
|
-
rawSenderId,
|
|
197
|
-
canonicalSenderId,
|
|
198
|
-
sourceChannel,
|
|
199
|
-
conversationExternalId,
|
|
200
|
-
replyCallbackUrl,
|
|
201
|
-
trustClass: trustCtx.trustClass,
|
|
202
|
-
guardianPrincipalId: trustCtx.guardianPrincipalId,
|
|
203
|
-
approvalConversationGenerator,
|
|
204
|
-
});
|
|
205
|
-
if (reactionIntercept.response) {
|
|
206
|
-
return reactionIntercept.response;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
193
|
// The reaction belongs to the conversation of the message it was attached
|
|
211
194
|
// to. Slack sends no `thread_ts` on a reaction, so resolving a conversation
|
|
212
195
|
// from the reaction's own address keys one on the reacted message instead of
|
|
@@ -217,19 +200,19 @@ export async function handleReactionIntercept(
|
|
|
217
200
|
// them. The assistant's own posts open no inbound event, so a reaction on
|
|
218
201
|
// one is resolved through the envelope those rows carry (`slackMeta` on
|
|
219
202
|
// Slack, the neutral `providerMeta` everywhere else).
|
|
220
|
-
const
|
|
203
|
+
const target = reactedMessageTs
|
|
221
204
|
? (findMessageBySourceId(
|
|
222
205
|
sourceChannel,
|
|
223
206
|
conversationExternalId,
|
|
224
207
|
reactedMessageTs,
|
|
225
|
-
)
|
|
226
|
-
|
|
208
|
+
) ??
|
|
209
|
+
findMessageByProviderMessageId(
|
|
227
210
|
sourceChannel,
|
|
228
211
|
conversationExternalId,
|
|
229
212
|
reactedMessageTs,
|
|
230
213
|
))
|
|
231
214
|
: null;
|
|
232
|
-
if (!
|
|
215
|
+
if (!target || !reactedMessageTs) {
|
|
233
216
|
log.debug(
|
|
234
217
|
{ sourceChannel, conversationExternalId, reactedMessageTs },
|
|
235
218
|
"Dropping reaction: reacted message is not stored",
|
|
@@ -237,6 +220,23 @@ export async function handleReactionIntercept(
|
|
|
237
220
|
return { accepted: true, reaction: "dropped_unknown_target" };
|
|
238
221
|
}
|
|
239
222
|
|
|
223
|
+
// A guardian card is a delivery projection, not conversation content.
|
|
224
|
+
// `isGuardianCardRow` is the predicate both history assemblers already use
|
|
225
|
+
// to keep these rows out of a turn's history; the wake path is the third
|
|
226
|
+
// reader of the same question. A reaction on a card has nothing to
|
|
227
|
+
// annotate, and the card is assistant-authored, so without this it would
|
|
228
|
+
// read as a signal addressed to the assistant and wake a turn. Channel
|
|
229
|
+
// deliveries persist no row today, but ones paired before the
|
|
230
|
+
// projection-only policy are still stored.
|
|
231
|
+
const targetRow = getMessageById(target.messageId, target.conversationId);
|
|
232
|
+
if (isGuardianCardRow(targetRow?.content)) {
|
|
233
|
+
log.debug(
|
|
234
|
+
{ sourceChannel, conversationExternalId, reactedMessageTs },
|
|
235
|
+
"Dropping reaction: reacted message is a guardian card",
|
|
236
|
+
);
|
|
237
|
+
return { accepted: true, reaction: "dropped_guardian_card" };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
240
|
const result = recordInbound(
|
|
241
241
|
sourceChannel,
|
|
242
242
|
conversationExternalId,
|
|
@@ -245,11 +245,20 @@ export async function handleReactionIntercept(
|
|
|
245
245
|
// own message, and a reaction is not one. Claiming the reacted message's
|
|
246
246
|
// id here would put two linked rows on that id, leaving a later edit or
|
|
247
247
|
// delete of it free to resolve to the reaction instead.
|
|
248
|
-
{ conversationId:
|
|
248
|
+
{ conversationId: target.conversationId },
|
|
249
249
|
);
|
|
250
|
+
if (result.duplicate) {
|
|
251
|
+
return {
|
|
252
|
+
accepted: result.accepted,
|
|
253
|
+
duplicate: true,
|
|
254
|
+
eventId: result.eventId,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
250
257
|
|
|
251
|
-
|
|
252
|
-
|
|
258
|
+
const rowTrustContext = toTrustContext(trustCtx, conversationExternalId);
|
|
259
|
+
const actorExternalId = canonicalSenderId ?? rawSenderId ?? undefined;
|
|
260
|
+
|
|
261
|
+
const persistPassively = async (): Promise<void> => {
|
|
253
262
|
await persistReactionAsMessage({
|
|
254
263
|
conversationId: result.conversationId,
|
|
255
264
|
conversationExternalId,
|
|
@@ -257,21 +266,70 @@ export async function handleReactionIntercept(
|
|
|
257
266
|
sourceChannel,
|
|
258
267
|
reaction,
|
|
259
268
|
actorDisplayName,
|
|
260
|
-
actorExternalId
|
|
269
|
+
actorExternalId,
|
|
261
270
|
reactedMessageTs,
|
|
262
|
-
duplicate:
|
|
263
|
-
trustCtx:
|
|
271
|
+
duplicate: false,
|
|
272
|
+
trustCtx: rowTrustContext,
|
|
264
273
|
});
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
274
|
+
// A passive row lands in the store only, so a resident conversation
|
|
275
|
+
// would otherwise carry it only after eviction. Marking it stale makes
|
|
276
|
+
// the next turn's history reload pick the reaction up.
|
|
277
|
+
findConversation(result.conversationId)?.markHistoryStale();
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// An admitted actor ADDING a reaction to the assistant's own message is a
|
|
281
|
+
// signal addressed to the assistant, like a thumbs-up sent to a person: it
|
|
282
|
+
// wakes a discretion turn. Everything else stays a passive transcript
|
|
283
|
+
// row: removals retract rather than address, and a reaction on another
|
|
284
|
+
// participant's message is between-humans signaling. Mechanics on
|
|
285
|
+
// `buildReactionWakeTurn`.
|
|
286
|
+
const wakeTurn =
|
|
287
|
+
reaction.op === "added" && replyCallbackUrl && sourceInterface
|
|
288
|
+
? buildReactionWakeTurn({
|
|
289
|
+
target,
|
|
290
|
+
targetRow,
|
|
291
|
+
result,
|
|
292
|
+
reaction,
|
|
293
|
+
sourceChannel,
|
|
294
|
+
sourceInterface,
|
|
295
|
+
conversationExternalId,
|
|
296
|
+
replyCallbackUrl,
|
|
297
|
+
actorDisplayName,
|
|
298
|
+
actorExternalId,
|
|
299
|
+
reactedMessageTs,
|
|
300
|
+
trustCtx: rowTrustContext,
|
|
301
|
+
chatType: sourceMetadata?.chatType?.trim() || undefined,
|
|
302
|
+
persistPassively,
|
|
303
|
+
})
|
|
304
|
+
: null;
|
|
305
|
+
if (wakeTurn) {
|
|
306
|
+
try {
|
|
307
|
+
wakeTurn();
|
|
308
|
+
return {
|
|
309
|
+
accepted: result.accepted,
|
|
310
|
+
duplicate: false,
|
|
311
|
+
eventId: result.eventId,
|
|
312
|
+
reaction: "wake_dispatched",
|
|
313
|
+
};
|
|
314
|
+
} catch (err) {
|
|
315
|
+
// Dispatch startup failed before the background turn owns the event
|
|
316
|
+
// (its own failures degrade through `onTurnLostToBusy`). The dedup
|
|
317
|
+
// record already exists, so a gateway retry would short-circuit and
|
|
318
|
+
// leave the reaction unrecorded entirely; fall through to the passive
|
|
319
|
+
// row, which is what this reaction would have been without the wake.
|
|
320
|
+
log.error(
|
|
321
|
+
{ err, conversationId: result.conversationId, eventId: result.eventId },
|
|
322
|
+
"Reaction wake dispatch failed to start; recording the passive row",
|
|
323
|
+
);
|
|
270
324
|
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
await persistPassively();
|
|
271
329
|
} catch (err) {
|
|
272
330
|
log.error(
|
|
273
331
|
{ err, conversationId: result.conversationId, eventId: result.eventId },
|
|
274
|
-
"Failed to persist
|
|
332
|
+
"Failed to persist reaction event",
|
|
275
333
|
);
|
|
276
334
|
}
|
|
277
335
|
|
|
@@ -282,11 +340,163 @@ export async function handleReactionIntercept(
|
|
|
282
340
|
};
|
|
283
341
|
}
|
|
284
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Build the dispatch thunk for a reaction-driven discretion turn, or null
|
|
345
|
+
* when the reaction does not qualify: only a reaction on a message the
|
|
346
|
+
* assistant itself authored addresses the assistant.
|
|
347
|
+
*
|
|
348
|
+
* The turn rides `processChannelMessageInBackground` unchanged, so it defers
|
|
349
|
+
* behind an in-flight turn, streams and delivers its reply through the
|
|
350
|
+
* channel rail, and a `<no_response/>` outcome is suppressed everywhere. Its
|
|
351
|
+
* persisted user row carries the same reaction envelope the passive path
|
|
352
|
+
* writes, so the row reads as a reaction to every envelope consumer, and its
|
|
353
|
+
* live content is the same line `renderReactionHistoryText` produces at
|
|
354
|
+
* reload, so the model sees one wording on both paths.
|
|
355
|
+
*
|
|
356
|
+
* Wake turns are at-most-once. The retry sweep's processing lane rebuilds a
|
|
357
|
+
* turn from its stored payload as a plain message, which would corrupt a
|
|
358
|
+
* reaction into a fabricated user message, so the event is marked processed
|
|
359
|
+
* up front and the payload it stores is delivery-only: it names where a
|
|
360
|
+
* generated reply goes and carries no content to rebuild a turn from. A
|
|
361
|
+
* crash mid-turn therefore loses the discretionary turn, never a row the
|
|
362
|
+
* store already held. The one recoverable loss, a non-channel turn stealing
|
|
363
|
+
* the lock after admission, degrades through `persistPassively` to exactly
|
|
364
|
+
* the row the passive path would have written.
|
|
365
|
+
*/
|
|
366
|
+
function buildReactionWakeTurn(params: {
|
|
367
|
+
target: { messageId: string; conversationId: string };
|
|
368
|
+
targetRow: ReturnType<typeof getMessageById>;
|
|
369
|
+
result: { eventId: string; conversationId: string };
|
|
370
|
+
reaction: InboundReactionPayload;
|
|
371
|
+
sourceChannel: ChannelId;
|
|
372
|
+
sourceInterface: InterfaceId;
|
|
373
|
+
conversationExternalId: string;
|
|
374
|
+
replyCallbackUrl: string;
|
|
375
|
+
actorDisplayName: string | undefined;
|
|
376
|
+
actorExternalId: string | undefined;
|
|
377
|
+
reactedMessageTs: string;
|
|
378
|
+
trustCtx: TrustContext;
|
|
379
|
+
chatType: string | undefined;
|
|
380
|
+
persistPassively: () => Promise<void>;
|
|
381
|
+
}): (() => void) | null {
|
|
382
|
+
const targetRow = params.targetRow;
|
|
383
|
+
if (!targetRow || targetRow.role !== "assistant") {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const facts = reactionFacts(params);
|
|
388
|
+
const neutralMeta = buildNeutralReactionMeta(facts);
|
|
389
|
+
const targetText = extractTextFromStoredMessageContent(targetRow.content);
|
|
390
|
+
const content = renderReactionHistoryText(
|
|
391
|
+
neutralMeta,
|
|
392
|
+
() => targetText || undefined,
|
|
393
|
+
{ selfAuthored: false },
|
|
394
|
+
);
|
|
395
|
+
if (!content) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
// The reaction envelope rides the same carrier lanes ordinary ingress
|
|
399
|
+
// uses: the neutral `channelInbound` for every non-Slack channel (its
|
|
400
|
+
// lane validates through the canonical schema and passes reaction-kind
|
|
401
|
+
// envelopes verbatim), and the transitional Slack-only field while Slack
|
|
402
|
+
// still writes its own envelope. The passive path keeps
|
|
403
|
+
// `buildReactionRowEnvelope`; the lanes here materialize the same
|
|
404
|
+
// builders' facts, so the two writers still cannot drift.
|
|
405
|
+
const envelopeCarrier =
|
|
406
|
+
params.sourceChannel === "slack"
|
|
407
|
+
? {
|
|
408
|
+
slackReactionRowMeta: writeSlackMetadata(
|
|
409
|
+
buildSlackReactionMeta(facts),
|
|
410
|
+
),
|
|
411
|
+
}
|
|
412
|
+
: { channelInbound: neutralMeta };
|
|
413
|
+
|
|
414
|
+
const replyCallbackUrl = resolveWakeReplyCallbackUrl(
|
|
415
|
+
params.sourceChannel,
|
|
416
|
+
params.replyCallbackUrl,
|
|
417
|
+
targetRow.metadata,
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
return () => {
|
|
421
|
+
// A delivery-only payload: the fields `channel-retry-sweep`'s delivery
|
|
422
|
+
// lane needs to re-post a generated reply whose first delivery failed
|
|
423
|
+
// transiently. It deliberately carries no `content` or `sourceChannel`,
|
|
424
|
+
// so it can never be replayed as a message turn even if this event
|
|
425
|
+
// somehow reached the processing lane; that replay is what the
|
|
426
|
+
// at-most-once design exists to prevent.
|
|
427
|
+
storePayload(params.result.eventId, {
|
|
428
|
+
replyCallbackUrl,
|
|
429
|
+
externalChatId: params.conversationExternalId,
|
|
430
|
+
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
431
|
+
});
|
|
432
|
+
markProcessed(params.result.eventId);
|
|
433
|
+
processChannelMessageInBackground({
|
|
434
|
+
processMessage,
|
|
435
|
+
conversationId: params.result.conversationId,
|
|
436
|
+
eventId: params.result.eventId,
|
|
437
|
+
content,
|
|
438
|
+
sourceChannel: params.sourceChannel,
|
|
439
|
+
sourceInterface: params.sourceInterface,
|
|
440
|
+
externalChatId: params.conversationExternalId,
|
|
441
|
+
trustCtx: params.trustCtx,
|
|
442
|
+
metadataHints: [],
|
|
443
|
+
replyCallbackUrl,
|
|
444
|
+
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
445
|
+
approvalCopyGenerator: createApprovalCopyGenerator(),
|
|
446
|
+
...(params.chatType ? { chatType: params.chatType } : {}),
|
|
447
|
+
...envelopeCarrier,
|
|
448
|
+
clientMessageId: `reaction:${params.result.eventId}`,
|
|
449
|
+
skipUserMessageIndexing: true,
|
|
450
|
+
onTurnLostToBusy: params.persistPassively,
|
|
451
|
+
});
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Where a wake turn's reply goes, which is the conversation the reacted
|
|
457
|
+
* message lives in rather than the reaction's own address.
|
|
458
|
+
*
|
|
459
|
+
* A reaction event names no thread of its own, so Slack's normalizer fills
|
|
460
|
+
* the callback's `threadTs` with the REACTED MESSAGE's ts. Delivering
|
|
461
|
+
* through it would root the reply at that message: a thread of its own off
|
|
462
|
+
* a channel post, or an invalid parent when the target is itself a threaded
|
|
463
|
+
* reply. Slack rows record the thread they live in, so the target row's
|
|
464
|
+
* stored `threadId` is the authoritative destination, and its absence means
|
|
465
|
+
* the target sits at the channel root, where the reply belongs too.
|
|
466
|
+
*
|
|
467
|
+
* Every other channel is already correct: a Discord thread IS a channel, so
|
|
468
|
+
* a reaction inside one carries that thread as its address.
|
|
469
|
+
*/
|
|
470
|
+
function resolveWakeReplyCallbackUrl(
|
|
471
|
+
sourceChannel: ChannelId,
|
|
472
|
+
reactionCallbackUrl: string,
|
|
473
|
+
targetMetadata: string | null,
|
|
474
|
+
): string {
|
|
475
|
+
if (sourceChannel !== "slack") {
|
|
476
|
+
return reactionCallbackUrl;
|
|
477
|
+
}
|
|
478
|
+
let url: URL;
|
|
479
|
+
try {
|
|
480
|
+
url = new URL(reactionCallbackUrl);
|
|
481
|
+
} catch {
|
|
482
|
+
return reactionCallbackUrl;
|
|
483
|
+
}
|
|
484
|
+
const targetThreadTs = readProviderMetadata(targetMetadata, {
|
|
485
|
+
allowFlatLegacy: true,
|
|
486
|
+
})?.threadId;
|
|
487
|
+
if (targetThreadTs) {
|
|
488
|
+
url.searchParams.set("threadTs", targetThreadTs);
|
|
489
|
+
} else {
|
|
490
|
+
url.searchParams.delete("threadTs");
|
|
491
|
+
}
|
|
492
|
+
return url.toString();
|
|
493
|
+
}
|
|
494
|
+
|
|
285
495
|
/**
|
|
286
496
|
* Persist a Slack reaction event as a `messages` row with a `slackMeta`
|
|
287
497
|
* envelope so the renderer can surface it inline in the chronological
|
|
288
|
-
* transcript.
|
|
289
|
-
*
|
|
498
|
+
* transcript. The row is written and the inbound event is linked; the wake
|
|
499
|
+
* decision is the caller's (`buildReactionWakeTurn`).
|
|
290
500
|
*
|
|
291
501
|
* The caller is expected to have run `recordInbound` already so that
|
|
292
502
|
* deduplication and conversation resolution have happened. Duplicate inbound
|
|
@@ -319,42 +529,10 @@ async function persistReactionAsMessage(params: {
|
|
|
319
529
|
if (params.duplicate) {
|
|
320
530
|
return;
|
|
321
531
|
}
|
|
322
|
-
const facts =
|
|
323
|
-
channel: params.sourceChannel,
|
|
324
|
-
chatId: params.conversationExternalId,
|
|
325
|
-
targetMessageId: params.reactedMessageTs,
|
|
326
|
-
emoji: params.reaction.emoji,
|
|
327
|
-
op: params.reaction.op,
|
|
328
|
-
...(params.actorExternalId
|
|
329
|
-
? { actorExternalId: params.actorExternalId }
|
|
330
|
-
: {}),
|
|
331
|
-
...(params.actorDisplayName
|
|
332
|
-
? { actorDisplayName: params.actorDisplayName }
|
|
333
|
-
: {}),
|
|
334
|
-
};
|
|
532
|
+
const facts = reactionFacts(params);
|
|
335
533
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
params.conversationId,
|
|
339
|
-
"user",
|
|
340
|
-
"[reaction]",
|
|
341
|
-
{
|
|
342
|
-
metadata: {
|
|
343
|
-
...provenanceFromTrustContext(params.trustCtx),
|
|
344
|
-
providerMeta: JSON.stringify(buildNeutralReactionMeta(facts)),
|
|
345
|
-
},
|
|
346
|
-
skipIndexing: true,
|
|
347
|
-
},
|
|
348
|
-
);
|
|
349
|
-
linkMessage(params.eventId, persisted.id);
|
|
350
|
-
markProcessed(params.eventId);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const slackMeta = buildSlackReactionMeta(facts);
|
|
355
|
-
|
|
356
|
-
// Sentinel content — Slack transcript renderers read `slackMeta` to format
|
|
357
|
-
// the reaction line; the literal text is never displayed to the model.
|
|
534
|
+
// Sentinel content: transcript renderers read the envelope to format the
|
|
535
|
+
// reaction line; the literal text is never displayed to the model.
|
|
358
536
|
const persisted = await addMessage(
|
|
359
537
|
params.conversationId,
|
|
360
538
|
"user",
|
|
@@ -362,7 +540,7 @@ async function persistReactionAsMessage(params: {
|
|
|
362
540
|
{
|
|
363
541
|
metadata: {
|
|
364
542
|
...provenanceFromTrustContext(params.trustCtx),
|
|
365
|
-
|
|
543
|
+
...buildReactionRowEnvelope(facts),
|
|
366
544
|
},
|
|
367
545
|
skipIndexing: true,
|
|
368
546
|
},
|
|
@@ -370,3 +548,32 @@ async function persistReactionAsMessage(params: {
|
|
|
370
548
|
linkMessage(params.eventId, persisted.id);
|
|
371
549
|
markProcessed(params.eventId);
|
|
372
550
|
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* The envelope facts of one inbound reaction, from the intercept's own
|
|
554
|
+
* vocabulary. Shared by the passive row and the wake turn's row so the two
|
|
555
|
+
* writers describe the same reaction identically.
|
|
556
|
+
*/
|
|
557
|
+
function reactionFacts(params: {
|
|
558
|
+
sourceChannel: ChannelId;
|
|
559
|
+
conversationExternalId: string;
|
|
560
|
+
reactedMessageTs: string;
|
|
561
|
+
reaction: InboundReactionPayload;
|
|
562
|
+
actorExternalId?: string;
|
|
563
|
+
actorDisplayName?: string;
|
|
564
|
+
}) {
|
|
565
|
+
return {
|
|
566
|
+
channel: params.sourceChannel,
|
|
567
|
+
chatId: params.conversationExternalId,
|
|
568
|
+
targetMessageId: params.reactedMessageTs,
|
|
569
|
+
emoji: params.reaction.emoji,
|
|
570
|
+
...pickReactionEmojiFields(params.reaction),
|
|
571
|
+
op: params.reaction.op,
|
|
572
|
+
...(params.actorExternalId
|
|
573
|
+
? { actorExternalId: params.actorExternalId }
|
|
574
|
+
: {}),
|
|
575
|
+
...(params.actorDisplayName
|
|
576
|
+
? { actorDisplayName: params.actorDisplayName }
|
|
577
|
+
: {}),
|
|
578
|
+
};
|
|
579
|
+
}
|
|
@@ -139,6 +139,7 @@ import { ROUTES as SCHEDULE_WORKER_ROUTES } from "./schedule-worker-routes.js";
|
|
|
139
139
|
import { ROUTES as SECRET_ROUTES } from "./secret-routes.js";
|
|
140
140
|
import { ROUTES as SEQUENCE_ROUTES } from "./sequence-routes.js";
|
|
141
141
|
import { ROUTES as SETTINGS_ROUTES } from "./settings-routes.js";
|
|
142
|
+
import { ROUTES as SIGHT_FRAME_ROUTES } from "./sight-frame-routes.js";
|
|
142
143
|
import { ROUTES as SKILL_ROUTES } from "./skills-routes.js";
|
|
143
144
|
import { ROUTES as SLACK_CHANNEL_RESOLVE_ROUTES } from "./slack-channel-routes.js";
|
|
144
145
|
import { ROUTES as SOUNDS_CONFIG_ROUTES } from "./sounds-config-routes.js";
|
|
@@ -287,6 +288,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
287
288
|
...SANITY_ROUTES,
|
|
288
289
|
...SECRET_ROUTES,
|
|
289
290
|
...SETTINGS_ROUTES,
|
|
291
|
+
...SIGHT_FRAME_ROUTES,
|
|
290
292
|
...SOUNDS_CONFIG_ROUTES,
|
|
291
293
|
...SKILL_ROUTES,
|
|
292
294
|
...A2A_ROUTES,
|