@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
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* stays bound to the oldest upload.
|
|
9
9
|
*/
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
11
|
+
import { basename } from "node:path";
|
|
11
12
|
import { beforeEach, describe, expect, test } from "bun:test";
|
|
12
13
|
|
|
13
14
|
import { reinjectAttachmentPathAnnotations } from "../daemon/conversation-lifecycle.js";
|
|
@@ -119,6 +120,85 @@ describe("persistQueuedMessageBody stored path annotations", () => {
|
|
|
119
120
|
expect(rebuiltBlock.text).toBe(annotation.text);
|
|
120
121
|
});
|
|
121
122
|
|
|
123
|
+
test("offloaded oversized text records its stored path for history reload", async () => {
|
|
124
|
+
const conv = createConversation();
|
|
125
|
+
const ctx = makeCtx(conv.id);
|
|
126
|
+
const original = "x".repeat(8_000_001);
|
|
127
|
+
|
|
128
|
+
await persistQueuedMessageBody(ctx, { content: original });
|
|
129
|
+
|
|
130
|
+
const row = rawGet<{ metadata: string; content: string }>(
|
|
131
|
+
"test:offloadMessage",
|
|
132
|
+
"SELECT metadata, content FROM messages ORDER BY created_at DESC LIMIT 1",
|
|
133
|
+
);
|
|
134
|
+
const meta = JSON.parse(row!.metadata) as {
|
|
135
|
+
attachmentStoredPaths?: Record<string, string>;
|
|
136
|
+
};
|
|
137
|
+
const storedPaths = Object.values(meta.attachmentStoredPaths ?? {});
|
|
138
|
+
expect(storedPaths).toHaveLength(1);
|
|
139
|
+
const filename = basename(storedPaths[0]!);
|
|
140
|
+
expect(filename.startsWith("oversized-content-")).toBe(true);
|
|
141
|
+
expect(filename.endsWith(".txt")).toBe(true);
|
|
142
|
+
expect(readFileSync(storedPaths[0]!).toString("utf8")).toBe(original);
|
|
143
|
+
|
|
144
|
+
const annotation = lastAnnotationBlock(ctx);
|
|
145
|
+
expect(annotation.text).toBe(
|
|
146
|
+
`[Attachment "${filename}" is stored at: ${storedPaths[0]}]`,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const persisted = JSON.parse(row!.content) as ContentBlock[];
|
|
150
|
+
const rebuilt = reinjectAttachmentPathAnnotations(
|
|
151
|
+
persisted,
|
|
152
|
+
"user",
|
|
153
|
+
row!.metadata,
|
|
154
|
+
);
|
|
155
|
+
const rebuiltBlock = rebuilt.at(-1) as { type: "text"; text: string };
|
|
156
|
+
expect(rebuiltBlock.text).toBe(annotation.text);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("two oversized messages in one conversation get distinct filenames", async () => {
|
|
160
|
+
const conv = createConversation();
|
|
161
|
+
const ctx = makeCtx(conv.id);
|
|
162
|
+
|
|
163
|
+
await persistQueuedMessageBody(ctx, { content: "a".repeat(8_000_001) });
|
|
164
|
+
await persistQueuedMessageBody(ctx, { content: "b".repeat(8_000_001) });
|
|
165
|
+
|
|
166
|
+
const first = (ctx.messages[0].content as ContentBlock[]).at(-1) as {
|
|
167
|
+
text: string;
|
|
168
|
+
};
|
|
169
|
+
const second = (ctx.messages[1].content as ContentBlock[]).at(-1) as {
|
|
170
|
+
text: string;
|
|
171
|
+
};
|
|
172
|
+
expect(first.text).toContain("oversized-content-");
|
|
173
|
+
expect(second.text).toContain("oversized-content-");
|
|
174
|
+
expect(first.text).not.toBe(second.text);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("display-only offload is not named on the model-facing list", async () => {
|
|
178
|
+
const conv = createConversation();
|
|
179
|
+
const ctx = makeCtx(conv.id);
|
|
180
|
+
const display = "y".repeat(8_000_001);
|
|
181
|
+
|
|
182
|
+
await persistQueuedMessageBody(ctx, {
|
|
183
|
+
content: "fenced model copy",
|
|
184
|
+
displayContent: display,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const llmContent = ctx.messages[0].content as ContentBlock[];
|
|
188
|
+
expect(JSON.stringify(llmContent)).not.toContain("oversized-content-");
|
|
189
|
+
expect(JSON.stringify(llmContent)).toContain("fenced model copy");
|
|
190
|
+
expect(JSON.stringify(llmContent)).not.toContain(display);
|
|
191
|
+
|
|
192
|
+
const row = rawGet<{ metadata: string }>(
|
|
193
|
+
"test:displayOnlyOffloadMetadata",
|
|
194
|
+
"SELECT metadata FROM messages ORDER BY created_at DESC LIMIT 1",
|
|
195
|
+
);
|
|
196
|
+
const meta = JSON.parse(row!.metadata) as {
|
|
197
|
+
attachmentStoredPaths?: Record<string, string>;
|
|
198
|
+
};
|
|
199
|
+
expect(meta.attachmentStoredPaths).toBeUndefined();
|
|
200
|
+
});
|
|
201
|
+
|
|
122
202
|
test("messages without attachments get no annotation block", async () => {
|
|
123
203
|
const conv = createConversation();
|
|
124
204
|
const ctx = makeCtx(conv.id);
|
|
@@ -2,6 +2,10 @@ import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { beforeEach, describe, expect, test } from "bun:test";
|
|
4
4
|
|
|
5
|
+
import {
|
|
6
|
+
offloadOversizedText,
|
|
7
|
+
OVERSIZED_CONTENT_FILENAME_PREFIX,
|
|
8
|
+
} from "../daemon/port-oversized-content.js";
|
|
5
9
|
import {
|
|
6
10
|
attachInlineAttachmentToMessage,
|
|
7
11
|
AttachmentUploadError,
|
|
@@ -456,6 +460,29 @@ describe("createInlineAttachment (workspace_ref persistence)", () => {
|
|
|
456
460
|
stored.id,
|
|
457
461
|
]);
|
|
458
462
|
});
|
|
463
|
+
|
|
464
|
+
test("offloadOversizedText writes the original bytes next to the conversation", async () => {
|
|
465
|
+
const conv = createConversation();
|
|
466
|
+
const original = "x".repeat(32);
|
|
467
|
+
const result = await offloadOversizedText(
|
|
468
|
+
original,
|
|
469
|
+
{
|
|
470
|
+
conversationId: conv.id,
|
|
471
|
+
conversationCreatedAt: conv.createdAt,
|
|
472
|
+
},
|
|
473
|
+
16,
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
expect(result.attachmentId).toBeDefined();
|
|
477
|
+
const filePath = getFilePathForAttachment(result.attachmentId!);
|
|
478
|
+
expect(filePath).toBeTruthy();
|
|
479
|
+
expect(result.filename).toBeDefined();
|
|
480
|
+
expect(filePath!.endsWith(result.filename!)).toBe(true);
|
|
481
|
+
expect(result.filename!.startsWith(`${OVERSIZED_CONTENT_FILENAME_PREFIX}-`)).toBe(
|
|
482
|
+
true,
|
|
483
|
+
);
|
|
484
|
+
expect(readFileSync(filePath!).toString("utf8")).toBe(original);
|
|
485
|
+
});
|
|
459
486
|
});
|
|
460
487
|
|
|
461
488
|
describe("attachInlineAttachmentToMessage filename collisions", () => {
|
|
@@ -127,6 +127,32 @@ describe("attachmentsToReferenceBlocks", () => {
|
|
|
127
127
|
expect(JSON.stringify(blocks)).not.toContain("data");
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
+
test("omits extracted_text on a video workspace_ref", () => {
|
|
131
|
+
const blocks = attachmentsToReferenceBlocks([
|
|
132
|
+
{
|
|
133
|
+
attachmentId: "att-vid",
|
|
134
|
+
filename: "clip.mp4",
|
|
135
|
+
mimeType: "video/mp4",
|
|
136
|
+
sizeBytes: 40_000_000,
|
|
137
|
+
extractedText: "this must not dump into the prompt",
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
expect(blocks).toEqual([
|
|
142
|
+
{
|
|
143
|
+
type: "file",
|
|
144
|
+
source: {
|
|
145
|
+
type: "workspace_ref",
|
|
146
|
+
media_type: "video/mp4",
|
|
147
|
+
attachmentId: "att-vid",
|
|
148
|
+
sizeBytes: 40_000_000,
|
|
149
|
+
filename: "clip.mp4",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
]);
|
|
153
|
+
expect(JSON.stringify(blocks)).not.toContain("extracted");
|
|
154
|
+
});
|
|
155
|
+
|
|
130
156
|
test("builds a file workspace_ref with filename and extracted text", () => {
|
|
131
157
|
const blocks = attachmentsToReferenceBlocks([
|
|
132
158
|
{
|
|
@@ -208,6 +234,23 @@ describe("attachmentsToContentBlocks", () => {
|
|
|
208
234
|
expect(blocks[0].type).toBe("image");
|
|
209
235
|
});
|
|
210
236
|
|
|
237
|
+
test("omits extracted_text on an inline video file block", async () => {
|
|
238
|
+
const blocks = await attachmentsToContentBlocks([
|
|
239
|
+
{
|
|
240
|
+
filename: "clip.mp4",
|
|
241
|
+
mimeType: "video/mp4",
|
|
242
|
+
data: "AAAA",
|
|
243
|
+
extractedText: "must not dump",
|
|
244
|
+
},
|
|
245
|
+
]);
|
|
246
|
+
expect(blocks).toHaveLength(1);
|
|
247
|
+
expect(blocks[0]).toMatchObject({
|
|
248
|
+
type: "file",
|
|
249
|
+
source: { type: "base64", media_type: "video/mp4", filename: "clip.mp4" },
|
|
250
|
+
});
|
|
251
|
+
expect(blocks[0]).not.toHaveProperty("extracted_text");
|
|
252
|
+
});
|
|
253
|
+
|
|
211
254
|
test("creates file content block for non-image mime types", async () => {
|
|
212
255
|
const blocks = await attachmentsToContentBlocks([
|
|
213
256
|
{ filename: "doc.pdf", mimeType: "application/pdf", data: "pdfdata" },
|
|
@@ -5,21 +5,35 @@
|
|
|
5
5
|
* turns get: no agent loop runs, so a throw escaping the burst would latch the
|
|
6
6
|
* conversation "processing" for the life of the daemon and queue every later
|
|
7
7
|
* send behind an idle conversation.
|
|
8
|
+
*
|
|
9
|
+
* It releases the claim its scheduler took, and only that one: the timer fires
|
|
10
|
+
* a tick after the route returned, so a Stop and a fresh acquire can land in
|
|
11
|
+
* between, and clearing there would free a turn that is running.
|
|
8
12
|
*/
|
|
9
13
|
import { describe, expect, test } from "bun:test";
|
|
10
14
|
|
|
11
15
|
import type { QueueDrainReason } from "../daemon/conversation-queue-manager.js";
|
|
12
16
|
import { scheduleCannedReplyRelease } from "../runtime/routes/canned-reply-release.js";
|
|
13
17
|
|
|
14
|
-
function makeConversation() {
|
|
18
|
+
function makeConversation(owner = 1) {
|
|
15
19
|
let processing = true;
|
|
20
|
+
let holder = owner;
|
|
16
21
|
const drainOrigins: string[] = [];
|
|
17
22
|
return {
|
|
18
23
|
isProcessing: () => processing,
|
|
19
24
|
drainOrigins,
|
|
25
|
+
/** The force-clear-then-reacquire a Stop and a new request perform. */
|
|
26
|
+
claimAway: (nextOwner: number) => {
|
|
27
|
+
processing = true;
|
|
28
|
+
holder = nextOwner;
|
|
29
|
+
},
|
|
20
30
|
target: {
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
releaseProcessing: (claim: number) => {
|
|
32
|
+
if (claim !== holder) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
processing = false;
|
|
36
|
+
return true;
|
|
23
37
|
},
|
|
24
38
|
kickDrainQueue: (_reason?: QueueDrainReason, origin?: string) => {
|
|
25
39
|
drainOrigins.push(origin ?? "");
|
|
@@ -41,12 +55,13 @@ describe("scheduleCannedReplyRelease", () => {
|
|
|
41
55
|
|
|
42
56
|
scheduleCannedReplyRelease({
|
|
43
57
|
conversation: {
|
|
44
|
-
|
|
58
|
+
releaseProcessing: (claim: number) => {
|
|
45
59
|
order.push("release");
|
|
46
|
-
conversation.target.
|
|
60
|
+
return conversation.target.releaseProcessing(claim);
|
|
47
61
|
},
|
|
48
62
|
kickDrainQueue: conversation.target.kickDrainQueue,
|
|
49
63
|
},
|
|
64
|
+
owner: 1,
|
|
50
65
|
origin: "canned_greeting",
|
|
51
66
|
emit: () => {
|
|
52
67
|
order.push("emit");
|
|
@@ -72,6 +87,7 @@ describe("scheduleCannedReplyRelease", () => {
|
|
|
72
87
|
|
|
73
88
|
scheduleCannedReplyRelease({
|
|
74
89
|
conversation: conversation.target,
|
|
90
|
+
owner: 1,
|
|
75
91
|
origin: "slash_command",
|
|
76
92
|
emit: () => {
|
|
77
93
|
throw new Error("broadcast exploded");
|
|
@@ -83,4 +99,31 @@ describe("scheduleCannedReplyRelease", () => {
|
|
|
83
99
|
expect(conversation.isProcessing()).toBe(false);
|
|
84
100
|
expect(conversation.drainOrigins).toEqual(["slash_command"]);
|
|
85
101
|
});
|
|
102
|
+
|
|
103
|
+
test("leaves a hold claimed away since it was scheduled alone", async () => {
|
|
104
|
+
// Stop force-clears the flag and the next request acquires, all before
|
|
105
|
+
// this timer runs. Clearing here would release a turn that is running,
|
|
106
|
+
// and kicking the drain would start the next message inside it.
|
|
107
|
+
const conversation = makeConversation();
|
|
108
|
+
const after: string[] = [];
|
|
109
|
+
|
|
110
|
+
scheduleCannedReplyRelease({
|
|
111
|
+
conversation: conversation.target,
|
|
112
|
+
owner: 1,
|
|
113
|
+
origin: "canned_greeting",
|
|
114
|
+
emit: () => {},
|
|
115
|
+
afterRelease: () => {
|
|
116
|
+
after.push("after");
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
conversation.claimAway(2);
|
|
121
|
+
await nextTick();
|
|
122
|
+
|
|
123
|
+
// The newer hold survives, nothing drains into the turn that owns it, and
|
|
124
|
+
// the follow-up work waits for whoever does.
|
|
125
|
+
expect(conversation.isProcessing()).toBe(true);
|
|
126
|
+
expect(conversation.drainOrigins).toEqual([]);
|
|
127
|
+
expect(after).toEqual([]);
|
|
128
|
+
});
|
|
86
129
|
});
|
|
@@ -10,10 +10,11 @@ import { getDb } from "../persistence/db-connection.js";
|
|
|
10
10
|
import { initializeDb } from "../persistence/db-init.js";
|
|
11
11
|
import {
|
|
12
12
|
clearPayload,
|
|
13
|
-
|
|
13
|
+
findMessageByProviderMessageId,
|
|
14
14
|
findMessageBySourceId,
|
|
15
15
|
linkMessage,
|
|
16
16
|
recordInbound,
|
|
17
|
+
recordOutboundPost,
|
|
17
18
|
storePayload,
|
|
18
19
|
} from "../persistence/delivery-crud.js";
|
|
19
20
|
import {
|
|
@@ -52,6 +53,7 @@ await initializeDb();
|
|
|
52
53
|
function resetTables() {
|
|
53
54
|
const db = getDb();
|
|
54
55
|
db.run("DELETE FROM channel_inbound_events");
|
|
56
|
+
db.run("DELETE FROM channel_outbound_posts");
|
|
55
57
|
db.run("DELETE FROM messages");
|
|
56
58
|
db.run("DELETE FROM conversation_keys");
|
|
57
59
|
db.run("DELETE FROM external_conversation_bindings");
|
|
@@ -147,35 +149,144 @@ describe("channel-delivery-store", () => {
|
|
|
147
149
|
.run();
|
|
148
150
|
|
|
149
151
|
expect(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
).toBe(minted.conversationId);
|
|
152
|
+
findMessageByProviderMessageId("discord", chatId, "1234567890123456789"),
|
|
153
|
+
).toEqual({
|
|
154
|
+
messageId: "assistant-post-1",
|
|
155
|
+
conversationId: minted.conversationId,
|
|
156
|
+
});
|
|
156
157
|
// A split reply posts several provider messages from one row; any of
|
|
157
158
|
// their ids resolves it.
|
|
158
159
|
expect(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
).toBe(minted.conversationId);
|
|
160
|
+
findMessageByProviderMessageId("discord", chatId, "2234567890123456789"),
|
|
161
|
+
).toEqual({
|
|
162
|
+
messageId: "assistant-post-1",
|
|
163
|
+
conversationId: minted.conversationId,
|
|
164
|
+
});
|
|
165
165
|
// The scan is scoped to the reaction's own channel address: the same id
|
|
166
166
|
// is invisible from another channel's or another chat's key space.
|
|
167
167
|
expect(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
findMessageByProviderMessageId("telegram", chatId, "1234567890123456789"),
|
|
169
|
+
).toBeNull();
|
|
170
|
+
expect(
|
|
171
|
+
findMessageByProviderMessageId(
|
|
172
|
+
"discord",
|
|
173
|
+
"other-chat",
|
|
171
174
|
"1234567890123456789",
|
|
172
175
|
),
|
|
173
176
|
).toBeNull();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("linked inbound rows are excluded from the provider-id scan; crash-window rows resolve", () => {
|
|
180
|
+
const chatId = "chat-scan-budget";
|
|
181
|
+
const minted = recordInbound("telegram", chatId, "evt-linked", {
|
|
182
|
+
sourceMessageId: "tg-linked-1",
|
|
183
|
+
});
|
|
184
|
+
const envelope = (messageId: string) =>
|
|
185
|
+
JSON.stringify({
|
|
186
|
+
providerMeta: JSON.stringify({
|
|
187
|
+
source: "telegram",
|
|
188
|
+
conversationExternalId: chatId,
|
|
189
|
+
messageId,
|
|
190
|
+
eventKind: "message",
|
|
191
|
+
}),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// A normally-ingested inbound row: linked to its event, so the
|
|
195
|
+
// inbound-event index owns its resolution and the fallback scan must
|
|
196
|
+
// not spend budget on it.
|
|
197
|
+
insertMessage("linked-user-row", minted.conversationId, {});
|
|
198
|
+
getDb()
|
|
199
|
+
.update(messages)
|
|
200
|
+
.set({ metadata: envelope("tg-linked-1") })
|
|
201
|
+
.where(eq(messages.id, "linked-user-row"))
|
|
202
|
+
.run();
|
|
203
|
+
linkMessage(minted.eventId, "linked-user-row");
|
|
204
|
+
expect(
|
|
205
|
+
findMessageByProviderMessageId("telegram", chatId, "tg-linked-1"),
|
|
206
|
+
).toBeNull();
|
|
207
|
+
|
|
208
|
+
// A crash-window row: persisted with its envelope but its event link
|
|
209
|
+
// never landed. The fallback is the only path that can resolve it.
|
|
210
|
+
insertMessage("orphan-user-row", minted.conversationId, {});
|
|
211
|
+
getDb()
|
|
212
|
+
.update(messages)
|
|
213
|
+
.set({ metadata: envelope("tg-orphan-1") })
|
|
214
|
+
.where(eq(messages.id, "orphan-user-row"))
|
|
215
|
+
.run();
|
|
216
|
+
expect(
|
|
217
|
+
findMessageByProviderMessageId("telegram", chatId, "tg-orphan-1"),
|
|
218
|
+
).toEqual({
|
|
219
|
+
messageId: "orphan-user-row",
|
|
220
|
+
conversationId: minted.conversationId,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// A legacy linked row whose event carries no source_message_id:
|
|
224
|
+
// findMessageBySourceId matches only that column, so the fallback must
|
|
225
|
+
// still admit the row or reactions to it become unresolvable.
|
|
226
|
+
const legacy = recordInbound("telegram", chatId, "evt-legacy");
|
|
227
|
+
insertMessage("legacy-user-row", legacy.conversationId, {});
|
|
228
|
+
getDb()
|
|
229
|
+
.update(messages)
|
|
230
|
+
.set({ metadata: envelope("tg-legacy-1") })
|
|
231
|
+
.where(eq(messages.id, "legacy-user-row"))
|
|
232
|
+
.run();
|
|
233
|
+
linkMessage(legacy.eventId, "legacy-user-row");
|
|
234
|
+
expect(
|
|
235
|
+
findMessageByProviderMessageId("telegram", chatId, "tg-legacy-1"),
|
|
236
|
+
).toEqual({
|
|
237
|
+
messageId: "legacy-user-row",
|
|
238
|
+
conversationId: legacy.conversationId,
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("the outbound-posts index resolves a post exactly, at any age", () => {
|
|
243
|
+
const chatId = "999000111222333444";
|
|
244
|
+
const minted = recordInbound("discord", chatId, "evt-index-seed");
|
|
245
|
+
const db = getDb();
|
|
246
|
+
// The row's envelope names nothing: resolution comes from the index
|
|
247
|
+
// alone, proving the table is the contract rather than a cache in
|
|
248
|
+
// front of the scan.
|
|
249
|
+
db.insert(messages)
|
|
250
|
+
.values({
|
|
251
|
+
id: "assistant-indexed-post",
|
|
252
|
+
conversationId: minted.conversationId,
|
|
253
|
+
role: "assistant",
|
|
254
|
+
content: "indexed reply",
|
|
255
|
+
createdAt: Date.now(),
|
|
256
|
+
metadata: null,
|
|
257
|
+
})
|
|
258
|
+
.run();
|
|
259
|
+
recordOutboundPost({
|
|
260
|
+
sourceChannel: "discord",
|
|
261
|
+
externalChatId: chatId,
|
|
262
|
+
providerMessageId: "555666777888999000",
|
|
263
|
+
messageId: "assistant-indexed-post",
|
|
264
|
+
conversationId: minted.conversationId,
|
|
265
|
+
});
|
|
266
|
+
// Idempotent on redelivery.
|
|
267
|
+
recordOutboundPost({
|
|
268
|
+
sourceChannel: "discord",
|
|
269
|
+
externalChatId: chatId,
|
|
270
|
+
providerMessageId: "555666777888999000",
|
|
271
|
+
messageId: "assistant-indexed-post",
|
|
272
|
+
conversationId: minted.conversationId,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
expect(
|
|
276
|
+
findMessageByProviderMessageId("discord", chatId, "555666777888999000"),
|
|
277
|
+
).toEqual({
|
|
278
|
+
messageId: "assistant-indexed-post",
|
|
279
|
+
conversationId: minted.conversationId,
|
|
280
|
+
});
|
|
281
|
+
// Channel and chat scoping hold on the index path too.
|
|
282
|
+
expect(
|
|
283
|
+
findMessageByProviderMessageId("telegram", chatId, "555666777888999000"),
|
|
284
|
+
).toBeNull();
|
|
174
285
|
expect(
|
|
175
|
-
|
|
286
|
+
findMessageByProviderMessageId(
|
|
176
287
|
"discord",
|
|
177
288
|
"other-chat",
|
|
178
|
-
"
|
|
289
|
+
"555666777888999000",
|
|
179
290
|
),
|
|
180
291
|
).toBeNull();
|
|
181
292
|
});
|