@vellumai/assistant 0.11.7 → 0.11.8-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 +10 -10
- package/Dockerfile +1 -0
- package/docs/architecture/security.md +1 -1
- package/docs/error-handling.md +1 -2
- package/docs/guardian-request-flow.md +51 -5
- package/docs/trusted-contact-access.md +13 -12
- package/knip.json +1 -0
- package/node_modules/@vellumai/avatar-catalog/package.json +19 -0
- package/{src/avatar/character-components.ts → node_modules/@vellumai/avatar-catalog/src/catalog.ts} +12 -44
- package/node_modules/@vellumai/avatar-catalog/src/colors.ts +19 -0
- package/node_modules/@vellumai/avatar-catalog/src/index.ts +25 -0
- package/node_modules/@vellumai/avatar-catalog/src/types.ts +47 -0
- package/node_modules/@vellumai/avatar-catalog/tsconfig.json +20 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -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/no-response.ts +84 -0
- package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +43 -12
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/__tests__/test-path-guard.test.ts +89 -0
- package/node_modules/@vellumai/environments/src/test-path-guard.ts +113 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -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/no-response.ts +84 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +8 -18
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +46 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +46 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +62 -21
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +14 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +53 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +11 -5
- package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +1 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/openapi.yaml +481 -9
- package/package.json +3 -1
- package/scripts/sync-llm-catalog.ts +3 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +16 -0
- package/src/__tests__/agent-loop-compacted-history-transform.test.ts +304 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +3 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +3 -0
- package/src/__tests__/anthropic-provider.test.ts +56 -0
- package/src/__tests__/approval-cascade.test.ts +0 -13
- package/src/__tests__/attachments-store.test.ts +117 -6
- package/src/__tests__/attachments.test.ts +122 -0
- package/src/__tests__/bundled-asset.test.ts +22 -1
- package/src/__tests__/channel-approval.test.ts +14 -7
- package/src/__tests__/channel-approvals.test.ts +3 -3
- package/src/__tests__/channel-delivery-store.test.ts +58 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +1 -0
- package/src/__tests__/channel-invite-transport.test.ts +1 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +3 -3
- package/src/__tests__/compaction-events.test.ts +0 -12
- package/src/__tests__/compactor-call-site-logging.test.ts +7 -1
- package/src/__tests__/compactor-retained-image-validation.test.ts +15 -0
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/contacts-write.test.ts +141 -3
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -13
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -12
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -13
- package/src/__tests__/conversation-agent-loop.test.ts +95 -13
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -13
- package/src/__tests__/conversation-load-history-repair.test.ts +418 -0
- package/src/__tests__/conversation-media-retry.test.ts +33 -2
- package/src/__tests__/conversation-pairing.test.ts +54 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -12
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -13
- package/src/__tests__/conversation-queue.test.ts +4 -13
- package/src/__tests__/conversation-retry-route.test.ts +5 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
- package/src/__tests__/conversation-sight-frames.test.ts +615 -0
- package/src/__tests__/conversation-slash-queue.test.ts +0 -13
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -13
- package/src/__tests__/conversation-speed-override.test.ts +0 -12
- package/src/__tests__/conversation-store-ephemeral.test.ts +0 -12
- package/src/__tests__/conversation-summarize-route.test.ts +5 -0
- package/src/__tests__/conversation-summarize-up-to.test.ts +102 -0
- package/src/__tests__/conversation-wait-for-idle.test.ts +0 -12
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -17
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -17
- package/src/__tests__/credential-health-service.test.ts +45 -0
- package/src/__tests__/credential-record-retire.test.ts +256 -0
- package/src/__tests__/credential-record-write-through.test.ts +8 -4
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +0 -4
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +0 -4
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +0 -8
- package/src/__tests__/delete-propagation.test.ts +121 -3
- package/src/__tests__/evict-conversations-for-reload.test.ts +46 -6
- package/src/__tests__/file-ops-service.test.ts +8 -3
- package/src/__tests__/gemini-provider.test.ts +64 -0
- package/src/__tests__/guardian-action-service-scope.test.ts +118 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +299 -5
- package/src/__tests__/guardian-expiry-notifier.test.ts +117 -13
- package/src/__tests__/guardian-gateway-sim.test.ts +119 -0
- package/src/__tests__/guardian-gateway-sim.ts +56 -20
- package/src/__tests__/guardian-reply-router-answer-mode.test.ts +6 -0
- package/src/__tests__/guardian-request-status-sync.test.ts +162 -0
- package/src/__tests__/guardian-wait-controller.test.ts +6 -0
- package/src/__tests__/headless-browser-mode.test.ts +27 -0
- package/src/__tests__/helpers/mock-conversation.ts +2 -0
- package/src/__tests__/list-messages-system-card.test.ts +70 -0
- package/src/__tests__/llm-catalog-parity.test.ts +2 -0
- package/src/__tests__/managed-profile-guard.test.ts +2 -2
- package/src/__tests__/media-resolve-image-validation.test.ts +106 -0
- package/src/__tests__/media-stream-server-integration.test.ts +6 -0
- package/src/__tests__/non-member-access-request.test.ts +12 -0
- package/src/__tests__/notification-discord-adapter.test.ts +76 -4
- package/src/__tests__/oauth-commands-routes.test.ts +21 -0
- package/src/__tests__/openai-provider.test.ts +63 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +114 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +63 -1
- package/src/__tests__/persist-unsendable-image.test.ts +19 -0
- package/src/__tests__/plugin-app-serve-routes.test.ts +2 -2
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
- package/src/__tests__/process-message-background-slack.test.ts +1 -1
- package/src/__tests__/process-message-display-content.test.ts +1 -1
- package/src/__tests__/processing-flag-persist-failure.test.ts +0 -5
- package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +0 -17
- package/src/__tests__/qdrant-manager.test.ts +6 -0
- package/src/__tests__/reaction-persistence.test.ts +58 -62
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +67 -0
- package/src/__tests__/server-history-render.test.ts +22 -0
- package/src/__tests__/sight-frame-retention.test.ts +362 -0
- package/src/__tests__/slack-reaction-approvals.test.ts +44 -24
- package/src/__tests__/slack-reaction-guardian-approval.test.ts +29 -1
- package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
- package/src/__tests__/subagent-manager-notify.test.ts +2 -0
- package/src/__tests__/subagent-notify-parent.test.ts +2 -0
- package/src/__tests__/subagent-notify-stale-rebuild.test.ts +85 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +4 -4
- package/src/__tests__/terminal-tools.test.ts +9 -9
- package/src/__tests__/tool-result-spool.test.ts +5 -8
- package/src/__tests__/usage-attribution.test.ts +26 -0
- package/src/acp/__tests__/acp-auth-marker-store.test.ts +191 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +262 -0
- package/src/acp/__tests__/acp-connect-card-state.test.ts +17 -0
- package/src/acp/__tests__/helpers/acp-history-db.ts +10 -3
- package/src/acp/acp-auth-marker-store.ts +196 -0
- package/src/acp/acp-claude-oauth.ts +154 -4
- package/src/acp/acp-connect-card-state.ts +49 -8
- package/src/acp/agent-process.ts +1 -0
- package/src/acp/auto-install.ts +1 -0
- package/src/acp/client-handler.ts +1 -0
- package/src/acp/prepare-agent-env.ts +79 -1
- package/src/acp/resolve-agent.ts +12 -0
- package/src/acp/session-manager.test.ts +183 -0
- package/src/acp/session-manager.ts +110 -6
- package/src/acp/types.ts +17 -0
- package/src/agent/attachments.ts +7 -2
- package/src/agent/loop.ts +59 -1
- package/src/api/events/contact-form-closed.ts +32 -0
- package/src/api/events/contact-record-request.ts +63 -0
- package/src/api/events/contact-request.ts +10 -3
- package/src/api/index.ts +20 -0
- package/src/api/responses/conversation-message.ts +20 -0
- package/src/api/responses/home.ts +101 -0
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-card-withdrawal.ts +127 -30
- package/src/approvals/guardian-decision-primitive.ts +9 -0
- package/src/approvals/guardian-expiry-notifier.ts +22 -16
- package/src/approvals/guardian-request-status-sync.ts +98 -0
- package/src/avatar/svg-compositor.ts +1 -1
- package/src/avatar/traits-png-sync.ts +1 -1
- package/src/bundler/app-compiler.ts +1 -0
- package/src/bundler/compiler-tools.ts +1 -0
- package/src/bundler/package-resolver.ts +1 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +183 -3
- package/src/calls/guardian-action-sweep.ts +6 -1
- package/src/calls/voice-session-bridge.ts +46 -1
- package/src/channels/__tests__/gateway-guardian-requests.test.ts +13 -18
- package/src/channels/gateway-guardian-requests.ts +37 -18
- package/src/cli/__tests__/contacts-record-prompt-cli.test.ts +329 -0
- package/src/cli/commands/__tests__/browser.test.ts +65 -1
- package/src/cli/commands/browser.ts +30 -10
- package/src/cli/commands/contacts.help.ts +173 -11
- package/src/cli/commands/contacts.ts +343 -4
- package/src/cli/commands/db/refresh.ts +1 -0
- package/src/cli/commands/oauth/index.help.ts +6 -3
- package/src/cli/commands/oauth/request.test.ts +37 -0
- package/src/cli/commands/oauth/request.ts +7 -6
- package/src/cli/lib/bundled-marketplace.json +1 -1
- package/src/cli/lib/install-from-github.ts +20 -19
- package/src/cli/lib/install-plugin-dependencies.ts +3 -5
- package/src/cli/lib/merge-plugin-tree.ts +1 -0
- package/src/cli/lib/publish-plugin.ts +4 -1
- package/src/config/__tests__/balanced-model-experiment.test.ts +63 -31
- package/src/config/__tests__/default-profile-catalog.test.ts +2 -2
- package/src/config/balanced-model-experiment.ts +15 -9
- package/src/config/bundled-skills/contacts/SKILL.md +61 -18
- package/src/config/default-profile-catalog.ts +12 -11
- package/src/config/feature-flag-registry.json +22 -13
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/live-voice.test.ts +13 -0
- package/src/config/schemas/live-voice.ts +7 -0
- package/src/config/schemas/sight.ts +45 -0
- package/src/config/skills.ts +2 -1
- package/src/contacts/__tests__/mirror-reconciler.test.ts +352 -0
- package/src/contacts/contact-store.ts +47 -4
- package/src/contacts/contacts-write.ts +5 -2
- package/src/contacts/mirror-reconciler.ts +210 -0
- package/src/contacts/mirror-tombstones.ts +42 -0
- package/src/contacts/notify-contacts-changed.ts +11 -2
- package/src/context/compactor.ts +47 -19
- package/src/context/tool-result-spool.ts +21 -14
- package/src/conversations/message-consolidation.ts +2 -2
- package/src/credential-health/credential-health-service.ts +37 -6
- package/src/daemon/conversation-agent-loop-handlers.ts +19 -2
- package/src/daemon/conversation-agent-loop.ts +100 -1
- package/src/daemon/conversation-media-retry.ts +11 -6
- package/src/daemon/conversation-messaging.ts +293 -30
- package/src/daemon/conversation-runtime-assembly.ts +67 -0
- package/src/daemon/conversation-sight-frames.ts +187 -0
- package/src/daemon/conversation-store.ts +18 -28
- package/src/daemon/conversation-surfaces.ts +12 -2
- package/src/daemon/conversation-tool-setup.ts +39 -10
- package/src/daemon/conversation.ts +159 -16
- package/src/daemon/daemon-control.ts +6 -17
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +10 -11
- package/src/daemon/install-assistant-command.ts +1 -0
- package/src/daemon/lifecycle.ts +10 -13
- package/src/daemon/message-types/sync.ts +4 -0
- package/src/daemon/process-message.ts +2 -17
- package/src/daemon/reaction-history-render.test.ts +162 -0
- package/src/daemon/reaction-history-render.ts +115 -0
- package/src/daemon/reaction-record.test.ts +162 -0
- package/src/daemon/reaction-record.ts +83 -0
- package/src/daemon/shutdown-handlers.ts +0 -2
- package/src/daemon/summarize-boundary.ts +12 -0
- package/src/daemon/video-thumbnail.ts +1 -1
- package/src/home/feed-types.ts +4 -0
- package/src/home/feed-writer.ts +25 -0
- package/src/live-voice/__tests__/live-voice-attach-frame.test.ts +679 -0
- package/src/live-voice/__tests__/live-voice-attach-image.test.ts +8 -5
- package/src/live-voice/__tests__/live-voice-photo.test.ts +637 -1
- package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +787 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +347 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +109 -0
- package/src/live-voice/live-voice-photo.ts +420 -65
- package/src/live-voice/live-voice-session.ts +311 -27
- package/src/live-voice/protocol.ts +147 -11
- package/src/messaging/provider-message-metadata.ts +7 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +113 -1
- package/src/messaging/providers/channel-transport.ts +37 -0
- package/src/messaging/providers/discord/api.ts +4 -4
- package/src/messaging/providers/discord/send.test.ts +177 -2
- package/src/messaging/providers/discord/send.ts +197 -10
- package/src/messaging/providers/discord/transport.ts +10 -0
- package/src/messaging/providers/index.ts +42 -0
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +129 -3
- package/src/messaging/providers/slack/__tests__/auth.test.ts +35 -0
- package/src/messaging/providers/slack/__tests__/message-content.test.ts +152 -0
- package/src/messaging/providers/slack/adapter.ts +112 -27
- package/src/messaging/providers/slack/api.test.ts +10 -0
- package/src/messaging/providers/slack/auth.ts +32 -6
- package/src/messaging/providers/slack/client.ts +0 -15
- package/src/messaging/providers/slack/message-content.ts +137 -0
- package/src/messaging/providers/slack/send.test.ts +43 -2
- package/src/messaging/providers/slack/send.ts +18 -5
- package/src/messaging/providers/slack/transport.ts +10 -0
- package/src/messaging/providers/slack/types.ts +21 -2
- package/src/messaging/providers/telegram-bot/adapter.ts +5 -1
- package/src/messaging/providers/telegram-bot/send.test.ts +49 -2
- package/src/messaging/providers/telegram-bot/send.ts +41 -1
- package/src/messaging/providers/telegram-bot/transport.ts +10 -0
- package/src/messaging/providers/telegram-bot/withdraw.ts +13 -5
- package/src/messaging/reaction-envelopes.ts +79 -0
- package/src/monitoring/control.ts +2 -2
- package/src/monitoring/page-cache.ts +5 -1
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +360 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +8 -0
- package/src/notifications/adapters/discord.ts +62 -9
- package/src/notifications/conversation-pairing.ts +31 -4
- package/src/notifications/emit-signal.ts +8 -5
- package/src/notifications/guardian-delivery-recorder.ts +47 -6
- package/src/notifications/guardian-feed-projection.ts +492 -0
- package/src/notifications/home-feed-side-effect.ts +59 -11
- package/src/oauth/byo-connection.test.ts +22 -0
- package/src/oauth/byo-connection.ts +11 -5
- package/src/oauth/connection.ts +3 -2
- package/src/oauth/platform-connection.test.ts +35 -0
- package/src/oauth/platform-connection.ts +18 -13
- package/src/oauth/scope-utils.ts +48 -0
- package/src/oauth/seed-providers.ts +12 -0
- package/src/outbound-proxy/certs.ts +5 -4
- package/src/permissions/question-guardian-request.test.ts +7 -1
- package/src/persistence/attachments-store.ts +134 -31
- package/src/persistence/conversation-crud.ts +137 -4
- package/src/persistence/conversation-types.ts +109 -0
- package/src/persistence/db-connection.ts +3 -21
- package/src/persistence/delivery-crud.ts +42 -12
- package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +15 -2
- package/src/persistence/embeddings/embedding-local.ts +7 -112
- package/src/persistence/embeddings/qdrant-manager.ts +4 -1
- package/src/persistence/llm-request-log-store.ts +1 -1
- package/src/persistence/migrations/370-acp-session-history-auth-error-code.ts +36 -0
- package/src/persistence/migrations/371-acp-session-history-auth-error-credential.ts +40 -0
- package/src/persistence/migrations/372-create-acp-refused-credentials.ts +34 -0
- package/src/persistence/migrations/373-acp-auth-marker-index.ts +27 -0
- package/src/persistence/migrations/__tests__/370-acp-session-history-auth-error-code.test.ts +65 -0
- package/src/persistence/migrations/__tests__/371-acp-session-history-auth-error-credential.test.ts +68 -0
- package/src/persistence/migrations/__tests__/372-create-acp-refused-credentials.test.ts +64 -0
- package/src/persistence/migrations/__tests__/373-acp-auth-marker-index.test.ts +82 -0
- package/src/persistence/schema/acp.ts +25 -0
- package/src/persistence/steps.ts +8 -0
- package/src/platform/platform-patch-queue.test.ts +43 -41
- package/src/plugin-api/index.ts +12 -0
- package/src/plugins/defaults/compaction/window-manager.ts +2 -0
- package/src/plugins/defaults/image-recovery/recover.ts +12 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +166 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +3 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +3 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +1 -2
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +53 -9
- package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +4 -7
- package/src/plugins/defaults/memory/src/memory-worker-routes.ts +1 -1
- package/src/plugins/defaults/memory/v1/job-handlers/index-maintenance.ts +15 -1
- package/src/plugins/defaults/memory/worker-control.ts +1 -2
- package/src/providers/content-block-schema.ts +1 -0
- package/src/providers/fireworks/client.ts +9 -1
- package/src/providers/gemini/client.ts +22 -15
- package/src/providers/media-resolve.ts +24 -10
- package/src/providers/model-catalog.ts +91 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -5
- package/src/providers/retry.ts +16 -11
- package/src/providers/types.ts +56 -0
- package/src/routes/__tests__/control.test.ts +4 -3
- package/src/routes/control.ts +1 -1
- package/src/routes/route-host-client.ts +1 -1
- package/src/runtime/AGENTS.md +30 -12
- package/src/runtime/__tests__/agent-wake.test.ts +71 -6
- package/src/runtime/__tests__/guardian-form-registry.test.ts +242 -0
- package/src/runtime/access-request-helper.ts +15 -7
- package/src/runtime/agent-wake.ts +16 -4
- package/src/runtime/channel-approval-types.ts +8 -5
- package/src/runtime/channel-invite-transport.ts +2 -0
- package/src/runtime/channel-invite-transports/discord.ts +27 -0
- package/src/runtime/channel-readiness-service.ts +4 -4
- package/src/runtime/channel-reply-delivery.ts +72 -28
- package/src/runtime/guardian-action-service.ts +9 -5
- package/src/runtime/guardian-form-registry.ts +256 -0
- package/src/runtime/guardian-reply-router.ts +33 -14
- package/src/runtime/invite-instruction-generator.ts +14 -2
- package/src/runtime/migrations/vbundle-metadata-merge.ts +4 -7
- package/src/runtime/no-response.ts +13 -74
- package/src/runtime/routes/__tests__/acp-routes.test.ts +527 -0
- package/src/runtime/routes/__tests__/app-management-shared-apps.test.ts +74 -0
- package/src/runtime/routes/__tests__/contact-form-kinds.test.ts +22 -0
- package/src/runtime/routes/__tests__/contact-record-prompt-route.test.ts +303 -0
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +3 -4
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +6 -0
- package/src/runtime/routes/__tests__/route-host-worker-routes.test.ts +3 -4
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +5 -6
- package/src/runtime/routes/acp-routes-list.test.ts +58 -0
- package/src/runtime/routes/acp-routes.ts +253 -47
- package/src/runtime/routes/app-management-routes.ts +80 -47
- package/src/runtime/routes/approval-routes.ts +9 -0
- package/src/runtime/routes/archive-utils.ts +6 -1
- package/src/runtime/routes/avatar-routes.ts +1 -1
- package/src/runtime/routes/channel-route-shared.ts +18 -20
- package/src/runtime/routes/contact-prompt-routes.ts +271 -105
- package/src/runtime/routes/conversation-routes.ts +41 -32
- package/src/runtime/routes/credential-prompt-routes.ts +10 -1
- package/src/runtime/routes/debug-bash-routes.ts +1 -0
- package/src/runtime/routes/guardian-expiry-sweep.ts +89 -31
- package/src/runtime/routes/guardian-form-routes.ts +115 -0
- package/src/runtime/routes/inbound-message-handler.ts +82 -29
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +6 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +6 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +6 -1
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +50 -17
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +97 -94
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/channel.ts +39 -18
- package/src/runtime/routes/migration-routes.ts +1 -3
- package/src/runtime/routes/monitoring-routes.ts +0 -3
- package/src/runtime/routes/oauth-commands-routes.ts +18 -5
- package/src/runtime/routes/route-host-worker-routes.ts +1 -1
- package/src/runtime/routes/schedule-worker-routes.ts +1 -1
- package/src/runtime/tool-grant-request-helper.ts +4 -0
- package/src/schedule/run-script.ts +1 -0
- package/src/schedule/scheduler.ts +1 -1
- package/src/schedule/worker-control.ts +2 -2
- package/src/security/oauth2.ts +2 -11
- package/src/security/secure-keys.ts +55 -8
- package/src/skills/catalog-install.ts +7 -4
- package/src/skills/clawhub.ts +1 -0
- package/src/skills/inline-command-runner.ts +1 -0
- package/src/stt/__tests__/room-noise-floor.test.ts +108 -0
- package/src/stt/room-noise-floor.ts +140 -0
- package/src/subagent/notify.ts +49 -3
- package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
- package/src/tools/acp/spawn.ts +2 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +9 -0
- package/src/tools/browser/browser-execution.ts +10 -43
- package/src/tools/browser/browser-manager.ts +1 -0
- package/src/tools/browser/browser-status-constants.ts +6 -5
- package/src/tools/browser/runtime-check.ts +2 -0
- package/src/tools/channel/react-to-message.test.ts +193 -0
- package/src/tools/channel/react-to-message.ts +159 -0
- package/src/tools/credentials/metadata-store.ts +115 -19
- package/src/tools/credentials/store.ts +0 -2
- package/src/tools/filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +24 -0
- package/src/tools/host-terminal/host-shell.ts +11 -38
- package/src/tools/shared/filesystem/file-ops-service.ts +10 -7
- package/src/tools/shared/shell-output.test.ts +104 -0
- package/src/tools/shared/shell-output.ts +112 -39
- package/src/tools/skills/sandbox-runner.ts +1 -0
- package/src/tools/system/request-permission.test.ts +34 -0
- package/src/tools/system/request-permission.ts +29 -5
- package/src/tools/terminal/shell.test.ts +63 -0
- package/src/tools/terminal/shell.ts +12 -44
- package/src/tools/tool-input-schemas.ts +2 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/usage/attribution.ts +12 -4
- package/src/util/__tests__/worker-ownership.test.ts +77 -0
- package/src/util/__tests__/worker-process-command.test.ts +246 -1
- package/src/util/bun-runtime.ts +44 -13
- package/src/util/bundled-asset.ts +9 -1
- package/src/util/disk-usage.ts +1 -0
- package/src/util/file-use.ts +4 -1
- package/src/util/guardian-form-timeouts.ts +50 -0
- package/src/util/image-conversion.ts +1 -0
- package/src/util/oauth-request-body.test.ts +59 -0
- package/src/util/oauth-request-body.ts +56 -5
- package/src/util/platform.ts +69 -73
- package/src/util/process-table.ts +16 -5
- package/src/util/read-stdin.ts +5 -0
- package/src/util/spawn.ts +1 -0
- package/src/util/sqlite3-runtime.ts +25 -5
- package/src/util/worker-ownership.ts +193 -0
- package/src/util/worker-process.ts +375 -37
- package/src/workspace/git-service.ts +4 -12
- package/src/workspace/migrations/010-app-dir-rename.ts +1 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agent loop's post-compaction history transform.
|
|
3
|
+
*
|
|
4
|
+
* In-place compaction rebuilds history from the stored rows, so it can
|
|
5
|
+
* reintroduce content the caller had already trimmed out of the array it handed
|
|
6
|
+
* to `run()`. The rebuilt array is installed and sent on the very next request
|
|
7
|
+
* of the same turn, so the caller's trim has to be re-applied at the
|
|
8
|
+
* installation site rather than only before the run. The loop stays
|
|
9
|
+
* conversation-ignorant: the caller injects a closure, and this suite drives
|
|
10
|
+
* that seam with the real camera-frame retention transform.
|
|
11
|
+
*/
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
import type { PostCompactContext } from "@vellumai/plugin-api";
|
|
15
|
+
|
|
16
|
+
import { AgentLoop } from "../agent/loop.js";
|
|
17
|
+
import type { ContextWindowConfig } from "../config/types.js";
|
|
18
|
+
import { stripAgedSightFrames } from "../daemon/conversation-sight-frames.js";
|
|
19
|
+
import type { TrustContext } from "../daemon/trust-context-types.js";
|
|
20
|
+
import { HOOKS } from "../plugin-api/constants.js";
|
|
21
|
+
import {
|
|
22
|
+
createContextWindowManager,
|
|
23
|
+
disposeContextWindowManager,
|
|
24
|
+
getContextWindowManager,
|
|
25
|
+
} from "../plugins/defaults/compaction/manager-store.js";
|
|
26
|
+
import {
|
|
27
|
+
registerPlugin,
|
|
28
|
+
resetPluginRegistryForTests,
|
|
29
|
+
} from "../plugins/registry.js";
|
|
30
|
+
import type {
|
|
31
|
+
ContentBlock,
|
|
32
|
+
Message,
|
|
33
|
+
Provider,
|
|
34
|
+
ProviderResponse,
|
|
35
|
+
} from "../providers/types.js";
|
|
36
|
+
import { ContextOverflowError } from "../providers/types.js";
|
|
37
|
+
|
|
38
|
+
const testPostCompactPlugin = {
|
|
39
|
+
manifest: { name: "test-post-compact-transform", version: "0.0.0" },
|
|
40
|
+
hooks: {
|
|
41
|
+
[HOOKS.POST_COMPACT]: async (input: PostCompactContext): Promise<void> => {
|
|
42
|
+
void input;
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const CONVERSATION_ID = "compacted-history-transform-conversation";
|
|
48
|
+
|
|
49
|
+
/** Capture times one second apart, the shape the row read produces. */
|
|
50
|
+
const CAPTURE_TIMES = new Map<string, number>([
|
|
51
|
+
["f1", 1000],
|
|
52
|
+
["f2", 2000],
|
|
53
|
+
["f3", 3000],
|
|
54
|
+
["f4", 4000],
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
/** A retained frame as `buildRetainedImageBlocks` rebuilds it: inline + id. */
|
|
58
|
+
function retainedFrame(
|
|
59
|
+
attachmentId: string,
|
|
60
|
+
): Extract<ContentBlock, { type: "image" }> {
|
|
61
|
+
return {
|
|
62
|
+
type: "image",
|
|
63
|
+
source: { type: "base64", media_type: "image/png", data: "AAAAAAAA" },
|
|
64
|
+
_attachmentId: attachmentId,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The history compaction rebuilds here: four tagged frames in one synthetic
|
|
70
|
+
* message, which is two more than the retention bound allows.
|
|
71
|
+
*/
|
|
72
|
+
function compactedHistoryWithFourFrames(): Message[] {
|
|
73
|
+
return [
|
|
74
|
+
{
|
|
75
|
+
role: "user",
|
|
76
|
+
content: [
|
|
77
|
+
{ type: "text", text: "Images retained from the compacted portion:" },
|
|
78
|
+
retainedFrame("f1"),
|
|
79
|
+
retainedFrame("f2"),
|
|
80
|
+
retainedFrame("f3"),
|
|
81
|
+
retainedFrame("f4"),
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function textResponse(text: string): ProviderResponse {
|
|
88
|
+
return {
|
|
89
|
+
content: [{ type: "text", text }],
|
|
90
|
+
model: "mock-model",
|
|
91
|
+
usage: { inputTokens: 10, outputTokens: 5 },
|
|
92
|
+
stopReason: "end_turn",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Records every history the provider is asked to send. */
|
|
97
|
+
function createRecordingProvider(requests: Message[][]): Provider {
|
|
98
|
+
return {
|
|
99
|
+
name: "mock",
|
|
100
|
+
async sendMessage(messages: Message[]): Promise<ProviderResponse> {
|
|
101
|
+
requests.push(messages);
|
|
102
|
+
return textResponse("done");
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** A provider that rejects once as context-too-large, then answers. */
|
|
108
|
+
function createOverflowingProvider(requests: Message[][]): Provider {
|
|
109
|
+
let throwOnce = true;
|
|
110
|
+
return {
|
|
111
|
+
name: "mock",
|
|
112
|
+
async sendMessage(messages: Message[]): Promise<ProviderResponse> {
|
|
113
|
+
requests.push(messages);
|
|
114
|
+
if (throwOnce) {
|
|
115
|
+
throwOnce = false;
|
|
116
|
+
throw new ContextOverflowError("prompt too long", "mock", {
|
|
117
|
+
actualTokens: 999_999,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return textResponse("done after overflow recovery");
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Install a manager whose compaction returns the four-frame history, so the
|
|
127
|
+
* loop installs more frames than the bound allows.
|
|
128
|
+
*/
|
|
129
|
+
function installOverfullCompactionManager(): { trust: TrustContext } {
|
|
130
|
+
createContextWindowManager({
|
|
131
|
+
provider: { name: "mock-provider" } as unknown as Provider,
|
|
132
|
+
config: {} as unknown as ContextWindowConfig,
|
|
133
|
+
conversationId: CONVERSATION_ID,
|
|
134
|
+
});
|
|
135
|
+
const manager = getContextWindowManager(CONVERSATION_ID);
|
|
136
|
+
if (manager) {
|
|
137
|
+
const rebuild = async () => ({
|
|
138
|
+
messages: compactedHistoryWithFourFrames(),
|
|
139
|
+
compacted: true,
|
|
140
|
+
exhausted: false,
|
|
141
|
+
});
|
|
142
|
+
manager.maybeCompact = rebuild as unknown as typeof manager.maybeCompact;
|
|
143
|
+
manager.recoverContextOverflow =
|
|
144
|
+
rebuild as unknown as typeof manager.recoverContextOverflow;
|
|
145
|
+
}
|
|
146
|
+
return { trust: { sourceChannel: "vellum", trustClass: "unknown" } };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Image blocks in the last history the provider was asked to send. */
|
|
150
|
+
function imagesInLastRequest(requests: Message[][]): ContentBlock[] {
|
|
151
|
+
const last = requests.at(-1) ?? [];
|
|
152
|
+
return last.flatMap((message) =>
|
|
153
|
+
message.content.filter((block) => block.type === "image"),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function frameStubsInLastRequest(requests: Message[][]): string[] {
|
|
158
|
+
const last = requests.at(-1) ?? [];
|
|
159
|
+
return last.flatMap((message) =>
|
|
160
|
+
message.content
|
|
161
|
+
.filter((block) => block.type === "text")
|
|
162
|
+
.map((block) => block.text)
|
|
163
|
+
.filter((text) => text.startsWith("[Camera frame omitted from context:")),
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
describe("AgentLoop post-compaction history transform", () => {
|
|
168
|
+
beforeEach(() => {
|
|
169
|
+
resetPluginRegistryForTests();
|
|
170
|
+
registerPlugin(testPostCompactPlugin);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
afterEach(() => {
|
|
174
|
+
disposeContextWindowManager(CONVERSATION_ID);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("trims a budget-gate compaction before the next provider call", async () => {
|
|
178
|
+
const requests: Message[][] = [];
|
|
179
|
+
const loop = new AgentLoop({
|
|
180
|
+
provider: createRecordingProvider(requests),
|
|
181
|
+
systemPrompt: "system",
|
|
182
|
+
conversationId: CONVERSATION_ID,
|
|
183
|
+
tools: [],
|
|
184
|
+
toolExecutor: async () => ({ content: "ok", isError: false }),
|
|
185
|
+
transformCompactedHistory: (messages) =>
|
|
186
|
+
stripAgedSightFrames(messages, CAPTURE_TIMES).messages,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
await loop.run({
|
|
190
|
+
requestId: "req",
|
|
191
|
+
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
192
|
+
onEvent: () => {},
|
|
193
|
+
modelProfileKey: "balanced",
|
|
194
|
+
resolveContextWindow: () => ({
|
|
195
|
+
maxInputTokens: 10,
|
|
196
|
+
overflowRecovery: { enabled: true, safetyMarginRatio: 0 },
|
|
197
|
+
}),
|
|
198
|
+
compactInPlace: true,
|
|
199
|
+
...installOverfullCompactionManager(),
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// The request that went out after compaction carries the bound, not the
|
|
203
|
+
// four frames compaction rebuilt.
|
|
204
|
+
expect(imagesInLastRequest(requests)).toHaveLength(2);
|
|
205
|
+
expect(frameStubsInLastRequest(requests)).toHaveLength(2);
|
|
206
|
+
// The newest two survived.
|
|
207
|
+
expect(imagesInLastRequest(requests)).toEqual([
|
|
208
|
+
retainedFrame("f3"),
|
|
209
|
+
retainedFrame("f4"),
|
|
210
|
+
]);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("trims an overflow-driven compaction before the retry", async () => {
|
|
214
|
+
const requests: Message[][] = [];
|
|
215
|
+
const loop = new AgentLoop({
|
|
216
|
+
provider: createOverflowingProvider(requests),
|
|
217
|
+
systemPrompt: "system",
|
|
218
|
+
conversationId: CONVERSATION_ID,
|
|
219
|
+
tools: [],
|
|
220
|
+
toolExecutor: async () => ({ content: "ok", isError: false }),
|
|
221
|
+
transformCompactedHistory: (messages) =>
|
|
222
|
+
stripAgedSightFrames(messages, CAPTURE_TIMES).messages,
|
|
223
|
+
});
|
|
224
|
+
// Blocks the budget gate so only the overflow rejection drives compaction.
|
|
225
|
+
loop.compactionCircuit.lastPostCompactionEstimate = 0;
|
|
226
|
+
|
|
227
|
+
await loop.run({
|
|
228
|
+
requestId: "req",
|
|
229
|
+
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
230
|
+
onEvent: () => {},
|
|
231
|
+
modelProfileKey: "balanced",
|
|
232
|
+
resolveContextWindow: () => ({
|
|
233
|
+
maxInputTokens: 10,
|
|
234
|
+
overflowRecovery: { enabled: true, safetyMarginRatio: 0 },
|
|
235
|
+
}),
|
|
236
|
+
compactInPlace: false,
|
|
237
|
+
...installOverfullCompactionManager(),
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// The overflow retry is a second provider call, and it too is bounded.
|
|
241
|
+
expect(requests.length).toBeGreaterThan(1);
|
|
242
|
+
expect(imagesInLastRequest(requests)).toHaveLength(2);
|
|
243
|
+
expect(frameStubsInLastRequest(requests)).toHaveLength(2);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("installs the compacted history as built when no transform is configured", async () => {
|
|
247
|
+
// The workflow leaf runner builds a loop with no conversation behind it.
|
|
248
|
+
const requests: Message[][] = [];
|
|
249
|
+
const loop = new AgentLoop({
|
|
250
|
+
provider: createRecordingProvider(requests),
|
|
251
|
+
systemPrompt: "system",
|
|
252
|
+
conversationId: CONVERSATION_ID,
|
|
253
|
+
tools: [],
|
|
254
|
+
toolExecutor: async () => ({ content: "ok", isError: false }),
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
await loop.run({
|
|
258
|
+
requestId: "req",
|
|
259
|
+
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
260
|
+
onEvent: () => {},
|
|
261
|
+
modelProfileKey: "balanced",
|
|
262
|
+
resolveContextWindow: () => ({
|
|
263
|
+
maxInputTokens: 10,
|
|
264
|
+
overflowRecovery: { enabled: true, safetyMarginRatio: 0 },
|
|
265
|
+
}),
|
|
266
|
+
compactInPlace: true,
|
|
267
|
+
...installOverfullCompactionManager(),
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
expect(imagesInLastRequest(requests)).toHaveLength(4);
|
|
271
|
+
expect(frameStubsInLastRequest(requests)).toHaveLength(0);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("keeps the compacted history when the transform throws", async () => {
|
|
275
|
+
const requests: Message[][] = [];
|
|
276
|
+
const loop = new AgentLoop({
|
|
277
|
+
provider: createRecordingProvider(requests),
|
|
278
|
+
systemPrompt: "system",
|
|
279
|
+
conversationId: CONVERSATION_ID,
|
|
280
|
+
tools: [],
|
|
281
|
+
toolExecutor: async () => ({ content: "ok", isError: false }),
|
|
282
|
+
transformCompactedHistory: () => {
|
|
283
|
+
throw new Error("row read failed");
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await loop.run({
|
|
288
|
+
requestId: "req",
|
|
289
|
+
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
290
|
+
onEvent: () => {},
|
|
291
|
+
modelProfileKey: "balanced",
|
|
292
|
+
resolveContextWindow: () => ({
|
|
293
|
+
maxInputTokens: 10,
|
|
294
|
+
overflowRecovery: { enabled: true, safetyMarginRatio: 0 },
|
|
295
|
+
}),
|
|
296
|
+
compactInPlace: true,
|
|
297
|
+
...installOverfullCompactionManager(),
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// The turn still completed against the untrimmed compacted history.
|
|
301
|
+
expect(requests.length).toBeGreaterThan(0);
|
|
302
|
+
expect(imagesInLastRequest(requests)).toHaveLength(4);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
@@ -78,6 +78,9 @@ function makeTarget(): Conversation {
|
|
|
78
78
|
}),
|
|
79
79
|
},
|
|
80
80
|
messages,
|
|
81
|
+
// The wake trims its own run input; no row here is tagged as a camera
|
|
82
|
+
// frame, so the real pass would return the array unchanged too.
|
|
83
|
+
trimAgedSightFrames: (msgs: Message[]) => msgs,
|
|
81
84
|
getMessages: () => messages,
|
|
82
85
|
isProcessing: () => processing,
|
|
83
86
|
waitForIdle: async () => !processing,
|
|
@@ -90,6 +90,9 @@ function makeTarget(): {
|
|
|
90
90
|
},
|
|
91
91
|
},
|
|
92
92
|
messages,
|
|
93
|
+
// The wake trims its own run input; no row here is tagged as a camera
|
|
94
|
+
// frame, so the real pass would return the array unchanged too.
|
|
95
|
+
trimAgedSightFrames: (msgs: Message[]) => msgs,
|
|
93
96
|
getMessages: () => messages,
|
|
94
97
|
isProcessing: () => processing,
|
|
95
98
|
waitForIdle: async () => !processing,
|
|
@@ -2973,6 +2973,62 @@ describe("AnthropicProvider — Managed Proxy Fallback", () => {
|
|
|
2973
2973
|
});
|
|
2974
2974
|
});
|
|
2975
2975
|
|
|
2976
|
+
// ---------------------------------------------------------------------------
|
|
2977
|
+
// Tests - internal `_attachmentId` never reaches the wire
|
|
2978
|
+
// ---------------------------------------------------------------------------
|
|
2979
|
+
|
|
2980
|
+
describe("AnthropicProvider - internal attachment id", () => {
|
|
2981
|
+
let provider: AnthropicProvider;
|
|
2982
|
+
|
|
2983
|
+
beforeEach(() => {
|
|
2984
|
+
lastStreamParams = null;
|
|
2985
|
+
provider = new AnthropicProvider("sk-ant-test", "claude-sonnet-4-6");
|
|
2986
|
+
});
|
|
2987
|
+
|
|
2988
|
+
test("drops _attachmentId from image and file blocks", async () => {
|
|
2989
|
+
// The declared media type matches the bytes, so `resolveMediaReferences`
|
|
2990
|
+
// takes its identity fast path and hands the block through untouched. What
|
|
2991
|
+
// keeps the field off the wire is the client rebuilding each payload out of
|
|
2992
|
+
// `source` alone.
|
|
2993
|
+
const messages: Message[] = [
|
|
2994
|
+
{
|
|
2995
|
+
role: "user",
|
|
2996
|
+
content: [
|
|
2997
|
+
{ type: "text", text: "what is this" },
|
|
2998
|
+
{
|
|
2999
|
+
type: "image",
|
|
3000
|
+
source: {
|
|
3001
|
+
type: "base64",
|
|
3002
|
+
media_type: "image/png",
|
|
3003
|
+
data: PNG_B64,
|
|
3004
|
+
},
|
|
3005
|
+
_attachmentId: "att-image-1",
|
|
3006
|
+
},
|
|
3007
|
+
{
|
|
3008
|
+
type: "file",
|
|
3009
|
+
source: {
|
|
3010
|
+
type: "base64",
|
|
3011
|
+
media_type: "application/pdf",
|
|
3012
|
+
data: "cGRm",
|
|
3013
|
+
filename: "notes.pdf",
|
|
3014
|
+
},
|
|
3015
|
+
_attachmentId: "att-file-1",
|
|
3016
|
+
},
|
|
3017
|
+
],
|
|
3018
|
+
},
|
|
3019
|
+
];
|
|
3020
|
+
|
|
3021
|
+
await provider.sendMessage(messages);
|
|
3022
|
+
|
|
3023
|
+
const sent = JSON.stringify(lastStreamParams!.messages);
|
|
3024
|
+
expect(sent).not.toContain("_attachmentId");
|
|
3025
|
+
expect(sent).not.toContain("att-image-1");
|
|
3026
|
+
expect(sent).not.toContain("att-file-1");
|
|
3027
|
+
// The image itself still went out.
|
|
3028
|
+
expect(sent).toContain(PNG_B64);
|
|
3029
|
+
});
|
|
3030
|
+
});
|
|
3031
|
+
|
|
2976
3032
|
// ---------------------------------------------------------------------------
|
|
2977
3033
|
// Tests — Orphaned UTF-16 surrogate sanitization
|
|
2978
3034
|
// ---------------------------------------------------------------------------
|
|
@@ -95,19 +95,6 @@ mock.module("../persistence/attachments-store.js", () => ({
|
|
|
95
95
|
linkAttachmentToMessage: () => {},
|
|
96
96
|
}));
|
|
97
97
|
|
|
98
|
-
mock.module("../memory/retriever.js", () => ({
|
|
99
|
-
buildMemoryRecall: async () => ({
|
|
100
|
-
enabled: false,
|
|
101
|
-
degraded: false,
|
|
102
|
-
injectedText: "",
|
|
103
|
-
|
|
104
|
-
semanticHits: 0,
|
|
105
|
-
injectedTokens: 0,
|
|
106
|
-
latencyMs: 0,
|
|
107
|
-
}),
|
|
108
|
-
injectMemoryRecallAsUserBlock: (msgs: Message[]) => msgs,
|
|
109
|
-
}));
|
|
110
|
-
|
|
111
98
|
mock.module("../plugins/defaults/compaction/window-manager.js", () => ({
|
|
112
99
|
ContextWindowManager: class {
|
|
113
100
|
estimateInputTokens() {
|
|
@@ -16,12 +16,14 @@ import {
|
|
|
16
16
|
isValidBase64,
|
|
17
17
|
linkAttachmentToMessage,
|
|
18
18
|
MAX_UPLOAD_BYTES,
|
|
19
|
+
resolveAttachmentsForPersist,
|
|
19
20
|
uploadAttachment,
|
|
20
21
|
validateAttachmentUpload,
|
|
21
22
|
} from "../persistence/attachments-store.js";
|
|
22
23
|
import {
|
|
23
24
|
addMessage,
|
|
24
25
|
createConversation,
|
|
26
|
+
deleteMessageById,
|
|
25
27
|
} from "../persistence/conversation-crud.js";
|
|
26
28
|
import { getConversationDirPath } from "../persistence/conversation-disk-view.js";
|
|
27
29
|
import { getDb } from "../persistence/db-connection.js";
|
|
@@ -675,6 +677,115 @@ describe("deleteOrphanAttachments", () => {
|
|
|
675
677
|
});
|
|
676
678
|
});
|
|
677
679
|
|
|
680
|
+
// ---------------------------------------------------------------------------
|
|
681
|
+
// resolveAttachmentsForPersist
|
|
682
|
+
// ---------------------------------------------------------------------------
|
|
683
|
+
|
|
684
|
+
describe("resolveAttachmentsForPersist", () => {
|
|
685
|
+
beforeEach(resetTables);
|
|
686
|
+
|
|
687
|
+
// One implementation behind every send path that turns ids into a persisted
|
|
688
|
+
// user message (HTTP send, processMessage, the voice bridge), so its shape is
|
|
689
|
+
// pinned here rather than at each caller.
|
|
690
|
+
test("hydrates ids into the shape a persisted message stores", async () => {
|
|
691
|
+
const stored = await uploadAttachment("photo.png", "image/png", "ZnJhbWU=");
|
|
692
|
+
|
|
693
|
+
expect(resolveAttachmentsForPersist([stored.id])).toEqual([
|
|
694
|
+
{
|
|
695
|
+
id: stored.id,
|
|
696
|
+
filename: "photo.png",
|
|
697
|
+
mimeType: "image/png",
|
|
698
|
+
data: "ZnJhbWU=",
|
|
699
|
+
},
|
|
700
|
+
]);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
test("carries the source path only for attachments that have one", async () => {
|
|
704
|
+
const withPath = await uploadAttachment(
|
|
705
|
+
"from-disk.txt",
|
|
706
|
+
"text/plain",
|
|
707
|
+
"AA==",
|
|
708
|
+
);
|
|
709
|
+
const plain = await uploadAttachment("typed.txt", "text/plain", "BB==");
|
|
710
|
+
rawRun(
|
|
711
|
+
"test:setSourcePath",
|
|
712
|
+
"UPDATE attachments SET source_path = ? WHERE id = ?",
|
|
713
|
+
"/tmp/from-disk.txt",
|
|
714
|
+
withPath.id,
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
const resolved = resolveAttachmentsForPersist([withPath.id, plain.id]);
|
|
718
|
+
|
|
719
|
+
expect(resolved[0]).toMatchObject({ filePath: "/tmp/from-disk.txt" });
|
|
720
|
+
expect(resolved[1]).not.toHaveProperty("filePath");
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
test("drops ids with no attachment row", async () => {
|
|
724
|
+
const stored = await uploadAttachment("kept.txt", "text/plain", "AA==");
|
|
725
|
+
|
|
726
|
+
const resolved = resolveAttachmentsForPersist([stored.id, "att-missing"]);
|
|
727
|
+
|
|
728
|
+
expect(resolved.map((a) => a.id)).toEqual([stored.id]);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
test("returns nothing for an empty id list", () => {
|
|
732
|
+
expect(resolveAttachmentsForPersist([])).toEqual([]);
|
|
733
|
+
});
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
// ---------------------------------------------------------------------------
|
|
737
|
+
// deleteMessageById orphan cleanup
|
|
738
|
+
// ---------------------------------------------------------------------------
|
|
739
|
+
|
|
740
|
+
describe("deleteMessageById attachment cleanup", () => {
|
|
741
|
+
beforeEach(resetTables);
|
|
742
|
+
|
|
743
|
+
test("collects an attachment the deleted message alone referenced", async () => {
|
|
744
|
+
const conv = createConversation();
|
|
745
|
+
const msg = await addMessage(conv.id, "user", "With attachment");
|
|
746
|
+
const stored = await uploadAttachment("shot.txt", "text/plain", "ZGF0YQ==");
|
|
747
|
+
linkAttachmentToMessage(msg.id, stored.id, 0);
|
|
748
|
+
const filePath = getFilePathForAttachment(stored.id);
|
|
749
|
+
|
|
750
|
+
deleteMessageById(msg.id);
|
|
751
|
+
|
|
752
|
+
expect(getAttachmentById(stored.id)).toBeNull();
|
|
753
|
+
expect(existsSync(filePath!)).toBe(false);
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
test("retained ids survive the delete, row and bytes", async () => {
|
|
757
|
+
// A caller rolling a message back rather than deleting it: the attachment
|
|
758
|
+
// returns to the uploaded-but-unlinked state every attachment sits in
|
|
759
|
+
// between upload and send, so a later message can still reference it.
|
|
760
|
+
const conv = createConversation();
|
|
761
|
+
const msg = await addMessage(conv.id, "user", "Rolled back");
|
|
762
|
+
const stored = await uploadAttachment("shot.txt", "text/plain", "ZGF0YQ==");
|
|
763
|
+
linkAttachmentToMessage(msg.id, stored.id, 0);
|
|
764
|
+
const filePath = getFilePathForAttachment(stored.id);
|
|
765
|
+
|
|
766
|
+
deleteMessageById(msg.id, { retainAttachmentIds: [stored.id] });
|
|
767
|
+
|
|
768
|
+
expect(getAttachmentById(stored.id)).not.toBeNull();
|
|
769
|
+
expect(existsSync(filePath!)).toBe(true);
|
|
770
|
+
// The row is gone even though its attachment stayed.
|
|
771
|
+
expect(getAttachmentsForMessage(msg.id)).toHaveLength(0);
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
test("retaining one id does not spare the others on the row", async () => {
|
|
775
|
+
const conv = createConversation();
|
|
776
|
+
const msg = await addMessage(conv.id, "user", "Two attachments");
|
|
777
|
+
const kept = await uploadAttachment("kept.txt", "text/plain", "AAAA");
|
|
778
|
+
const dropped = await uploadAttachment("dropped.txt", "text/plain", "BBBB");
|
|
779
|
+
linkAttachmentToMessage(msg.id, kept.id, 0);
|
|
780
|
+
linkAttachmentToMessage(msg.id, dropped.id, 1);
|
|
781
|
+
|
|
782
|
+
deleteMessageById(msg.id, { retainAttachmentIds: [kept.id] });
|
|
783
|
+
|
|
784
|
+
expect(getAttachmentById(kept.id)).not.toBeNull();
|
|
785
|
+
expect(getAttachmentById(dropped.id)).toBeNull();
|
|
786
|
+
});
|
|
787
|
+
});
|
|
788
|
+
|
|
678
789
|
// ---------------------------------------------------------------------------
|
|
679
790
|
// validateAttachmentUpload
|
|
680
791
|
// ---------------------------------------------------------------------------
|
|
@@ -735,9 +846,9 @@ describe("validateAttachmentUpload", () => {
|
|
|
735
846
|
test("accepts shell scripts as text attachments", () => {
|
|
736
847
|
expect(validateAttachmentUpload("script.sh", "text/plain").ok).toBe(true);
|
|
737
848
|
expect(validateAttachmentUpload("script.SH", "text/plain").ok).toBe(true);
|
|
738
|
-
expect(
|
|
739
|
-
|
|
740
|
-
)
|
|
849
|
+
expect(validateAttachmentUpload("setup.sh", "application/x-sh").ok).toBe(
|
|
850
|
+
true,
|
|
851
|
+
);
|
|
741
852
|
expect(
|
|
742
853
|
validateAttachmentUpload("run.sh", "application/x-shellscript").ok,
|
|
743
854
|
).toBe(true);
|
|
@@ -760,9 +871,9 @@ describe("validateAttachmentUpload", () => {
|
|
|
760
871
|
expect(
|
|
761
872
|
validateAttachmentUpload("PROGRAM.EXE", "application/octet-stream").ok,
|
|
762
873
|
).toBe(false);
|
|
763
|
-
expect(
|
|
764
|
-
|
|
765
|
-
);
|
|
874
|
+
expect(
|
|
875
|
+
validateAttachmentUpload("INSTALL.DMG", "application/octet-stream").ok,
|
|
876
|
+
).toBe(false);
|
|
766
877
|
});
|
|
767
878
|
|
|
768
879
|
test("rejects unsupported MIME types", () => {
|