@vellumai/assistant 0.11.8 → 0.11.9-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +2 -2
- package/Dockerfile +8 -48
- package/docker-entrypoint.sh +5 -1
- package/docker-kata-apt-env.sh +3 -0
- package/docker-kata-apt-shims.sh +127 -0
- package/docker-kata-apt-wrapper.sh +45 -0
- package/docker-kata-chroot-exec.sh +35 -0
- package/docker-kata-pip.sh +8 -2
- package/docs/architecture/memory.md +8 -4
- package/docs/guardian-request-flow.md +18 -14
- package/docs/trusted-contact-access.md +1 -7
- package/node_modules/@vellumai/avatar-catalog/src/catalog.ts +7 -1
- package/node_modules/@vellumai/avatar-catalog/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +0 -18
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +110 -5
- package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +5 -33
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +3 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +100 -9
- package/node_modules/@vellumai/gateway-client/src/index.ts +1 -2
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +9 -0
- package/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/openapi.yaml +193 -4
- package/package.json +2 -2
- package/src/__tests__/access-request-card-view.test.ts +6 -5
- package/src/__tests__/access-request-seed-content-blocks.test.ts +5 -2
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +43 -82
- package/src/__tests__/always-loaded-tools-guard.test.ts +8 -2
- package/src/__tests__/attachment-stored-path-annotation.test.ts +80 -0
- package/src/__tests__/attachments-store.test.ts +27 -0
- package/src/__tests__/attachments.test.ts +43 -0
- package/src/__tests__/canned-reply-release.test.ts +48 -5
- package/src/__tests__/channel-delivery-store.test.ts +129 -18
- package/src/__tests__/channel-reply-delivery.test.ts +553 -93
- package/src/__tests__/channel-retry-sweep.test.ts +199 -0
- package/src/__tests__/client-os-metadata-persistence.test.ts +32 -3
- package/src/__tests__/conversation-error.test.ts +15 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +118 -0
- package/src/__tests__/conversation-pairing.test.ts +141 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +28 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +125 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +79 -0
- package/src/__tests__/conversation-store-ephemeral.test.ts +323 -10
- package/src/__tests__/conversation-sync-tags.test.ts +2 -37
- package/src/__tests__/credential-health-service.test.ts +98 -10
- package/src/__tests__/delete-propagation.test.ts +469 -0
- package/src/__tests__/dm-persistence.test.ts +16 -0
- package/src/__tests__/docker-kata-apt-shims.test.ts +135 -0
- package/src/__tests__/forbidden-legacy-symbols.test.ts +12 -0
- package/src/__tests__/gemini-provider.test.ts +46 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +0 -22
- package/src/__tests__/guardian-gateway-sim.ts +0 -23
- package/src/__tests__/guardian-question-mode.test.ts +108 -0
- package/src/__tests__/guardian-reply-router-answer-mode.test.ts +0 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +26 -12
- package/src/__tests__/host-proxy-interface.test.ts +10 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +16 -0
- package/src/__tests__/injector-v3-suppression.test.ts +43 -9
- package/src/__tests__/list-messages-page-latest.test.ts +127 -0
- package/src/__tests__/list-messages-system-card.test.ts +103 -0
- package/src/__tests__/media-resolve-image-validation.test.ts +77 -0
- package/src/__tests__/notification-decision-fallback.test.ts +199 -77
- package/src/__tests__/notification-decision-strategy.test.ts +198 -213
- package/src/__tests__/notification-discord-adapter.test.ts +25 -0
- package/src/__tests__/notification-slack-adapter.test.ts +133 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +36 -0
- package/src/__tests__/openai-provider.test.ts +5 -5
- package/src/__tests__/outbound-slack-persistence.test.ts +85 -72
- package/src/__tests__/persist-user-message-set-processing-failure.test.ts +83 -65
- package/src/__tests__/platform-client-verify-credential.test.ts +100 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -1
- package/src/__tests__/pricing.test.ts +11 -0
- package/src/__tests__/process-message-display-content.test.ts +20 -9
- package/src/__tests__/processing-acquire-fenced-guard.test.ts +56 -0
- package/src/__tests__/provider-meta-persistence.test.ts +67 -0
- package/src/__tests__/reaction-persistence.test.ts +22 -198
- package/src/__tests__/run-conversation-turn-persistence.test.ts +5 -2
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +16 -0
- package/src/__tests__/skill-load-tool.test.ts +27 -0
- package/src/__tests__/skills.test.ts +34 -1
- package/src/__tests__/strip-memory-injections.test.ts +3 -4
- package/src/__tests__/terminal-tools.test.ts +9 -0
- package/src/__tests__/thread-backfill.test.ts +5 -3
- package/src/__tests__/unified-turn-context-location.test.ts +76 -0
- package/src/__tests__/voice-session-bridge.test.ts +18 -0
- package/src/__tests__/watch-retro-report-payload.test.ts +185 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +82 -0
- package/src/__tests__/workspace-migration-151-repair-renamed-fireworks-deepseek-pro-model-id.test.ts +235 -0
- package/src/__tests__/workspace-migration-152-repair-retired-fireworks-minimax-m2p7-model-id.test.ts +233 -0
- package/src/agent/attachments.ts +13 -2
- package/src/agent/loop.ts +3 -19
- package/src/api/README.md +9 -5
- package/src/api/index.ts +9 -8
- package/src/api/package.json +1 -0
- package/src/api/responses/conversation-message.ts +8 -0
- package/src/api/responses/home.ts +7 -18
- package/src/api/surfaces.ts +114 -7
- package/src/approvals/AGENTS.md +1 -1
- package/src/channels/__tests__/gateway-guardian-requests.test.ts +1 -23
- package/src/channels/__tests__/types.test.ts +22 -1
- package/src/channels/gateway-guardian-requests.ts +0 -22
- package/src/channels/types.ts +8 -6
- package/src/cli/__tests__/catalog-search-help.test.ts +18 -0
- package/src/cli/commands/channels/__tests__/channels.test.ts +26 -0
- package/src/cli/commands/channels/index.help.ts +19 -6
- package/src/cli/commands/channels/index.ts +6 -2
- package/src/cli/commands/db/__tests__/status.test.ts +22 -0
- package/src/cli/commands/db/index.help.ts +1 -1
- package/src/cli/commands/db/status.ts +172 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +29 -0
- package/src/cli/commands/platform/connect.ts +14 -5
- package/src/cli/commands/plugins.help.ts +6 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +0 -8
- package/src/cli/lib/__tests__/install-from-platform.test.ts +72 -0
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +33 -0
- package/src/cli/lib/bundled-marketplace.json +14 -0
- package/src/cli/lib/install-from-github.ts +9 -5
- package/src/cli/lib/install-from-platform.ts +12 -1
- package/src/config/__tests__/assistant-initiated-threads-gate.test.ts +61 -0
- package/src/config/assistant-initiated-threads-gate.ts +50 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -3
- package/src/config/bundled-skills/schedule/SKILL.md +25 -11
- package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +3 -1
- package/src/config/call-site-defaults.ts +4 -1
- package/src/config/feature-flag-registry.json +21 -4
- package/src/config/schemas/memory-v3.ts +4 -3
- package/src/context/strip-injections.ts +17 -56
- package/src/conversations/__tests__/message-consolidation.test.ts +39 -0
- package/src/conversations/message-consolidation.ts +4 -3
- package/src/credential-health/credential-health-service.ts +130 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +31 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +55 -59
- package/src/daemon/conversation-error.ts +2 -0
- package/src/daemon/conversation-messaging.ts +195 -47
- package/src/daemon/conversation-process.ts +20 -5
- package/src/daemon/conversation-runtime-assembly.ts +48 -39
- package/src/daemon/conversation-store.ts +159 -11
- package/src/daemon/conversation-tool-setup.ts +9 -2
- package/src/daemon/conversation.ts +290 -19
- package/src/daemon/dictation-text-processing.ts +2 -7
- package/src/daemon/handlers/config-channels.ts +33 -3
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/shared.ts +9 -0
- package/src/daemon/port-oversized-content.test.ts +117 -0
- package/src/daemon/port-oversized-content.ts +110 -0
- package/src/daemon/process-message.ts +87 -16
- package/src/daemon/reaction-record.test.ts +23 -11
- package/src/daemon/reaction-record.ts +6 -9
- package/src/documents/document-store.ts +1 -5
- package/src/home/conversation-starter-validation.ts +1 -5
- package/src/live-voice/__tests__/live-voice-photo.test.ts +910 -50
- package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +31 -25
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +70 -1
- package/src/live-voice/live-voice-photo.ts +544 -61
- package/src/live-voice/live-voice-session.ts +6 -2
- package/src/live-voice/protocol.ts +20 -1
- package/src/messaging/provider-message-metadata.ts +40 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +10 -2
- package/src/messaging/providers/channel-transport.ts +28 -0
- package/src/messaging/providers/discord/send.ts +3 -2
- package/src/messaging/providers/slack/api.ts +11 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +133 -0
- package/src/messaging/providers/slack/message-metadata.ts +141 -1
- package/src/messaging/providers/slack/send.test.ts +58 -0
- package/src/messaging/providers/slack/send.ts +4 -4
- package/src/messaging/providers/slack/transport.ts +28 -2
- package/src/messaging/providers/telegram-bot/send.test.ts +159 -0
- package/src/messaging/providers/telegram-bot/send.ts +93 -0
- package/src/messaging/providers/telegram-bot/transport.ts +71 -0
- package/src/messaging/reaction-envelopes.test.ts +73 -0
- package/src/messaging/reaction-envelopes.ts +33 -5
- package/src/messaging/read-provider-metadata.ts +4 -3
- package/src/monitoring/recovery/__tests__/stranded-delivery-events.test.ts +208 -0
- package/src/monitoring/recovery/db.ts +35 -0
- package/src/monitoring/recovery/orphaned-channel-events.ts +3 -22
- package/src/monitoring/recovery/run-recovery.ts +6 -2
- package/src/monitoring/recovery/stale-processing.ts +3 -20
- package/src/monitoring/recovery/stranded-delivery-events.ts +68 -0
- package/src/notifications/AGENTS.md +2 -2
- package/src/notifications/README.md +2 -2
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +11 -9
- package/src/notifications/__tests__/broadcaster.test.ts +35 -4
- package/src/notifications/__tests__/notification-utils.test.ts +31 -0
- package/src/notifications/access-request-copy.ts +126 -114
- package/src/notifications/adapters/discord.ts +9 -5
- package/src/notifications/adapters/shared.ts +17 -4
- package/src/notifications/adapters/slack.ts +14 -4
- package/src/notifications/adapters/telegram.ts +8 -4
- package/src/notifications/approval-card-data.ts +5 -2
- package/src/notifications/assistant-reply-producer.ts +7 -7
- package/src/notifications/broadcaster.ts +57 -25
- package/src/notifications/conversation-pairing.ts +68 -2
- package/src/notifications/copy-composer.ts +13 -32
- package/src/notifications/decision-engine.ts +56 -237
- package/src/notifications/guardian-delivery-recorder.ts +5 -9
- package/src/notifications/guardian-feed-projection.ts +0 -15
- package/src/notifications/guardian-question-mode.ts +147 -6
- package/src/notifications/notification-utils.ts +117 -1
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/prompter.ts +1 -1
- package/src/persistence/conversation-crud.ts +93 -14
- package/src/persistence/conversation-queries.ts +144 -17
- package/src/persistence/conversation-types.ts +39 -5
- package/src/persistence/delivery-crud.ts +224 -28
- package/src/persistence/delivery-status.ts +21 -2
- package/src/persistence/migrations/374-channel-inbound-message-id-index.ts +26 -0
- package/src/persistence/migrations/375-create-channel-outbound-posts.ts +52 -0
- package/src/persistence/migrations/__tests__/375-create-channel-outbound-posts.test.ts +84 -0
- package/src/persistence/schema/conversations.ts +71 -2
- package/src/persistence/schema-contract.test.ts +78 -0
- package/src/persistence/schema-contract.ts +96 -0
- package/src/persistence/steps.ts +4 -0
- package/src/platform/client.ts +55 -0
- package/src/plugins/__tests__/mcp-servers.test.ts +46 -0
- package/src/plugins/defaults/injector-order.ts +2 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +2 -2
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +13 -15
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +10 -0
- package/src/plugins/defaults/memory/injectors.ts +1 -1
- package/src/plugins/defaults/memory/memory-marker.ts +17 -7
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +1 -5
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +17 -0
- package/src/plugins/defaults/memory/substrate/skill-content.ts +7 -0
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +54 -41
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +3 -3
- package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +22 -0
- package/src/plugins/defaults/memory/v3/injector.ts +14 -15
- package/src/plugins/defaults/memory/v3/prune.test.ts +20 -2
- package/src/plugins/defaults/memory/v3/render-injection.ts +10 -1
- package/src/plugins/defaults/memory/v3/types.ts +15 -4
- package/src/plugins/defaults/turn-context/injectors.ts +3 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +24 -0
- package/src/plugins/mcp-servers.ts +14 -2
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +50 -0
- package/src/providers/__tests__/registry-native-web-search.test.ts +49 -2
- package/src/providers/anthropic/client.ts +21 -24
- package/src/providers/call-site-routing.ts +5 -4
- package/src/providers/connection-resolution.ts +29 -1
- package/src/providers/content-block-size.test.ts +82 -0
- package/src/providers/content-block-size.ts +99 -0
- package/src/providers/file-block-text.test.ts +64 -0
- package/src/providers/file-block-text.ts +28 -0
- package/src/providers/gemini/client.ts +15 -9
- package/src/providers/inference/auth.ts +6 -6
- package/src/providers/media-resolve.ts +14 -0
- package/src/providers/model-catalog.ts +47 -34
- package/src/providers/openai/chat-completions-provider.ts +9 -25
- package/src/providers/openai/responses-provider.ts +11 -18
- package/src/providers/registry.ts +10 -7
- package/src/providers/routing-identity.ts +2 -1
- package/src/providers/types.ts +1 -4
- package/src/providers/vellum-model-routing.ts +3 -2
- package/src/runtime/AGENTS.md +40 -15
- package/src/runtime/approval-message-composer.ts +1 -6
- package/src/runtime/assistant-event-hub.ts +2 -39
- package/src/runtime/channel-approval-types.ts +1 -5
- package/src/runtime/channel-reply-delivery.ts +182 -115
- package/src/runtime/{slack-reply-session.test.ts → channel-reply-session.test.ts} +294 -156
- package/src/runtime/{slack-reply-session.ts → channel-reply-session.ts} +107 -86
- package/src/runtime/channel-retry-sweep.ts +102 -1
- package/src/runtime/finalize-event-delivery.ts +4 -4
- package/src/runtime/guardian-action-message-composer.ts +1 -4
- package/src/runtime/guardian-reply-router.ts +4 -75
- package/src/runtime/http-router.ts +1 -5
- package/src/runtime/question-request-guardian-bridge.ts +2 -3
- package/src/runtime/routes/__tests__/conversation-list-assistant-section.test.ts +359 -0
- package/src/runtime/routes/__tests__/dictation-command-mode.test.ts +120 -0
- package/src/runtime/routes/__tests__/sight-frame-routes.test.ts +487 -0
- package/src/runtime/routes/acp-routes.ts +1 -1
- package/src/runtime/routes/canned-reply-release.ts +19 -9
- package/src/runtime/routes/channel-route-shared.ts +0 -39
- package/src/runtime/routes/channel-verification-routes.ts +4 -1
- package/src/runtime/routes/conversation-list-routes.ts +44 -3
- package/src/runtime/routes/conversation-management-routes.ts +2 -1
- package/src/runtime/routes/conversation-routes.ts +138 -59
- package/src/runtime/routes/diagnostics-routes.ts +111 -60
- package/src/runtime/routes/guardian-action-routes.ts +10 -14
- package/src/runtime/routes/guardian-approval-interception.ts +0 -5
- package/src/runtime/routes/inbound-message-handler.ts +170 -48
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +4 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +169 -8
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +82 -14
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +23 -48
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +454 -94
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +309 -102
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/platform-routes.ts +99 -10
- package/src/runtime/routes/sight-frame-routes.ts +136 -0
- package/src/runtime/sync/resource-sync-events.ts +14 -37
- package/src/runtime/sync/sync-publisher.test.ts +5 -4
- package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
- package/src/tools/client-os.ts +11 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.ts +2 -2
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/terminal/safe-env.ts +4 -0
- package/src/tools/tool-input-schemas.ts +2 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/tools/ui-surface/definitions.ts +4 -3
- package/src/tools/watch/watch-retro-report.ts +205 -0
- package/src/watch/__tests__/watch-retro.test.ts +268 -40
- package/src/watch/watch-retro.ts +190 -77
- package/src/workspace/migrations/151-repair-renamed-fireworks-deepseek-pro-model-id.ts +195 -0
- package/src/workspace/migrations/152-repair-retired-fireworks-minimax-m2p7-model-id.ts +198 -0
- package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +4 -0
- package/docker-kata-pip-chroot.sh +0 -22
- package/src/__tests__/slack-reaction-approvals.test.ts +0 -97
- package/src/__tests__/slack-reaction-guardian-approval.test.ts +0 -307
- package/src/api/events/conversation-list-invalidated.ts +0 -38
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
type MessageAttachmentInput,
|
|
16
16
|
} from "../agent/attachments.js";
|
|
17
17
|
import { optimizeImageForTransport } from "../agent/image-optimize.js";
|
|
18
|
-
import { createUserMessage } from "../agent/message-types.js";
|
|
19
18
|
import type { AssistantEvent } from "../api/index.js";
|
|
20
19
|
import type {
|
|
21
20
|
TurnChannelContext,
|
|
@@ -27,6 +26,10 @@ import {
|
|
|
27
26
|
parseInterfaceId,
|
|
28
27
|
} from "../channels/types.js";
|
|
29
28
|
import { parseImageDimensions } from "../context/image-dimensions.js";
|
|
29
|
+
import {
|
|
30
|
+
type ProviderMessageMetadata,
|
|
31
|
+
providerMessageMetadataSchema,
|
|
32
|
+
} from "../messaging/provider-message-metadata.js";
|
|
30
33
|
import {
|
|
31
34
|
buildSlackTimezoneMetadata,
|
|
32
35
|
type SlackMessageMetadata,
|
|
@@ -68,11 +71,16 @@ import {
|
|
|
68
71
|
} from "../providers/types.js";
|
|
69
72
|
import type { AuthContext } from "../runtime/auth/types.js";
|
|
70
73
|
import { getLogger } from "../util/logger.js";
|
|
71
|
-
import { withSqliteRetry } from "../util/sqlite-retry.js";
|
|
72
74
|
import type { MessageQueue } from "./conversation-queue-manager.js";
|
|
73
75
|
import type { SlackInboundMessageMetadata } from "./handlers/shared.js";
|
|
74
76
|
import type { UserMessageAttachment } from "./message-protocol.js";
|
|
75
77
|
import type { ConversationTransportMetadata } from "./message-types/conversations.js";
|
|
78
|
+
import {
|
|
79
|
+
assembleUserContentBlocks,
|
|
80
|
+
offloadLinkPlan,
|
|
81
|
+
offloadOversizedText,
|
|
82
|
+
type PortOversizedContext,
|
|
83
|
+
} from "./port-oversized-content.js";
|
|
76
84
|
import type { TrustContext } from "./trust-context-types.js";
|
|
77
85
|
import { restingTrust } from "./trust-context-types.js";
|
|
78
86
|
import { postUnsendableImageNotice } from "./unsendable-image-notice.js";
|
|
@@ -217,6 +225,8 @@ export interface MessagingConversationContext {
|
|
|
217
225
|
messages: Message[];
|
|
218
226
|
isProcessing(): boolean;
|
|
219
227
|
setProcessing(value: boolean): void;
|
|
228
|
+
acquireProcessingFenced(): Promise<number | null>;
|
|
229
|
+
releaseProcessing(owner: number): boolean;
|
|
220
230
|
abortController: AbortController | null;
|
|
221
231
|
currentRequestId?: string;
|
|
222
232
|
readonly queue: MessageQueue;
|
|
@@ -267,11 +277,8 @@ export async function serializePersistedUserMessageContent(
|
|
|
267
277
|
displayContent: string | undefined,
|
|
268
278
|
attachments: MessageAttachmentInput[],
|
|
269
279
|
): Promise<string> {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
displayContent,
|
|
273
|
-
await attachmentsToContentBlocks(attachments),
|
|
274
|
-
);
|
|
280
|
+
const attachmentBlocks = await attachmentsToContentBlocks(attachments);
|
|
281
|
+
return serializeUserContentBlocks(content, displayContent, attachmentBlocks);
|
|
275
282
|
}
|
|
276
283
|
|
|
277
284
|
/**
|
|
@@ -501,17 +508,19 @@ async function prepareUserAttachmentReferences(
|
|
|
501
508
|
* backstop, and swallowed on failure: cleaning up must never mask the error
|
|
502
509
|
* that caused it.
|
|
503
510
|
*/
|
|
504
|
-
function discardAttemptAttachments(attachmentIds: string[]):
|
|
511
|
+
function discardAttemptAttachments(attachmentIds: string[]): string[] {
|
|
505
512
|
if (attachmentIds.length === 0) {
|
|
506
|
-
return;
|
|
513
|
+
return [];
|
|
507
514
|
}
|
|
508
515
|
try {
|
|
509
516
|
deleteOrphanAttachments(attachmentIds);
|
|
517
|
+
return [];
|
|
510
518
|
} catch (err) {
|
|
511
519
|
log.warn(
|
|
512
520
|
{ err, attachmentIds },
|
|
513
521
|
"Could not discard the attachments of a failed persist attempt",
|
|
514
522
|
);
|
|
523
|
+
return attachmentIds;
|
|
515
524
|
}
|
|
516
525
|
}
|
|
517
526
|
|
|
@@ -748,6 +757,37 @@ export function buildSlackMetaForPersistence(params: {
|
|
|
748
757
|
return writeSlackMetadata(slackMeta);
|
|
749
758
|
}
|
|
750
759
|
|
|
760
|
+
/**
|
|
761
|
+
* Build the neutral channel envelope persisted under the `providerMeta` key
|
|
762
|
+
* on a user message's `metadata` JSON, the non-Slack counterpart of
|
|
763
|
+
* {@link buildSlackMetaForPersistence}. Returns `null` (do not include the
|
|
764
|
+
* key) when the turn channel does not match the envelope's own `source`, so
|
|
765
|
+
* a stale plumbing field can never tag a row with another channel's
|
|
766
|
+
* identity. Slack turns also return `null`: Slack still writes `slackMeta`,
|
|
767
|
+
* which `readProviderMetadata` maps onto the neutral shape on read, and a
|
|
768
|
+
* `providerMeta` key on a Slack row would shadow that richer envelope.
|
|
769
|
+
*
|
|
770
|
+
* TRANSITIONAL: the Slack exclusion exists only while Slack writes its own
|
|
771
|
+
* envelope; do not extend it.
|
|
772
|
+
*/
|
|
773
|
+
export function buildProviderMetaForPersistence(params: {
|
|
774
|
+
channelInbound: ProviderMessageMetadata | undefined;
|
|
775
|
+
turnChannel: string | undefined;
|
|
776
|
+
}): string | null {
|
|
777
|
+
const inbound = params.channelInbound;
|
|
778
|
+
if (!inbound) {
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
if (params.turnChannel !== inbound.source || inbound.source === "slack") {
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
const parsed = providerMessageMetadataSchema.safeParse(inbound);
|
|
785
|
+
if (!parsed.success) {
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
return JSON.stringify(parsed.data);
|
|
789
|
+
}
|
|
790
|
+
|
|
751
791
|
// ── EnqueueMessageOptions ────────────────────────────────────────────
|
|
752
792
|
|
|
753
793
|
/** Options for `enqueueMessage`. Only `content` is required; everything
|
|
@@ -961,6 +1001,30 @@ export interface PersistMessageOptions {
|
|
|
961
1001
|
* exactly the one retention has to be able to stub.
|
|
962
1002
|
*/
|
|
963
1003
|
sightFrameAttachmentIds?: readonly string[];
|
|
1004
|
+
/**
|
|
1005
|
+
* Answered synchronously at the top of every insert attempt, immediately
|
|
1006
|
+
* before that attempt's statement. False aborts the persist with a
|
|
1007
|
+
* `MessageInsertPreconditionError` and inserts nothing.
|
|
1008
|
+
*
|
|
1009
|
+
* For a caller whose right to write can lapse while this persist runs.
|
|
1010
|
+
* Everything ahead of the insert (attachment materialization, content
|
|
1011
|
+
* building) is awaited, and the insert retries itself on contention across
|
|
1012
|
+
* an awaited backoff, so a caller that answered before calling has only
|
|
1013
|
+
* answered for the moment it called. Threaded down to the insert rather
|
|
1014
|
+
* than checked here, so each attempt asks for itself.
|
|
1015
|
+
*/
|
|
1016
|
+
insertPrecondition?: () => boolean;
|
|
1017
|
+
/**
|
|
1018
|
+
* Told the ids this attempt materialized for itself and then could not
|
|
1019
|
+
* delete, so a caller that owns the cleanup can come back to them.
|
|
1020
|
+
*
|
|
1021
|
+
* An attachment already linked to another conversation is CLONED into this
|
|
1022
|
+
* one under a fresh id, and only this function knows that id. A caller
|
|
1023
|
+
* retrying the delete under the id it handed in would reclaim nothing: that
|
|
1024
|
+
* row is still linked where it came from, and the clone nobody names
|
|
1025
|
+
* survives every pass.
|
|
1026
|
+
*/
|
|
1027
|
+
onUndiscardedAttachments?: (attachmentIds: readonly string[]) => void;
|
|
964
1028
|
}
|
|
965
1029
|
|
|
966
1030
|
// ── persistUserMessage ───────────────────────────────────────────────
|
|
@@ -1002,38 +1066,46 @@ export async function persistUserMessage(
|
|
|
1002
1066
|
ctx.currentRequestId = reqId;
|
|
1003
1067
|
ctx.abortController = new AbortController();
|
|
1004
1068
|
|
|
1069
|
+
let owner: number | null = null;
|
|
1005
1070
|
try {
|
|
1006
|
-
//
|
|
1007
|
-
//
|
|
1008
|
-
// the
|
|
1009
|
-
//
|
|
1010
|
-
//
|
|
1011
|
-
//
|
|
1012
|
-
//
|
|
1013
|
-
//
|
|
1014
|
-
//
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1071
|
+
// Taking the flag rather than setting it: the read and the take are one
|
|
1072
|
+
// step, so nothing can claim it in between, and the hold is released only
|
|
1073
|
+
// by the claim that took it. Its advisory mirror write is detached, which
|
|
1074
|
+
// is what closes the window a retry around a strict set used to open. That
|
|
1075
|
+
// retry re-ran the set after an awaited backoff, and the set reverted the
|
|
1076
|
+
// in-memory flag before rethrowing, so the conversation read idle for the
|
|
1077
|
+
// length of the sleep and anything waiting for idle could take the flag
|
|
1078
|
+
// while this turn was still starting.
|
|
1079
|
+
// Fenced: the flag is taken and its marker is durable before anything
|
|
1080
|
+
// this turn writes, because a reconnecting client and the out-of-process
|
|
1081
|
+
// retrospective worker read that marker to decide a turn is live. Null is
|
|
1082
|
+
// a conversation that belongs to someone else, whether it was already held
|
|
1083
|
+
// or was claimed away while the marker landed. A throw is the marker
|
|
1084
|
+
// refusing to persist, with the claim already given back.
|
|
1085
|
+
owner = await ctx.acquireProcessingFenced();
|
|
1086
|
+
if (owner === null) {
|
|
1087
|
+
throw new Error(CONVERSATION_BUSY_MESSAGE);
|
|
1088
|
+
}
|
|
1019
1089
|
const result = await persistQueuedMessageBody(ctx, {
|
|
1020
1090
|
...options,
|
|
1021
1091
|
attachments,
|
|
1022
1092
|
requestId: reqId,
|
|
1023
1093
|
});
|
|
1024
1094
|
if (result.deduplicated) {
|
|
1025
|
-
ctx.
|
|
1095
|
+
ctx.releaseProcessing(owner);
|
|
1026
1096
|
ctx.abortController = null;
|
|
1027
1097
|
ctx.currentRequestId = undefined;
|
|
1028
1098
|
}
|
|
1029
1099
|
return result;
|
|
1030
1100
|
} catch (err) {
|
|
1031
|
-
//
|
|
1032
|
-
// or skip the bookkeeping reset.
|
|
1033
|
-
//
|
|
1034
|
-
//
|
|
1101
|
+
// Release this turn's own hold, but never let a failure there mask the
|
|
1102
|
+
// original error or skip the bookkeeping reset. A hold another caller
|
|
1103
|
+
// claimed since is left alone, and one this call never took is nothing to
|
|
1104
|
+
// release.
|
|
1035
1105
|
try {
|
|
1036
|
-
|
|
1106
|
+
if (owner !== null) {
|
|
1107
|
+
ctx.releaseProcessing(owner);
|
|
1108
|
+
}
|
|
1037
1109
|
} catch (clearErr) {
|
|
1038
1110
|
log.error(
|
|
1039
1111
|
{ err: clearErr, conversationId: ctx.conversationId },
|
|
@@ -1088,6 +1160,7 @@ export async function persistQueuedMessageBody(
|
|
|
1088
1160
|
// exactly the shape a link-aware delete reads as collectible, so the cleanup
|
|
1089
1161
|
// must stop at the insert.
|
|
1090
1162
|
let preparedAttachments: PreparedUserAttachment[] = [];
|
|
1163
|
+
let portedAttachmentIds: string[] = [];
|
|
1091
1164
|
let messageInserted = false;
|
|
1092
1165
|
|
|
1093
1166
|
try {
|
|
@@ -1119,11 +1192,13 @@ export async function persistQueuedMessageBody(
|
|
|
1119
1192
|
// never reported.
|
|
1120
1193
|
const {
|
|
1121
1194
|
slackInbound: rawSlackInbound,
|
|
1195
|
+
channelInbound: rawChannelInbound,
|
|
1122
1196
|
scripted: rawScriptedFromMetadata,
|
|
1123
1197
|
clientOsFromRequest: _rawClientOsFromRequest,
|
|
1124
1198
|
...metadataWithoutSlackInbound
|
|
1125
1199
|
} = (metadata ?? {}) as Record<string, unknown> & {
|
|
1126
1200
|
slackInbound?: SlackInboundMessageMetadata;
|
|
1201
|
+
channelInbound?: ProviderMessageMetadata;
|
|
1127
1202
|
scripted?: unknown;
|
|
1128
1203
|
clientOsFromRequest?: unknown;
|
|
1129
1204
|
};
|
|
@@ -1131,6 +1206,10 @@ export async function persistQueuedMessageBody(
|
|
|
1131
1206
|
slackInbound: rawSlackInbound,
|
|
1132
1207
|
turnChannel: turnCtx?.userMessageChannel,
|
|
1133
1208
|
});
|
|
1209
|
+
const providerMeta = buildProviderMetaForPersistence({
|
|
1210
|
+
channelInbound: rawChannelInbound,
|
|
1211
|
+
turnChannel: turnCtx?.userMessageChannel,
|
|
1212
|
+
});
|
|
1134
1213
|
|
|
1135
1214
|
// See the `scripted` note on the merged metadata below. Only a real
|
|
1136
1215
|
// boolean in the bag counts: a stray truthy string must not be read as a
|
|
@@ -1210,6 +1289,7 @@ export async function persistQueuedMessageBody(
|
|
|
1210
1289
|
...(clientOsFromRequest ? { clientOsFromRequest: true } : {}),
|
|
1211
1290
|
...(imageSourcePaths ? { imageSourcePaths } : {}),
|
|
1212
1291
|
...(slackMeta ? { slackMeta } : {}),
|
|
1292
|
+
...(providerMeta ? { providerMeta } : {}),
|
|
1213
1293
|
// Scripted-turn marker, forwarded by `turn-events-store` onto
|
|
1214
1294
|
// `TurnTelemetryEvent.scripted`. Written LAST so it cannot be
|
|
1215
1295
|
// half-overwritten by the raw metadata spread above.
|
|
@@ -1259,22 +1339,42 @@ export async function persistQueuedMessageBody(
|
|
|
1259
1339
|
preparedAttachments,
|
|
1260
1340
|
);
|
|
1261
1341
|
const sightFrameRenames = sightFrameBlockRenames(sightFrameEntries);
|
|
1262
|
-
|
|
1263
|
-
|
|
1342
|
+
|
|
1343
|
+
const portCtx: PortOversizedContext = {
|
|
1344
|
+
conversationId: ctx.conversationId,
|
|
1345
|
+
conversationCreatedAt,
|
|
1346
|
+
};
|
|
1347
|
+
const persistText = displayContent !== undefined ? displayContent : content;
|
|
1348
|
+
const persistOffload = await offloadOversizedText(persistText, portCtx);
|
|
1349
|
+
const liveOffload =
|
|
1350
|
+
persistText === content
|
|
1351
|
+
? persistOffload
|
|
1352
|
+
: await offloadOversizedText(content, portCtx);
|
|
1353
|
+
const attachmentBlocks = preparedAttachments.map((p) => p.block);
|
|
1354
|
+
const persistBlocks = assembleUserContentBlocks(
|
|
1355
|
+
persistOffload.text,
|
|
1356
|
+
attachmentBlocks,
|
|
1357
|
+
persistOffload.fileBlock,
|
|
1358
|
+
);
|
|
1359
|
+
const liveBlocks = assembleUserContentBlocks(
|
|
1360
|
+
liveOffload.text,
|
|
1361
|
+
attachmentBlocks,
|
|
1362
|
+
liveOffload.fileBlock,
|
|
1363
|
+
);
|
|
1364
|
+
const offloadPlan = offloadLinkPlan(
|
|
1365
|
+
persistOffload.attachmentId,
|
|
1366
|
+
liveOffload.attachmentId,
|
|
1367
|
+
);
|
|
1368
|
+
portedAttachmentIds = offloadPlan.linkIds;
|
|
1369
|
+
|
|
1370
|
+
// Workspace-ref blocks (including offloaded oversized text) so the live
|
|
1371
|
+
// turn matches persist: video and huge strings stay off the prompt.
|
|
1264
1372
|
const cleanMessage = restampInlineAttachmentIds(
|
|
1265
|
-
|
|
1373
|
+
{ role: "user", content: liveBlocks },
|
|
1266
1374
|
sightFrameRenames,
|
|
1267
1375
|
);
|
|
1268
1376
|
|
|
1269
|
-
|
|
1270
|
-
// intent stripping), persist that to DB so users see the full message
|
|
1271
|
-
// after restart. The in-memory userMessage (sent to the LLM) still uses
|
|
1272
|
-
// the stripped content.
|
|
1273
|
-
const contentToPersist = serializeUserContentBlocks(
|
|
1274
|
-
content,
|
|
1275
|
-
displayContent,
|
|
1276
|
-
preparedAttachments.map((p) => p.block),
|
|
1277
|
-
);
|
|
1377
|
+
const contentToPersist = JSON.stringify(persistBlocks);
|
|
1278
1378
|
// Composed here rather than with the rest of the metadata above, because
|
|
1279
1379
|
// materialization is what decides the id each frame is stored under.
|
|
1280
1380
|
const sightFrameIds = sightFrameTagIds(sightFrameEntries);
|
|
@@ -1292,11 +1392,17 @@ export async function persistQueuedMessageBody(
|
|
|
1292
1392
|
clientMessageId,
|
|
1293
1393
|
id: requestId,
|
|
1294
1394
|
...(skipIndexing ? { skipIndexing: true } : {}),
|
|
1395
|
+
// Handed down rather than answered here: the insert retries itself on
|
|
1396
|
+
// contention, so the question belongs where each attempt can ask it.
|
|
1397
|
+
...(options.insertPrecondition
|
|
1398
|
+
? { insertPrecondition: options.insertPrecondition }
|
|
1399
|
+
: {}),
|
|
1295
1400
|
},
|
|
1296
1401
|
);
|
|
1297
1402
|
messageInserted = true;
|
|
1298
1403
|
|
|
1299
1404
|
if (persistedUserMessage.deduplicated) {
|
|
1405
|
+
discardAttemptAttachments(portedAttachmentIds);
|
|
1300
1406
|
return { id: persistedUserMessage.id, deduplicated: true };
|
|
1301
1407
|
}
|
|
1302
1408
|
|
|
@@ -1371,14 +1477,52 @@ export async function persistQueuedMessageBody(
|
|
|
1371
1477
|
if (repairedBlocks) {
|
|
1372
1478
|
updateMessageContent(
|
|
1373
1479
|
persistedUserMessage.id,
|
|
1374
|
-
|
|
1480
|
+
JSON.stringify(
|
|
1481
|
+
assembleUserContentBlocks(
|
|
1482
|
+
persistOffload.text,
|
|
1483
|
+
repairedBlocks,
|
|
1484
|
+
persistOffload.fileBlock,
|
|
1485
|
+
),
|
|
1486
|
+
),
|
|
1375
1487
|
);
|
|
1376
1488
|
}
|
|
1377
1489
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1490
|
+
let nextPortPosition =
|
|
1491
|
+
preparedAttachments.reduce((max, p) => Math.max(max, p.position), -1) + 1;
|
|
1492
|
+
for (const attachmentId of portedAttachmentIds) {
|
|
1493
|
+
try {
|
|
1494
|
+
const scopedId = linkAttachmentToMessage(
|
|
1495
|
+
persistedUserMessage.id,
|
|
1496
|
+
attachmentId,
|
|
1497
|
+
nextPortPosition,
|
|
1498
|
+
);
|
|
1499
|
+
nextPortPosition += 1;
|
|
1500
|
+
const storedPath = getFilePathForAttachment(scopedId) ?? undefined;
|
|
1501
|
+
// Display-only offloads stay linked (GC + persisted workspace_ref)
|
|
1502
|
+
// but must not be named on the model-facing list.
|
|
1503
|
+
if (
|
|
1504
|
+
offloadPlan.modelFacingIds.has(attachmentId) &&
|
|
1505
|
+
liveOffload.filename
|
|
1506
|
+
) {
|
|
1507
|
+
sentAttachments.push({
|
|
1508
|
+
filename: liveOffload.filename,
|
|
1509
|
+
mimeType: "text/plain",
|
|
1510
|
+
data: "",
|
|
1511
|
+
storedPath,
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
} catch (err) {
|
|
1515
|
+
log.error(
|
|
1516
|
+
{ attachmentId, err },
|
|
1517
|
+
"Failed to link offloaded oversized content attachment",
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// Same list enrichMessageWithSourcePaths sees, so history reload rebuilds
|
|
1523
|
+
// an identical annotation block (prefix-cache parity).
|
|
1380
1524
|
const attachmentStoredPaths =
|
|
1381
|
-
extractAttachmentStoredPaths(
|
|
1525
|
+
extractAttachmentStoredPaths(sentAttachments);
|
|
1382
1526
|
if (attachmentStoredPaths) {
|
|
1383
1527
|
updateMessageMetadata(persistedUserMessage.id, { attachmentStoredPaths });
|
|
1384
1528
|
}
|
|
@@ -1428,9 +1572,13 @@ export async function persistQueuedMessageBody(
|
|
|
1428
1572
|
ctx.messages.pop();
|
|
1429
1573
|
}
|
|
1430
1574
|
if (!messageInserted) {
|
|
1431
|
-
discardAttemptAttachments(
|
|
1432
|
-
attemptCreatedAttachmentIds(attachmentInputs, preparedAttachments),
|
|
1433
|
-
|
|
1575
|
+
const undiscarded = discardAttemptAttachments([
|
|
1576
|
+
...attemptCreatedAttachmentIds(attachmentInputs, preparedAttachments),
|
|
1577
|
+
...portedAttachmentIds,
|
|
1578
|
+
]);
|
|
1579
|
+
if (undiscarded.length > 0) {
|
|
1580
|
+
options.onUndiscardedAttachments?.(undiscarded);
|
|
1581
|
+
}
|
|
1434
1582
|
}
|
|
1435
1583
|
throw err;
|
|
1436
1584
|
}
|
|
@@ -1932,7 +1932,6 @@ export async function processMessage(
|
|
|
1932
1932
|
if (trimmedContent.length > 0) {
|
|
1933
1933
|
const routerResult = await routeGuardianReply({
|
|
1934
1934
|
messageText: trimmedContent,
|
|
1935
|
-
channel: "vellum",
|
|
1936
1935
|
actor: {
|
|
1937
1936
|
actorPrincipalId:
|
|
1938
1937
|
conversation.trustContext?.guardianPrincipalId ?? undefined,
|
|
@@ -2124,7 +2123,15 @@ export async function processMessage(
|
|
|
2124
2123
|
|
|
2125
2124
|
// /compact — force context compaction, persist exchange, return message ID.
|
|
2126
2125
|
if (slashResult.kind === "compact") {
|
|
2127
|
-
|
|
2126
|
+
// Taken rather than set: `processMessage` never checks the flag itself, so
|
|
2127
|
+
// the idle decision behind this branch was made by a caller several awaits
|
|
2128
|
+
// ago (history scoping, guardian routing, slash resolution all sit
|
|
2129
|
+
// between). A hold taken since belongs to a real turn, and this reports
|
|
2130
|
+
// busy the way that caller's own gate does rather than claiming it away.
|
|
2131
|
+
const compactOwner = await conversation.acquireProcessingFenced();
|
|
2132
|
+
if (compactOwner === null) {
|
|
2133
|
+
throw new Error(CONVERSATION_BUSY_MESSAGE);
|
|
2134
|
+
}
|
|
2128
2135
|
let persistedCompactMessage = false;
|
|
2129
2136
|
try {
|
|
2130
2137
|
const pmTurnCtx = conversation.getTurnChannelContext();
|
|
@@ -2195,7 +2202,7 @@ export async function processMessage(
|
|
|
2195
2202
|
}
|
|
2196
2203
|
throw err;
|
|
2197
2204
|
} finally {
|
|
2198
|
-
conversation.
|
|
2205
|
+
conversation.releaseProcessing(compactOwner);
|
|
2199
2206
|
// `kickQueueDrain` never rejects, so a failed drain in this `finally`
|
|
2200
2207
|
// cannot mask the error the try block is unwinding, and its retry plus
|
|
2201
2208
|
// sender notification replace a silently stranded queue.
|
|
@@ -2205,7 +2212,15 @@ export async function processMessage(
|
|
|
2205
2212
|
|
|
2206
2213
|
// /clean — strip runtime injections, return message ID. No LLM call.
|
|
2207
2214
|
if (slashResult.kind === "clean") {
|
|
2208
|
-
|
|
2215
|
+
// Taken rather than set: `processMessage` never checks the flag itself, so
|
|
2216
|
+
// the idle decision behind this branch was made by a caller several awaits
|
|
2217
|
+
// ago (history scoping, guardian routing, slash resolution all sit
|
|
2218
|
+
// between). A hold taken since belongs to a real turn, and this reports
|
|
2219
|
+
// busy the way that caller's own gate does rather than claiming it away.
|
|
2220
|
+
const cleanOwner = await conversation.acquireProcessingFenced();
|
|
2221
|
+
if (cleanOwner === null) {
|
|
2222
|
+
throw new Error(CONVERSATION_BUSY_MESSAGE);
|
|
2223
|
+
}
|
|
2209
2224
|
let persistedCleanMessage = false;
|
|
2210
2225
|
try {
|
|
2211
2226
|
const pmTurnCtx = conversation.getTurnChannelContext();
|
|
@@ -2272,7 +2287,7 @@ export async function processMessage(
|
|
|
2272
2287
|
}
|
|
2273
2288
|
throw err;
|
|
2274
2289
|
} finally {
|
|
2275
|
-
conversation.
|
|
2290
|
+
conversation.releaseProcessing(cleanOwner);
|
|
2276
2291
|
// Same never-rejecting drain as the `/compact` branch above.
|
|
2277
2292
|
await kickQueueDrain(conversation, "loop_complete", "clean_command");
|
|
2278
2293
|
}
|
|
@@ -27,15 +27,13 @@ import {
|
|
|
27
27
|
quarantineRefusedExchanges,
|
|
28
28
|
} from "../context/refusal-quarantine.js";
|
|
29
29
|
import {
|
|
30
|
+
MEMORY_SPOTLIGHT_MATCHER,
|
|
30
31
|
NOW_SCRATCHPAD_STRIP_PREFIXES,
|
|
31
|
-
|
|
32
|
+
stripTailUserTextBlocksByPrefix,
|
|
32
33
|
stripUserTextBlocksByPrefix,
|
|
33
34
|
} from "../context/strip-injections.js";
|
|
34
35
|
import { getDocumentsForConversation } from "../documents/document-store.js";
|
|
35
|
-
import {
|
|
36
|
-
readSlackMetadata,
|
|
37
|
-
readSlackMetadataFromMessageMetadata,
|
|
38
|
-
} from "../messaging/providers/slack/message-metadata.js";
|
|
36
|
+
import { readSlackMetadataFromMessageMetadata } from "../messaging/providers/slack/message-metadata.js";
|
|
39
37
|
import {
|
|
40
38
|
compareSlackTs,
|
|
41
39
|
extractTagLineTexts,
|
|
@@ -64,6 +62,7 @@ import {
|
|
|
64
62
|
import {
|
|
65
63
|
MEMORY_V3_BLOCK_ID,
|
|
66
64
|
MEMORY_V3_COMMIT_META_KEY,
|
|
65
|
+
MEMORY_V3_SPOTLIGHT_BLOCK_ID,
|
|
67
66
|
} from "../plugins/defaults/memory/v3/types.js";
|
|
68
67
|
import { getRegisteredInjectors } from "../plugins/injector-registry.js";
|
|
69
68
|
import type {
|
|
@@ -74,9 +73,11 @@ import type {
|
|
|
74
73
|
import type { ContentBlock, Message } from "../providers/types.js";
|
|
75
74
|
import type { TrustClass } from "../runtime/actor-trust-resolver.js";
|
|
76
75
|
import { resolveCapabilities } from "../runtime/capabilities.js";
|
|
76
|
+
import { trustClassSchema } from "../runtime/trust-class.js";
|
|
77
77
|
import type { SubagentState } from "../subagent/types.js";
|
|
78
78
|
import { TERMINAL_STATUSES } from "../subagent/types.js";
|
|
79
79
|
import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
|
|
80
|
+
import { safeParseRecord } from "../util/json.js";
|
|
80
81
|
import { getLogger } from "../util/logger.js";
|
|
81
82
|
import { channelSupportsInlineOptions } from "./channel-ui-capability.js";
|
|
82
83
|
import { findConversationOrSubagent } from "./conversation-registry.js";
|
|
@@ -821,7 +822,8 @@ export function buildChannelCapabilityBlock(
|
|
|
821
822
|
caps.supportsVoiceInput &&
|
|
822
823
|
!isGroupChatType(caps.chatType) &&
|
|
823
824
|
clientOs !== "macos" &&
|
|
824
|
-
clientOs !== "windows"
|
|
825
|
+
clientOs !== "windows" &&
|
|
826
|
+
clientOs !== "linux"
|
|
825
827
|
) {
|
|
826
828
|
return null;
|
|
827
829
|
}
|
|
@@ -849,6 +851,13 @@ export function buildChannelCapabilityBlock(
|
|
|
849
851
|
);
|
|
850
852
|
}
|
|
851
853
|
|
|
854
|
+
if (clientOs === "linux") {
|
|
855
|
+
lines.push("");
|
|
856
|
+
lines.push(
|
|
857
|
+
"On Linux, prefer CLI via `host_bash` over computer use tools, which take over the user's cursor. Use foreground computer use only when no scripting alternative exists or the user explicitly asks.",
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
|
|
852
861
|
if (!caps.dashboardCapable) {
|
|
853
862
|
lines.push("");
|
|
854
863
|
lines.push("CHANNEL CONSTRAINTS:");
|
|
@@ -1152,29 +1161,12 @@ function placeholderForBlockType(type: ContentBlock["type"]): string | null {
|
|
|
1152
1161
|
* renderer drops the redundant `@user` placeholder.
|
|
1153
1162
|
*/
|
|
1154
1163
|
function rowToRenderable(row: SlackTranscriptInputRow): RenderableSlackMessage {
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
provenanceTrustClass?: unknown;
|
|
1162
|
-
};
|
|
1163
|
-
if (typeof outer.slackMeta === "string") {
|
|
1164
|
-
slackMeta = readSlackMetadata(outer.slackMeta);
|
|
1165
|
-
}
|
|
1166
|
-
if (
|
|
1167
|
-
outer.provenanceTrustClass === "guardian" ||
|
|
1168
|
-
outer.provenanceTrustClass === "trusted_contact" ||
|
|
1169
|
-
outer.provenanceTrustClass === "unverified_contact" ||
|
|
1170
|
-
outer.provenanceTrustClass === "unknown"
|
|
1171
|
-
) {
|
|
1172
|
-
provenanceTrustClass = outer.provenanceTrustClass;
|
|
1173
|
-
}
|
|
1174
|
-
} catch {
|
|
1175
|
-
// Malformed metadata — fall through to legacy/null treatment.
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1164
|
+
const slackMeta = readSlackMetadataFromMessageMetadata(row.metadata);
|
|
1165
|
+
const provenanceTrustClass = row.metadata
|
|
1166
|
+
? trustClassSchema.safeParse(
|
|
1167
|
+
safeParseRecord(row.metadata).provenanceTrustClass,
|
|
1168
|
+
).data
|
|
1169
|
+
: undefined;
|
|
1178
1170
|
|
|
1179
1171
|
const isReaction = slackMeta?.eventKind === "reaction";
|
|
1180
1172
|
let senderLabel: string | null;
|
|
@@ -1876,6 +1868,13 @@ export interface RuntimeInjectionBlocks {
|
|
|
1876
1868
|
* (`MEMORY_V3_INJECTED_BLOCK_METADATA_KEY`).
|
|
1877
1869
|
*/
|
|
1878
1870
|
memoryV3InjectedBlock?: string;
|
|
1871
|
+
/**
|
|
1872
|
+
* Rendered `<memory_spotlight>` body spliced onto this turn's user
|
|
1873
|
+
* message. Persisted by the user-prompt-submit hook under
|
|
1874
|
+
* `metadata.memoryV3SpotlightBlock`. Historical turns keep the block they
|
|
1875
|
+
* were sent with; a new spotlight is added only on the new tail.
|
|
1876
|
+
*/
|
|
1877
|
+
memoryV3SpotlightBlock?: string;
|
|
1879
1878
|
/**
|
|
1880
1879
|
* True when memory-v3 superseded v2 as this turn's `<memory>` source —
|
|
1881
1880
|
* `memory.v3.live` is on AND the v3 injector produced a block (possibly
|
|
@@ -2065,6 +2064,7 @@ function applyInjectionBlock(
|
|
|
2065
2064
|
];
|
|
2066
2065
|
}
|
|
2067
2066
|
}
|
|
2067
|
+
return runMessages;
|
|
2068
2068
|
}
|
|
2069
2069
|
|
|
2070
2070
|
/**
|
|
@@ -2297,7 +2297,7 @@ function fallbackTurnTrust(
|
|
|
2297
2297
|
* the user tail content.
|
|
2298
2298
|
*
|
|
2299
2299
|
* Returns the final message array plus a `blocks` object holding the exact
|
|
2300
|
-
* injected text for each captured block
|
|
2300
|
+
* injected text for each captured block. Callers persist those bytes to
|
|
2301
2301
|
* message metadata for later byte-exact rehydration.
|
|
2302
2302
|
*/
|
|
2303
2303
|
export async function applyRuntimeInjections(
|
|
@@ -2511,6 +2511,7 @@ export async function applyRuntimeInjections(
|
|
|
2511
2511
|
let pkbSystemReminderCaptured: string | undefined;
|
|
2512
2512
|
let memoryV2StaticCaptured: string | undefined;
|
|
2513
2513
|
let memoryV3Captured: string | undefined;
|
|
2514
|
+
let memoryV3SpotlightCaptured: string | undefined;
|
|
2514
2515
|
let backgroundTurnCaptured: string | undefined;
|
|
2515
2516
|
let channelCapabilitiesCaptured: string | undefined;
|
|
2516
2517
|
let nonInteractiveContextCaptured: string | undefined;
|
|
@@ -2560,6 +2561,11 @@ export async function applyRuntimeInjections(
|
|
|
2560
2561
|
}
|
|
2561
2562
|
break;
|
|
2562
2563
|
}
|
|
2564
|
+
case MEMORY_V3_SPOTLIGHT_BLOCK_ID:
|
|
2565
|
+
if (block.text.length > 0) {
|
|
2566
|
+
memoryV3SpotlightCaptured = block.text;
|
|
2567
|
+
}
|
|
2568
|
+
break;
|
|
2563
2569
|
}
|
|
2564
2570
|
}
|
|
2565
2571
|
}
|
|
@@ -2578,16 +2584,18 @@ export async function applyRuntimeInjections(
|
|
|
2578
2584
|
? injectorChainPieces.join("\n\n")
|
|
2579
2585
|
: undefined;
|
|
2580
2586
|
|
|
2581
|
-
// ── Step 0:
|
|
2587
|
+
// ── Step 0: tail spotlight strip + v2 tail suppression ──
|
|
2582
2588
|
//
|
|
2583
|
-
// Spotlight strip (
|
|
2584
|
-
//
|
|
2585
|
-
//
|
|
2586
|
-
//
|
|
2587
|
-
//
|
|
2588
|
-
// With the v3 flag off no
|
|
2589
|
-
//
|
|
2590
|
-
let runMessagesForAssembly =
|
|
2589
|
+
// Spotlight strip (tail only): mid-turn re-entry and post-compact can
|
|
2590
|
+
// hand back a tail that already carries this turn's `<memory_spotlight>`.
|
|
2591
|
+
// Strip that leftover from the tail so Step 2 splices a single fresh
|
|
2592
|
+
// copy. Historical user messages keep the spotlight they were sent with,
|
|
2593
|
+
// so the provider prefix through those messages stays byte-identical.
|
|
2594
|
+
// Frozen `<memory>` card blocks are untouched. With the v3 flag off no
|
|
2595
|
+
// spotlight blocks exist and this is a content no-op.
|
|
2596
|
+
let runMessagesForAssembly = stripTailUserTextBlocksByPrefix(runMessages, [
|
|
2597
|
+
MEMORY_SPOTLIGHT_MATCHER,
|
|
2598
|
+
]);
|
|
2591
2599
|
|
|
2592
2600
|
// v2 suppression: when `memory.v3.live` is on AND the v3 injector
|
|
2593
2601
|
// produced a block this turn (possibly empty-text on an all-repeat turn), v3
|
|
@@ -2794,6 +2802,7 @@ export async function applyRuntimeInjections(
|
|
|
2794
2802
|
pkbContextBlock: pkbContextCaptured,
|
|
2795
2803
|
memoryV2StaticBlock: memoryV2StaticCaptured,
|
|
2796
2804
|
memoryV3InjectedBlock: memoryV3Captured,
|
|
2805
|
+
memoryV3SpotlightBlock: memoryV3SpotlightCaptured,
|
|
2797
2806
|
backgroundTurnBlock: backgroundTurnCaptured,
|
|
2798
2807
|
channelCapabilitiesBlock: channelCapabilitiesCaptured,
|
|
2799
2808
|
nonInteractiveContextBlock: nonInteractiveContextCaptured,
|