@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
|
@@ -3,6 +3,7 @@ import { randomBytes } from "node:crypto";
|
|
|
3
3
|
import type { GuardianDelivery } from "@vellumai/gateway-client";
|
|
4
4
|
import { hashVerificationSecret } from "@vellumai/gateway-client";
|
|
5
5
|
import { MarkChannelRevokedIpcResponseSchema } from "@vellumai/gateway-client/gateway-ipc-contracts";
|
|
6
|
+
import { z } from "zod";
|
|
6
7
|
|
|
7
8
|
import { startVerificationCall } from "../../calls/call-domain.js";
|
|
8
9
|
import {
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
revokePendingSessions,
|
|
15
16
|
updateSessionDelivery,
|
|
16
17
|
} from "../../channels/gateway-verification-sessions.js";
|
|
17
|
-
import type
|
|
18
|
+
import { CHANNEL_IDS, type ChannelId } from "../../channels/types.js";
|
|
18
19
|
import {
|
|
19
20
|
findContactChannel,
|
|
20
21
|
getChannelById,
|
|
@@ -173,9 +174,38 @@ export async function createInboundChallenge(
|
|
|
173
174
|
};
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Exactly what {@link getVerificationStatus} returns.
|
|
179
|
+
*
|
|
180
|
+
* Its own shape rather than the wide {@link ChannelVerificationSessionResult},
|
|
181
|
+
* which spans five different responses and so marks every field optional. A
|
|
182
|
+
* status caller always gets the binding verdict, and the route publishes this
|
|
183
|
+
* as its response body so clients read it from a generated type.
|
|
184
|
+
*/
|
|
185
|
+
export const verificationStatusResponseSchema = z.object({
|
|
186
|
+
success: z.boolean(),
|
|
187
|
+
bound: z.boolean(),
|
|
188
|
+
channel: z.enum(CHANNEL_IDS),
|
|
189
|
+
assistantId: z.string(),
|
|
190
|
+
hasPendingChallenge: z.boolean(),
|
|
191
|
+
guardianExternalUserId: z.string().optional(),
|
|
192
|
+
guardianDeliveryChatId: z.string().optional(),
|
|
193
|
+
guardianUsername: z.string().optional(),
|
|
194
|
+
guardianDisplayName: z.string().optional(),
|
|
195
|
+
verificationSessionId: z.string().optional(),
|
|
196
|
+
expiresAt: z.number().optional(),
|
|
197
|
+
nextResendAt: z.number().nullable().optional(),
|
|
198
|
+
sendCount: z.number().optional(),
|
|
199
|
+
pendingBootstrap: z.boolean().optional(),
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
export type VerificationStatusResponse = z.infer<
|
|
203
|
+
typeof verificationStatusResponseSchema
|
|
204
|
+
>;
|
|
205
|
+
|
|
176
206
|
export async function getVerificationStatus(
|
|
177
207
|
channel?: ChannelId,
|
|
178
|
-
): Promise<
|
|
208
|
+
): Promise<VerificationStatusResponse> {
|
|
179
209
|
const resolvedAssistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
180
210
|
const resolvedChannel = channel ?? "telegram";
|
|
181
211
|
|
|
@@ -215,7 +245,7 @@ export async function getVerificationStatus(
|
|
|
215
245
|
findActiveSession(resolvedChannel, { verificationPurpose: "guardian" }),
|
|
216
246
|
]);
|
|
217
247
|
const hasPendingChallenge = pendingSession != null;
|
|
218
|
-
const outboundFields:
|
|
248
|
+
const outboundFields: Partial<VerificationStatusResponse> = {};
|
|
219
249
|
if (activeOutboundSession) {
|
|
220
250
|
outboundFields.verificationSessionId = activeOutboundSession.id;
|
|
221
251
|
outboundFields.expiresAt = activeOutboundSession.expiresAt;
|
|
@@ -15,6 +15,7 @@ import { ConfirmationDecisionSchema } from "../../api/responses/conversation-mes
|
|
|
15
15
|
import { getConfig } from "../../config/loader.js";
|
|
16
16
|
import type { LLMCallSite, Speed } from "../../config/schemas/llm.js";
|
|
17
17
|
import { ipcCall as gatewayIpcCall } from "../../ipc/gateway-client.js";
|
|
18
|
+
import type { ProviderMessageMetadata } from "../../messaging/provider-message-metadata.js";
|
|
18
19
|
import type { SecretPromptResult } from "../../permissions/secret-prompt-types.js";
|
|
19
20
|
import type { ConversationCreateType } from "../../persistence/conversation-types.js";
|
|
20
21
|
import { resolveMediaSourceData } from "../../providers/media-resolve.js";
|
|
@@ -223,6 +224,14 @@ export interface ConversationCreateOptions {
|
|
|
223
224
|
* chronological renderer to consume.
|
|
224
225
|
*/
|
|
225
226
|
slackInbound?: SlackInboundMessageMetadata;
|
|
227
|
+
/**
|
|
228
|
+
* Neutral per-row channel envelope captured at the channel ingress
|
|
229
|
+
* boundary, the non-Slack counterpart of `slackInbound`. When present (and
|
|
230
|
+
* the turn channel matches its `source`), persistence writes it as the
|
|
231
|
+
* message's `providerMeta` metadata key so the stored row can say which
|
|
232
|
+
* external message it is, in which thread, from whom.
|
|
233
|
+
*/
|
|
234
|
+
channelInbound?: ProviderMessageMetadata;
|
|
226
235
|
/**
|
|
227
236
|
* Conversation type for newly created conversations. When omitted,
|
|
228
237
|
* defaults to `"standard"` (visible in the sidebar). Set to
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { createConversation } from "../persistence/conversation-crud.js";
|
|
4
|
+
import { getDb } from "../persistence/db-connection.js";
|
|
5
|
+
import { initializeDb } from "../persistence/db-init.js";
|
|
6
|
+
import {
|
|
7
|
+
assembleUserContentBlocks,
|
|
8
|
+
offloadLinkPlan,
|
|
9
|
+
offloadOversizedText,
|
|
10
|
+
OVERSIZED_CONTENT_FILENAME_PREFIX,
|
|
11
|
+
OVERSIZED_CONTENT_NOTE,
|
|
12
|
+
oversizedContentFilename,
|
|
13
|
+
} from "./port-oversized-content.js";
|
|
14
|
+
|
|
15
|
+
const OVERSIZED_FILENAME_RE = /^oversized-content-[0-9a-f-]{36}\.txt$/;
|
|
16
|
+
|
|
17
|
+
await initializeDb();
|
|
18
|
+
|
|
19
|
+
function resetTables() {
|
|
20
|
+
const db = getDb();
|
|
21
|
+
db.run("DELETE FROM message_attachments");
|
|
22
|
+
db.run("DELETE FROM attachments");
|
|
23
|
+
db.run("DELETE FROM messages");
|
|
24
|
+
db.run("DELETE FROM conversations");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("offloadOversizedText", () => {
|
|
28
|
+
test("leaves text under the cap unchanged", async () => {
|
|
29
|
+
resetTables();
|
|
30
|
+
const conv = createConversation();
|
|
31
|
+
const result = await offloadOversizedText("hello", {
|
|
32
|
+
conversationId: conv.id,
|
|
33
|
+
conversationCreatedAt: conv.createdAt,
|
|
34
|
+
});
|
|
35
|
+
expect(result).toEqual({ text: "hello" });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("ports over-cap text into a workspace_ref attachment", async () => {
|
|
39
|
+
resetTables();
|
|
40
|
+
const conv = createConversation();
|
|
41
|
+
const original = "x".repeat(32);
|
|
42
|
+
const result = await offloadOversizedText(
|
|
43
|
+
original,
|
|
44
|
+
{
|
|
45
|
+
conversationId: conv.id,
|
|
46
|
+
conversationCreatedAt: conv.createdAt,
|
|
47
|
+
},
|
|
48
|
+
16,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(result.text).toBe(OVERSIZED_CONTENT_NOTE);
|
|
52
|
+
expect(result.attachmentId).toBeDefined();
|
|
53
|
+
expect(result.filename).toMatch(OVERSIZED_FILENAME_RE);
|
|
54
|
+
expect(result.fileBlock).toMatchObject({
|
|
55
|
+
type: "file",
|
|
56
|
+
source: {
|
|
57
|
+
type: "workspace_ref",
|
|
58
|
+
media_type: "text/plain",
|
|
59
|
+
filename: result.filename,
|
|
60
|
+
attachmentId: result.attachmentId,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
expect(JSON.stringify(result)).not.toContain(original);
|
|
64
|
+
if (result.fileBlock === undefined) {
|
|
65
|
+
throw new Error("expected an offloaded file block");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const blocks = assembleUserContentBlocks(result.text, [], result.fileBlock);
|
|
69
|
+
expect(JSON.stringify(blocks)).not.toContain(original);
|
|
70
|
+
expect(blocks[0]).toEqual({ type: "text", text: OVERSIZED_CONTENT_NOTE });
|
|
71
|
+
expect(blocks[1]).toEqual(result.fileBlock);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("assigns a distinct generated filename per offload in one conversation", async () => {
|
|
75
|
+
resetTables();
|
|
76
|
+
const conv = createConversation();
|
|
77
|
+
const ctx = {
|
|
78
|
+
conversationId: conv.id,
|
|
79
|
+
conversationCreatedAt: conv.createdAt,
|
|
80
|
+
};
|
|
81
|
+
const first = await offloadOversizedText("a".repeat(32), ctx, 16);
|
|
82
|
+
const second = await offloadOversizedText("b".repeat(32), ctx, 16);
|
|
83
|
+
|
|
84
|
+
expect(first.filename).toMatch(OVERSIZED_FILENAME_RE);
|
|
85
|
+
expect(second.filename).toMatch(OVERSIZED_FILENAME_RE);
|
|
86
|
+
expect(first.filename).not.toBe(second.filename);
|
|
87
|
+
expect(first.filename).toContain(OVERSIZED_CONTENT_FILENAME_PREFIX);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("oversizedContentFilename", () => {
|
|
92
|
+
test("embeds the given id", () => {
|
|
93
|
+
expect(oversizedContentFilename("11111111-1111-1111-1111-111111111111")).toBe(
|
|
94
|
+
"oversized-content-11111111-1111-1111-1111-111111111111.txt",
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("offloadLinkPlan", () => {
|
|
100
|
+
test("same persist and live id is linked and model-facing", () => {
|
|
101
|
+
const plan = offloadLinkPlan("att-1", "att-1");
|
|
102
|
+
expect(plan.linkIds).toEqual(["att-1"]);
|
|
103
|
+
expect([...plan.modelFacingIds]).toEqual(["att-1"]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("display-only persist offload is linked but not model-facing", () => {
|
|
107
|
+
const plan = offloadLinkPlan("att-display", undefined);
|
|
108
|
+
expect(plan.linkIds).toEqual(["att-display"]);
|
|
109
|
+
expect(plan.modelFacingIds.size).toBe(0);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("distinct persist and live ids keep only live model-facing", () => {
|
|
113
|
+
const plan = offloadLinkPlan("att-display", "att-live");
|
|
114
|
+
expect(plan.linkIds).toEqual(["att-display", "att-live"]);
|
|
115
|
+
expect([...plan.modelFacingIds]).toEqual(["att-live"]);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Move a user text payload that exceeds the provider string cap into the
|
|
3
|
+
* conversation attachment store, and leave a short note plus a workspace_ref
|
|
4
|
+
* in the message. The full text stays on disk, off the model prompt.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
|
|
9
|
+
import { createInlineAttachment } from "../persistence/attachments-store.js";
|
|
10
|
+
import {
|
|
11
|
+
MAX_PROVIDER_STRING_BYTES,
|
|
12
|
+
utf8ByteLength,
|
|
13
|
+
} from "../providers/content-block-size.js";
|
|
14
|
+
import type { ContentBlock } from "../providers/types.js";
|
|
15
|
+
|
|
16
|
+
export const OVERSIZED_CONTENT_FILENAME_PREFIX = "oversized-content";
|
|
17
|
+
|
|
18
|
+
export const OVERSIZED_CONTENT_NOTE =
|
|
19
|
+
"Content exceeded the provider size limit and was saved as a workspace file.";
|
|
20
|
+
|
|
21
|
+
/** Unique per offload so two oversized payloads in one conversation do not share a name. */
|
|
22
|
+
export function oversizedContentFilename(id: string = randomUUID()): string {
|
|
23
|
+
return `${OVERSIZED_CONTENT_FILENAME_PREFIX}-${id}.txt`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface PortOversizedContext {
|
|
27
|
+
conversationId: string;
|
|
28
|
+
conversationCreatedAt: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface OffloadOversizedTextResult {
|
|
32
|
+
text: string;
|
|
33
|
+
fileBlock?: Extract<ContentBlock, { type: "file" }>;
|
|
34
|
+
attachmentId?: string;
|
|
35
|
+
filename?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function offloadOversizedText(
|
|
39
|
+
text: string,
|
|
40
|
+
ctx: PortOversizedContext,
|
|
41
|
+
maxBytes: number = MAX_PROVIDER_STRING_BYTES,
|
|
42
|
+
): Promise<OffloadOversizedTextResult> {
|
|
43
|
+
if (utf8ByteLength(text) <= maxBytes) {
|
|
44
|
+
return { text };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const filename = oversizedContentFilename();
|
|
48
|
+
const stored = await createInlineAttachment(
|
|
49
|
+
ctx.conversationId,
|
|
50
|
+
ctx.conversationCreatedAt,
|
|
51
|
+
filename,
|
|
52
|
+
"text/plain",
|
|
53
|
+
Buffer.from(text, "utf8").toString("base64"),
|
|
54
|
+
{ skipSizeLimit: true },
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
text: OVERSIZED_CONTENT_NOTE,
|
|
59
|
+
fileBlock: {
|
|
60
|
+
type: "file",
|
|
61
|
+
source: {
|
|
62
|
+
type: "workspace_ref",
|
|
63
|
+
media_type: "text/plain",
|
|
64
|
+
attachmentId: stored.id,
|
|
65
|
+
sizeBytes: stored.sizeBytes,
|
|
66
|
+
filename,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
attachmentId: stored.id,
|
|
70
|
+
filename,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Persist may offload a display-only copy (raw Slack, etc.) that must stay
|
|
76
|
+
* linked for GC but must not be named on the model-facing attachment list.
|
|
77
|
+
* The live offload is the only one `enrichMessageWithSourcePaths` may see.
|
|
78
|
+
*/
|
|
79
|
+
export function offloadLinkPlan(
|
|
80
|
+
persistAttachmentId: string | undefined,
|
|
81
|
+
liveAttachmentId: string | undefined,
|
|
82
|
+
): { linkIds: string[]; modelFacingIds: ReadonlySet<string> } {
|
|
83
|
+
const linkIds = [
|
|
84
|
+
...new Set(
|
|
85
|
+
[persistAttachmentId, liveAttachmentId].filter(
|
|
86
|
+
(id): id is string => id !== undefined,
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
];
|
|
90
|
+
return {
|
|
91
|
+
linkIds,
|
|
92
|
+
modelFacingIds: new Set(liveAttachmentId ? [liveAttachmentId] : []),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function assembleUserContentBlocks(
|
|
97
|
+
text: string,
|
|
98
|
+
attachmentBlocks: ContentBlock[],
|
|
99
|
+
extraFileBlock?: ContentBlock,
|
|
100
|
+
): ContentBlock[] {
|
|
101
|
+
const blocks: ContentBlock[] = [];
|
|
102
|
+
if (text.trim().length > 0) {
|
|
103
|
+
blocks.push({ type: "text", text });
|
|
104
|
+
}
|
|
105
|
+
blocks.push(...attachmentBlocks);
|
|
106
|
+
if (extraFileBlock) {
|
|
107
|
+
blocks.push(extraFileBlock);
|
|
108
|
+
}
|
|
109
|
+
return blocks;
|
|
110
|
+
}
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
import { getLogger } from "../util/logger.js";
|
|
39
39
|
import type { Conversation } from "./conversation.js";
|
|
40
40
|
import {
|
|
41
|
+
buildProviderMetaForPersistence,
|
|
41
42
|
buildSlackMetaForPersistence,
|
|
42
43
|
CONVERSATION_BUSY_MESSAGE,
|
|
43
44
|
serializePersistedUserMessageContent,
|
|
@@ -124,6 +125,17 @@ type ProcessMessageOptions = ConversationCreateOptions & {
|
|
|
124
125
|
* {@link deriveIngressIdempotencyKey}).
|
|
125
126
|
*/
|
|
126
127
|
clientMessageId?: string;
|
|
128
|
+
/**
|
|
129
|
+
* Serialized `slackMeta` reaction envelope for a Slack reaction-driven
|
|
130
|
+
* turn, stamped onto the persisted user row so it reads as a reaction to
|
|
131
|
+
* every envelope consumer. Slack-only on purpose: every other channel's
|
|
132
|
+
* reaction turn rides `channelInbound`, whose lane declines Slack while
|
|
133
|
+
* Slack writes its own envelope.
|
|
134
|
+
*
|
|
135
|
+
* TRANSITIONAL: dies when Slack joins the neutral `providerMeta`
|
|
136
|
+
* envelope; do not extend it.
|
|
137
|
+
*/
|
|
138
|
+
slackReactionRowMeta?: string;
|
|
127
139
|
};
|
|
128
140
|
|
|
129
141
|
/**
|
|
@@ -148,6 +160,58 @@ export function deriveIngressIdempotencyKey(options?: {
|
|
|
148
160
|
return undefined;
|
|
149
161
|
}
|
|
150
162
|
|
|
163
|
+
/**
|
|
164
|
+
* The transient carrier bag `persistUserMessage` strips back out of the
|
|
165
|
+
* metadata: the channel ingress envelopes threaded through options.
|
|
166
|
+
* Undefined when the turn carries neither, so non-channel turns persist no
|
|
167
|
+
* extra keys.
|
|
168
|
+
*/
|
|
169
|
+
function buildIngressCarrierMetadata(
|
|
170
|
+
options?: ProcessMessageOptions,
|
|
171
|
+
): Record<string, unknown> | undefined {
|
|
172
|
+
if (!options?.slackInbound && !options?.channelInbound) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
...(options.slackInbound ? { slackInbound: options.slackInbound } : {}),
|
|
177
|
+
...(options.channelInbound
|
|
178
|
+
? { channelInbound: options.channelInbound }
|
|
179
|
+
: {}),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The metadata a server-side ingress turn persists with its user row: the
|
|
185
|
+
* transient carrier bag above, plus the pre-built `slackMeta` a Slack
|
|
186
|
+
* reaction turn stamps directly (see `slackReactionRowMeta`).
|
|
187
|
+
*/
|
|
188
|
+
function buildPersistMetadata(
|
|
189
|
+
options?: ProcessMessageOptions,
|
|
190
|
+
): Record<string, unknown> | undefined {
|
|
191
|
+
const carrier = buildIngressCarrierMetadata(options);
|
|
192
|
+
if (!options?.slackReactionRowMeta) {
|
|
193
|
+
return carrier;
|
|
194
|
+
}
|
|
195
|
+
return { slackMeta: options.slackReactionRowMeta, ...carrier };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Layer the turn's persisted channel envelopes onto a metadata object:
|
|
200
|
+
* `slackMeta` for Slack turns, `providerMeta` for every other channel.
|
|
201
|
+
* At most one is non-null per turn.
|
|
202
|
+
*/
|
|
203
|
+
function withChannelEnvelopes(
|
|
204
|
+
base: Record<string, unknown>,
|
|
205
|
+
slackMeta: string | null,
|
|
206
|
+
providerMeta: string | null,
|
|
207
|
+
): Record<string, unknown> {
|
|
208
|
+
return {
|
|
209
|
+
...base,
|
|
210
|
+
...(slackMeta ? { slackMeta } : {}),
|
|
211
|
+
...(providerMeta ? { providerMeta } : {}),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
151
215
|
function buildEventEmitter(
|
|
152
216
|
observer?: (msg: AssistantEvent) => void,
|
|
153
217
|
): (msg: AssistantEvent) => void {
|
|
@@ -432,9 +496,14 @@ export async function processMessage(
|
|
|
432
496
|
});
|
|
433
497
|
const slashResult = await resolveSlash(content, slashContext);
|
|
434
498
|
|
|
499
|
+
const turnChannel = conversation.getTurnChannelContext()?.userMessageChannel;
|
|
435
500
|
const slackMeta = buildSlackMetaForPersistence({
|
|
436
501
|
slackInbound: options?.slackInbound,
|
|
437
|
-
turnChannel
|
|
502
|
+
turnChannel,
|
|
503
|
+
});
|
|
504
|
+
const providerMeta = buildProviderMetaForPersistence({
|
|
505
|
+
channelInbound: options?.channelInbound,
|
|
506
|
+
turnChannel,
|
|
438
507
|
});
|
|
439
508
|
|
|
440
509
|
if (slashResult.kind === "unknown") {
|
|
@@ -469,9 +538,11 @@ export async function processMessage(
|
|
|
469
538
|
: {}),
|
|
470
539
|
...(Object.keys(imageSourcePaths).length > 0 ? { imageSourcePaths } : {}),
|
|
471
540
|
};
|
|
472
|
-
const userMetaWithSlack =
|
|
473
|
-
|
|
474
|
-
|
|
541
|
+
const userMetaWithSlack = withChannelEnvelopes(
|
|
542
|
+
serverChannelMeta,
|
|
543
|
+
slackMeta,
|
|
544
|
+
providerMeta,
|
|
545
|
+
);
|
|
475
546
|
const cleanMsg = await createUserMessage(content, attachments);
|
|
476
547
|
const llmMsg = enrichMessageWithSourcePaths(cleanMsg, attachments);
|
|
477
548
|
const persisted = await addMessage(
|
|
@@ -566,9 +637,11 @@ export async function processMessage(
|
|
|
566
637
|
}
|
|
567
638
|
: {}),
|
|
568
639
|
};
|
|
569
|
-
const compactUserMeta =
|
|
570
|
-
|
|
571
|
-
|
|
640
|
+
const compactUserMeta = withChannelEnvelopes(
|
|
641
|
+
compactChannelMeta,
|
|
642
|
+
slackMeta,
|
|
643
|
+
providerMeta,
|
|
644
|
+
);
|
|
572
645
|
const cleanMsg = await createUserMessage(content, attachments);
|
|
573
646
|
const persisted = await addMessage(
|
|
574
647
|
conversationId,
|
|
@@ -624,9 +697,11 @@ export async function processMessage(
|
|
|
624
697
|
}
|
|
625
698
|
: {}),
|
|
626
699
|
};
|
|
627
|
-
const cleanUserMeta =
|
|
628
|
-
|
|
629
|
-
|
|
700
|
+
const cleanUserMeta = withChannelEnvelopes(
|
|
701
|
+
cleanChannelMeta,
|
|
702
|
+
slackMeta,
|
|
703
|
+
providerMeta,
|
|
704
|
+
);
|
|
630
705
|
const cleanMsg = await createUserMessage(content, attachments);
|
|
631
706
|
const persisted = await addMessage(
|
|
632
707
|
conversationId,
|
|
@@ -660,9 +735,7 @@ export async function processMessage(
|
|
|
660
735
|
const resolvedContent = slashResult.content;
|
|
661
736
|
|
|
662
737
|
const requestId = uuidv7();
|
|
663
|
-
const persistMetadata = options
|
|
664
|
-
? { slackInbound: options.slackInbound }
|
|
665
|
-
: undefined;
|
|
738
|
+
const persistMetadata = buildPersistMetadata(options);
|
|
666
739
|
const ingressKey = deriveIngressIdempotencyKey(options);
|
|
667
740
|
const { id: messageId, deduplicated } = await conversation.persistUserMessage(
|
|
668
741
|
{
|
|
@@ -743,9 +816,7 @@ export async function processMessageInBackground(
|
|
|
743
816
|
const emitEvent = buildEventEmitter(options?.onEvent);
|
|
744
817
|
|
|
745
818
|
const requestId = uuidv7();
|
|
746
|
-
const persistMetadata = options
|
|
747
|
-
? { slackInbound: options.slackInbound }
|
|
748
|
-
: undefined;
|
|
819
|
+
const persistMetadata = buildPersistMetadata(options);
|
|
749
820
|
const ingressKey = deriveIngressIdempotencyKey(options);
|
|
750
821
|
const { id: messageId, deduplicated } = await conversation.persistUserMessage(
|
|
751
822
|
{
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
|
+
import { readSlackMetadataFromMessageMetadata } from "../messaging/providers/slack/message-metadata.js";
|
|
3
4
|
import type { QueuedReactionRecord } from "./reaction-record.js";
|
|
4
5
|
|
|
5
6
|
let persisted: Array<{
|
|
@@ -75,24 +76,35 @@ describe("persistReactionRecords", () => {
|
|
|
75
76
|
expect(row.metadata?.slackMeta).toBeUndefined();
|
|
76
77
|
});
|
|
77
78
|
|
|
78
|
-
test("a Slack record writes the
|
|
79
|
+
test("a Slack record writes the neutral envelope, which the Slack context reads through its Slack view", async () => {
|
|
79
80
|
await persistReactionRecords("conv-1", [
|
|
80
81
|
record({ channel: "slack", chatId: "C1", messageId: "1700.1" }),
|
|
81
82
|
]);
|
|
82
83
|
const row = persisted[0]!;
|
|
83
|
-
expect(row.metadata?.
|
|
84
|
-
const
|
|
84
|
+
expect(row.metadata?.slackMeta).toBeUndefined();
|
|
85
|
+
const envelope = JSON.parse(String(row.metadata?.providerMeta)) as {
|
|
85
86
|
source: string;
|
|
86
|
-
|
|
87
|
-
channelTs: string;
|
|
87
|
+
conversationExternalId: string;
|
|
88
88
|
eventKind: string;
|
|
89
|
-
reaction: {
|
|
89
|
+
reaction: { targetMessageId: string; emoji: string; op: string };
|
|
90
90
|
};
|
|
91
|
-
expect(
|
|
92
|
-
expect(
|
|
93
|
-
expect(
|
|
94
|
-
expect(
|
|
95
|
-
|
|
91
|
+
expect(envelope.source).toBe("slack");
|
|
92
|
+
expect(envelope.conversationExternalId).toBe("C1");
|
|
93
|
+
expect(envelope.eventKind).toBe("reaction");
|
|
94
|
+
expect(envelope.reaction).toEqual({
|
|
95
|
+
targetMessageId: "1700.1",
|
|
96
|
+
emoji: "🎉",
|
|
97
|
+
op: "added",
|
|
98
|
+
});
|
|
99
|
+
// The Slack transcript's view of the same row: the reacted message's ts
|
|
100
|
+
// stands in as `channelTs`, as the Slack envelope stores it.
|
|
101
|
+
const view = readSlackMetadataFromMessageMetadata(
|
|
102
|
+
JSON.stringify(row.metadata),
|
|
103
|
+
);
|
|
104
|
+
expect(view?.channelId).toBe("C1");
|
|
105
|
+
expect(view?.channelTs).toBe("1700.1");
|
|
106
|
+
expect(view?.eventKind).toBe("reaction");
|
|
107
|
+
expect(view?.reaction).toEqual({
|
|
96
108
|
emoji: "🎉",
|
|
97
109
|
targetChannelTs: "1700.1",
|
|
98
110
|
op: "added",
|
|
@@ -16,11 +16,7 @@
|
|
|
16
16
|
* included.
|
|
17
17
|
*/
|
|
18
18
|
import type { ChannelId } from "../channels/types.js";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
buildNeutralReactionMeta,
|
|
22
|
-
buildSlackReactionMeta,
|
|
23
|
-
} from "../messaging/reaction-envelopes.js";
|
|
19
|
+
import { buildNeutralReactionMeta } from "../messaging/reaction-envelopes.js";
|
|
24
20
|
import {
|
|
25
21
|
addMessage,
|
|
26
22
|
REACTION_MESSAGE_KIND,
|
|
@@ -58,10 +54,11 @@ export async function persistReactionRecords(
|
|
|
58
54
|
emoji: record.emoji,
|
|
59
55
|
op: record.op,
|
|
60
56
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
// The assistant's own rows carry the neutral envelope on every channel;
|
|
58
|
+
// the Slack renderers read it through the envelope's Slack view.
|
|
59
|
+
const envelope = {
|
|
60
|
+
providerMeta: JSON.stringify(buildNeutralReactionMeta(facts)),
|
|
61
|
+
};
|
|
65
62
|
await addMessage(conversationId, "assistant", "[reaction]", {
|
|
66
63
|
metadata: {
|
|
67
64
|
messageKind: REACTION_MESSAGE_KIND,
|
|
@@ -470,10 +470,6 @@ export type ReplaceInDocumentResult =
|
|
|
470
470
|
| { success: true; replacements_made: number; content_changed: boolean }
|
|
471
471
|
| { success: false; error: string };
|
|
472
472
|
|
|
473
|
-
function escapeRegExpChars(str: string): string {
|
|
474
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
475
|
-
}
|
|
476
|
-
|
|
477
473
|
/**
|
|
478
474
|
* Find and replace text within a document — like sed.
|
|
479
475
|
* Supports literal text and regex patterns with optional backreferences.
|
|
@@ -497,7 +493,7 @@ export function replaceInDocument(
|
|
|
497
493
|
const flags = "g" + (options.caseSensitive === true ? "" : "i");
|
|
498
494
|
const pattern = options.regex
|
|
499
495
|
? new RegExp(find, flags)
|
|
500
|
-
: new RegExp(
|
|
496
|
+
: new RegExp(RegExp.escape(find), flags);
|
|
501
497
|
|
|
502
498
|
const totalMatches = [...row.content.matchAll(pattern)].length;
|
|
503
499
|
if (
|
|
@@ -79,10 +79,6 @@ function mentionsCurrentUser(
|
|
|
79
79
|
context: ConversationStarterValidationContext,
|
|
80
80
|
): boolean {
|
|
81
81
|
return context.userReferences.some((reference) =>
|
|
82
|
-
new RegExp(`\\b${
|
|
82
|
+
new RegExp(`\\b${RegExp.escape(reference)}\\b`, "i").test(text),
|
|
83
83
|
);
|
|
84
84
|
}
|
|
85
|
-
|
|
86
|
-
function escapeRegExp(value: string): string {
|
|
87
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
88
|
-
}
|