@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
|
@@ -385,7 +385,13 @@ mock.module("../runtime/access-request-helper.js", () => ({
|
|
|
385
385
|
// Gateway guardian-request client polled by the guardian wait controller.
|
|
386
386
|
let mockGuardianRequest: { status: string } | null = null;
|
|
387
387
|
const mockGetGuardianRequest = jest.fn(async () => mockGuardianRequest);
|
|
388
|
+
// Spread the actual module so transitive importers of names this
|
|
389
|
+
// factory does not stub keep resolving (a partial factory breaks at
|
|
390
|
+
// import time when the graph gains a new named import).
|
|
391
|
+
const actualGatewayGuardianRequests =
|
|
392
|
+
await import("../channels/gateway-guardian-requests.js");
|
|
388
393
|
mock.module("../channels/gateway-guardian-requests.js", () => ({
|
|
394
|
+
...actualGatewayGuardianRequests,
|
|
389
395
|
getGuardianRequestOrNull: mockGetGuardianRequest,
|
|
390
396
|
}));
|
|
391
397
|
|
|
@@ -318,6 +318,18 @@ describe("non-member access request notification", () => {
|
|
|
318
318
|
kind: "access_request",
|
|
319
319
|
});
|
|
320
320
|
expect(pending.length).toBe(1);
|
|
321
|
+
|
|
322
|
+
// A pending row past its deadline never dedupes: it can sit pending
|
|
323
|
+
// until the sweep expires it, it is already undecidable, and absorbing
|
|
324
|
+
// the fresh attempt would silently drop the new guardian notification.
|
|
325
|
+
pending[0].expiresAt = Date.now() - 1000;
|
|
326
|
+
const req3 = buildInboundRequest({
|
|
327
|
+
externalMessageId: `msg-third-${Date.now()}`,
|
|
328
|
+
content: "Still hoping to get access.",
|
|
329
|
+
});
|
|
330
|
+
await handleChannelInbound(req3, undefined, TEST_BEARER_TOKEN);
|
|
331
|
+
|
|
332
|
+
expect(emitSignalCalls.length).toBe(2);
|
|
321
333
|
});
|
|
322
334
|
|
|
323
335
|
// A bare guardian deny (a `denied` request with no durable contact seeded)
|
|
@@ -10,20 +10,44 @@
|
|
|
10
10
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
11
|
|
|
12
12
|
const dmOpens: string[] = [];
|
|
13
|
-
const sendCalls: Array<{
|
|
13
|
+
const sendCalls: Array<{
|
|
14
|
+
channelId: string;
|
|
15
|
+
text: string;
|
|
16
|
+
approval?: { requestId: string } | undefined;
|
|
17
|
+
}> = [];
|
|
14
18
|
const editCalls: Array<{ channelId: string; messageId: string; text: string }> =
|
|
15
19
|
[];
|
|
16
20
|
|
|
21
|
+
// Spread the actual module: the real send.js is imported below for its
|
|
22
|
+
// error class and transitively needs api.js's other named exports.
|
|
23
|
+
const actualApi = await import("../messaging/providers/discord/api.js");
|
|
17
24
|
mock.module("../messaging/providers/discord/api.js", () => ({
|
|
25
|
+
...actualApi,
|
|
18
26
|
openDiscordDmChannel: async (userId: string) => {
|
|
19
27
|
dmOpens.push(userId);
|
|
20
28
|
return `dm-for-${userId}`;
|
|
21
29
|
},
|
|
22
30
|
}));
|
|
23
31
|
|
|
32
|
+
const actualSend = await import("../messaging/providers/discord/send.js");
|
|
24
33
|
mock.module("../messaging/providers/discord/send.js", () => ({
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
DiscordPartialSendError: actualSend.DiscordPartialSendError,
|
|
35
|
+
sendDiscordReply: async (
|
|
36
|
+
target: { channelId: string },
|
|
37
|
+
text: string,
|
|
38
|
+
approval?: { requestId: string },
|
|
39
|
+
) => {
|
|
40
|
+
if (approval && failRichSends) {
|
|
41
|
+
throw failRichSends === "partial"
|
|
42
|
+
? new actualSend.DiscordPartialSendError(
|
|
43
|
+
new Error("final chunk rejected"),
|
|
44
|
+
2,
|
|
45
|
+
"tail of the card",
|
|
46
|
+
"1500",
|
|
47
|
+
)
|
|
48
|
+
: new Error("simulated component rejection");
|
|
49
|
+
}
|
|
50
|
+
sendCalls.push({ channelId: target.channelId, text, approval });
|
|
27
51
|
return { lastMessageId: String(2000 + sendCalls.length) };
|
|
28
52
|
},
|
|
29
53
|
editDiscordMessage: async (
|
|
@@ -68,10 +92,13 @@ function makeDestination(
|
|
|
68
92
|
};
|
|
69
93
|
}
|
|
70
94
|
|
|
95
|
+
let failRichSends: boolean | "partial" = false;
|
|
96
|
+
|
|
71
97
|
beforeEach(() => {
|
|
72
98
|
dmOpens.length = 0;
|
|
73
99
|
sendCalls.length = 0;
|
|
74
100
|
editCalls.length = 0;
|
|
101
|
+
failRichSends = false;
|
|
75
102
|
});
|
|
76
103
|
|
|
77
104
|
describe("DiscordAdapter.send", () => {
|
|
@@ -87,7 +114,51 @@ describe("DiscordAdapter.send", () => {
|
|
|
87
114
|
expect(sendCalls[0].text).toContain("Your task finished.");
|
|
88
115
|
});
|
|
89
116
|
|
|
90
|
-
test("approval notifications
|
|
117
|
+
test("approval notifications deliver with component buttons", async () => {
|
|
118
|
+
const adapter = new DiscordAdapter();
|
|
119
|
+
const result = await adapter.send(
|
|
120
|
+
makePayload({
|
|
121
|
+
approvalContext: {
|
|
122
|
+
requestId: "req-1",
|
|
123
|
+
actions: [{ id: "approve_once", label: "Approve once" }],
|
|
124
|
+
plainTextFallback: 'Reply "approve" or "reject" to decide.',
|
|
125
|
+
},
|
|
126
|
+
}),
|
|
127
|
+
makeDestination(),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
expect(result.success).toBe(true);
|
|
131
|
+
expect(sendCalls).toHaveLength(1);
|
|
132
|
+
expect(sendCalls[0].approval?.requestId).toBe("req-1");
|
|
133
|
+
// The rich card carries no typed-command tail; buttons are the controls.
|
|
134
|
+
expect(sendCalls[0].text).not.toContain("Reply");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("a mid-card failure completes the card instead of replaying it", async () => {
|
|
138
|
+
failRichSends = "partial";
|
|
139
|
+
const adapter = new DiscordAdapter();
|
|
140
|
+
const result = await adapter.send(
|
|
141
|
+
makePayload({
|
|
142
|
+
approvalContext: {
|
|
143
|
+
requestId: "req-1",
|
|
144
|
+
actions: [{ id: "approve_once", label: "Approve once" }],
|
|
145
|
+
plainTextFallback: 'Reply "approve" or "reject" to decide.',
|
|
146
|
+
},
|
|
147
|
+
}),
|
|
148
|
+
makeDestination(),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
expect(result.success).toBe(true);
|
|
152
|
+
expect(sendCalls).toHaveLength(1);
|
|
153
|
+
// Only the undelivered remainder goes out, with the typed instructions;
|
|
154
|
+
// the delivered chunks are never sent twice.
|
|
155
|
+
expect(sendCalls[0].approval).toBeUndefined();
|
|
156
|
+
expect(sendCalls[0].text).toContain("tail of the card");
|
|
157
|
+
expect(sendCalls[0].text).toContain('Reply "approve" or "reject"');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("a failed rich delivery falls back to the typed-command card", async () => {
|
|
161
|
+
failRichSends = true;
|
|
91
162
|
const adapter = new DiscordAdapter();
|
|
92
163
|
const result = await adapter.send(
|
|
93
164
|
makePayload({
|
|
@@ -102,6 +173,7 @@ describe("DiscordAdapter.send", () => {
|
|
|
102
173
|
|
|
103
174
|
expect(result.success).toBe(true);
|
|
104
175
|
expect(sendCalls).toHaveLength(1);
|
|
176
|
+
expect(sendCalls[0].approval).toBeUndefined();
|
|
105
177
|
expect(sendCalls[0].text).toContain(
|
|
106
178
|
'Reply "approve" or "reject" to decide.',
|
|
107
179
|
);
|
|
@@ -717,6 +717,27 @@ describe("POST oauth/request", () => {
|
|
|
717
717
|
expect(req.body).toEqual({ title: "Sheet" });
|
|
718
718
|
});
|
|
719
719
|
|
|
720
|
+
test("decodes a base64 request body into a Buffer", async () => {
|
|
721
|
+
const binary = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0xff, 0x00]);
|
|
722
|
+
|
|
723
|
+
await getRoute("POST", "oauth/request").handler(
|
|
724
|
+
makeArgs({
|
|
725
|
+
body: {
|
|
726
|
+
provider: "google",
|
|
727
|
+
url: "https://api.google.com/upload/drive/v3/files",
|
|
728
|
+
method: "POST",
|
|
729
|
+
headers: { "Content-Type": "application/pdf" },
|
|
730
|
+
parsed_data: binary.toString("base64"),
|
|
731
|
+
body_encoding: "base64",
|
|
732
|
+
},
|
|
733
|
+
}),
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
const req = mockResolveRequests[0] as { body: unknown };
|
|
737
|
+
expect(Buffer.isBuffer(req.body)).toBe(true);
|
|
738
|
+
expect(Buffer.from(req.body as Uint8Array).equals(binary)).toBe(true);
|
|
739
|
+
});
|
|
740
|
+
|
|
720
741
|
test("base64-encodes binary response bodies for the JSON envelope", async () => {
|
|
721
742
|
const binary = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0xff, 0x00]);
|
|
722
743
|
mockResolveResponse = {
|
|
@@ -2051,3 +2051,66 @@ describe("FireworksProvider reasoning_effort ceiling", () => {
|
|
|
2051
2051
|
expect(lastCreateParams!.reasoning_effort).toBe("max");
|
|
2052
2052
|
});
|
|
2053
2053
|
});
|
|
2054
|
+
|
|
2055
|
+
// GLM 5.3 accepts only low|high|max and 4xxes on the in-between tiers, so
|
|
2056
|
+
// the catalog's `supportedEfforts` snaps them down (see
|
|
2057
|
+
// snapReasoningEffortToSupported).
|
|
2058
|
+
describe("FireworksProvider sparse reasoning_effort support (GLM 5.3)", () => {
|
|
2059
|
+
beforeEach(() => {
|
|
2060
|
+
fakeChunks = [];
|
|
2061
|
+
lastCreateParams = null;
|
|
2062
|
+
});
|
|
2063
|
+
|
|
2064
|
+
const send = async (
|
|
2065
|
+
model: string,
|
|
2066
|
+
effort: "none" | "low" | "medium" | "high" | "xhigh" | "max",
|
|
2067
|
+
) => {
|
|
2068
|
+
const fw = new FireworksProvider("fw-key", model);
|
|
2069
|
+
await fw.sendMessage([userMsg("hi")], {
|
|
2070
|
+
systemPrompt: "system",
|
|
2071
|
+
config: { effort },
|
|
2072
|
+
});
|
|
2073
|
+
return lastCreateParams!.reasoning_effort;
|
|
2074
|
+
};
|
|
2075
|
+
|
|
2076
|
+
test('GLM 5.3 snaps "medium" down to "low"', async () => {
|
|
2077
|
+
expect(await send("accounts/fireworks/models/glm-5p3", "medium")).toBe(
|
|
2078
|
+
"low",
|
|
2079
|
+
);
|
|
2080
|
+
});
|
|
2081
|
+
|
|
2082
|
+
test('GLM 5.3 snaps "xhigh" down to "high"', async () => {
|
|
2083
|
+
expect(await send("accounts/fireworks/models/glm-5p3", "xhigh")).toBe(
|
|
2084
|
+
"high",
|
|
2085
|
+
);
|
|
2086
|
+
});
|
|
2087
|
+
|
|
2088
|
+
test("GLM 5.3 forwards supported values verbatim", async () => {
|
|
2089
|
+
expect(await send("accounts/fireworks/models/glm-5p3", "low")).toBe("low");
|
|
2090
|
+
expect(await send("accounts/fireworks/models/glm-5p3", "high")).toBe(
|
|
2091
|
+
"high",
|
|
2092
|
+
);
|
|
2093
|
+
expect(await send("accounts/fireworks/models/glm-5p3", "max")).toBe("max");
|
|
2094
|
+
});
|
|
2095
|
+
|
|
2096
|
+
test('GLM 5.3 Flash snaps "medium"/"xhigh" the same way', async () => {
|
|
2097
|
+
expect(
|
|
2098
|
+
await send("accounts/fireworks/models/glm-5p3-flash", "medium"),
|
|
2099
|
+
).toBe("low");
|
|
2100
|
+
expect(await send("accounts/fireworks/models/glm-5p3-flash", "xhigh")).toBe(
|
|
2101
|
+
"high",
|
|
2102
|
+
);
|
|
2103
|
+
});
|
|
2104
|
+
|
|
2105
|
+
test('effort: "none" is not snapped (opt-out keeps its own handling)', async () => {
|
|
2106
|
+
expect(await send("accounts/fireworks/models/glm-5p3", "none")).toBe(
|
|
2107
|
+
"none",
|
|
2108
|
+
);
|
|
2109
|
+
});
|
|
2110
|
+
|
|
2111
|
+
test("models without supportedEfforts are unaffected", async () => {
|
|
2112
|
+
expect(
|
|
2113
|
+
await send("accounts/fireworks/models/deepseek-v4-pro", "medium"),
|
|
2114
|
+
).toBe("medium");
|
|
2115
|
+
});
|
|
2116
|
+
});
|
|
@@ -185,7 +185,7 @@ import { setConfig } from "./helpers/set-config.js";
|
|
|
185
185
|
function makeDeps(
|
|
186
186
|
conversationId: string,
|
|
187
187
|
overrides: {
|
|
188
|
-
assistantMessageChannel?: "slack" | "vellum" | "telegram";
|
|
188
|
+
assistantMessageChannel?: "slack" | "vellum" | "telegram" | "discord";
|
|
189
189
|
requesterChatId?: string;
|
|
190
190
|
requesterTimezoneLabel?: string;
|
|
191
191
|
clientTimezone?: string;
|
|
@@ -518,7 +518,119 @@ describe("outbound assistant Slack metadata persistence", () => {
|
|
|
518
518
|
|
|
519
519
|
const persisted = lastAssistantPersisted();
|
|
520
520
|
expect(persisted.metadata).toBeDefined();
|
|
521
|
-
// Non-Slack channels must leave the existing metadata shape untouched
|
|
521
|
+
// Non-Slack channels must leave the existing metadata shape untouched,
|
|
522
|
+
// and a vellum turn is not a provider message, so it gets no neutral
|
|
523
|
+
// envelope either.
|
|
522
524
|
expect(persisted.metadata?.slackMeta).toBeUndefined();
|
|
525
|
+
expect(persisted.metadata?.providerMeta).toBeUndefined();
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
test("stamps a partial neutral envelope on non-Slack channel replies", async () => {
|
|
529
|
+
const conversationId = "conv-discord-stamp";
|
|
530
|
+
const deps = makeDeps(conversationId, {
|
|
531
|
+
assistantMessageChannel: "discord",
|
|
532
|
+
requesterChatId: "9990001112223334445",
|
|
533
|
+
});
|
|
534
|
+
await handleLlmCallStarted(state, deps);
|
|
535
|
+
await handleMessageComplete(
|
|
536
|
+
state,
|
|
537
|
+
deps,
|
|
538
|
+
makeMessageCompleteEvent("discord reply"),
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
const persisted = lastAssistantPersisted();
|
|
542
|
+
const providerMetaRaw = persisted.metadata?.providerMeta;
|
|
543
|
+
expect(typeof providerMetaRaw).toBe("string");
|
|
544
|
+
const providerMeta = JSON.parse(providerMetaRaw as string) as Record<
|
|
545
|
+
string,
|
|
546
|
+
unknown
|
|
547
|
+
>;
|
|
548
|
+
expect(providerMeta.source).toBe("discord");
|
|
549
|
+
expect(providerMeta.conversationExternalId).toBe("9990001112223334445");
|
|
550
|
+
expect(providerMeta.eventKind).toBe("message");
|
|
551
|
+
// Persistence runs BEFORE the transport posts the message, so the
|
|
552
|
+
// provider-assigned id is not yet known; the post-send reconciliation in
|
|
553
|
+
// `deliverReplyViaCallback` fills `messageId` once the transport returns
|
|
554
|
+
// it. `threadId` is never stamped: a value there asserts a thread exists.
|
|
555
|
+
expect(providerMeta.messageId).toBeUndefined();
|
|
556
|
+
expect(providerMeta.threadId).toBeUndefined();
|
|
557
|
+
expect(persisted.metadata?.slackMeta).toBeUndefined();
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
test("post-send reconciliation patches messageId into the neutral envelope", async () => {
|
|
561
|
+
const conversationId = "conv-discord-reconcile";
|
|
562
|
+
const chatId = "9990001112223334445";
|
|
563
|
+
const deps = makeDeps(conversationId, {
|
|
564
|
+
assistantMessageChannel: "discord",
|
|
565
|
+
requesterChatId: chatId,
|
|
566
|
+
});
|
|
567
|
+
await handleLlmCallStarted(state, deps);
|
|
568
|
+
await handleMessageComplete(
|
|
569
|
+
state,
|
|
570
|
+
deps,
|
|
571
|
+
makeMessageCompleteEvent("discord reconciliation reply"),
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
const persisted = lastAssistantPersisted();
|
|
575
|
+
nextDeliveryTs = "1234567890123456789";
|
|
576
|
+
await deliverReplyViaCallback(
|
|
577
|
+
conversationId,
|
|
578
|
+
chatId,
|
|
579
|
+
"http://gateway/deliver/discord",
|
|
580
|
+
"assistant-1",
|
|
581
|
+
{ messageId: persisted.id },
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
const row = persistedRows.find(
|
|
585
|
+
(candidate) => candidate.id === persisted.id,
|
|
586
|
+
);
|
|
587
|
+
expect(typeof row?.metadata).toBe("string");
|
|
588
|
+
const envelope = JSON.parse(row!.metadata!) as Record<string, unknown>;
|
|
589
|
+
const reconciled = JSON.parse(envelope.providerMeta as string) as Record<
|
|
590
|
+
string,
|
|
591
|
+
unknown
|
|
592
|
+
>;
|
|
593
|
+
expect(reconciled.messageId).toBe("1234567890123456789");
|
|
594
|
+
expect(reconciled.source).toBe("discord");
|
|
595
|
+
expect(reconciled.conversationExternalId).toBe(chatId);
|
|
596
|
+
|
|
597
|
+
// A later report with a NEW id is another post of the same reply (a
|
|
598
|
+
// split at a tool boundary): it lands in additionalMessageIds and the
|
|
599
|
+
// first id keeps naming the row.
|
|
600
|
+
nextDeliveryTs = "8888888888888888888";
|
|
601
|
+
await deliverReplyViaCallback(
|
|
602
|
+
conversationId,
|
|
603
|
+
chatId,
|
|
604
|
+
"http://gateway/deliver/discord",
|
|
605
|
+
"assistant-1",
|
|
606
|
+
{ messageId: persisted.id },
|
|
607
|
+
);
|
|
608
|
+
const rowAfter = persistedRows.find(
|
|
609
|
+
(candidate) => candidate.id === persisted.id,
|
|
610
|
+
);
|
|
611
|
+
const metaAfter = JSON.parse(
|
|
612
|
+
JSON.parse(rowAfter!.metadata!).providerMeta as string,
|
|
613
|
+
) as Record<string, unknown>;
|
|
614
|
+
expect(metaAfter.messageId).toBe("1234567890123456789");
|
|
615
|
+
expect(metaAfter.additionalMessageIds).toEqual(["8888888888888888888"]);
|
|
616
|
+
|
|
617
|
+
// A redelivery reporting an already-recorded id changes nothing.
|
|
618
|
+
nextDeliveryTs = "8888888888888888888";
|
|
619
|
+
await deliverReplyViaCallback(
|
|
620
|
+
conversationId,
|
|
621
|
+
chatId,
|
|
622
|
+
"http://gateway/deliver/discord",
|
|
623
|
+
"assistant-1",
|
|
624
|
+
{ messageId: persisted.id },
|
|
625
|
+
);
|
|
626
|
+
const rowRedelivered = persistedRows.find(
|
|
627
|
+
(candidate) => candidate.id === persisted.id,
|
|
628
|
+
);
|
|
629
|
+
const metaRedelivered = JSON.parse(
|
|
630
|
+
JSON.parse(rowRedelivered!.metadata!).providerMeta as string,
|
|
631
|
+
) as Record<string, unknown>;
|
|
632
|
+
expect(metaRedelivered.additionalMessageIds).toEqual([
|
|
633
|
+
"8888888888888888888",
|
|
634
|
+
]);
|
|
523
635
|
});
|
|
524
636
|
});
|
|
@@ -33,6 +33,7 @@ mock.module("../agent/image-optimize.js", () => ({
|
|
|
33
33
|
}),
|
|
34
34
|
}));
|
|
35
35
|
|
|
36
|
+
import { uploadAttachment } from "../persistence/attachments-store.js";
|
|
36
37
|
import {
|
|
37
38
|
addMessage,
|
|
38
39
|
createConversation,
|
|
@@ -40,7 +41,10 @@ import {
|
|
|
40
41
|
} from "../persistence/conversation-crud.js";
|
|
41
42
|
import { getDb, getMemorySqlite } from "../persistence/db-connection.js";
|
|
42
43
|
import { initializeDb } from "../persistence/db-init.js";
|
|
43
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
persistUnsendableImageDowngrades,
|
|
46
|
+
unsendableImageReplacement,
|
|
47
|
+
} from "../plugins/defaults/image-recovery/recover.js";
|
|
44
48
|
import { base64Source } from "../providers/media-resolve.js";
|
|
45
49
|
import type { ContentBlock } from "../providers/types.js";
|
|
46
50
|
|
|
@@ -147,6 +151,64 @@ describe("persistUnsendableImageDowngrades (downscalable host)", () => {
|
|
|
147
151
|
).toBe(SHRUNK_DATA);
|
|
148
152
|
});
|
|
149
153
|
|
|
154
|
+
/** The resize replaces the block's whole source, so a reference-backed image
|
|
155
|
+
* loses `source.attachmentId` along with the reference. The id has to be
|
|
156
|
+
* derived from either shape and re-stamped, or the durable rewrite untags a
|
|
157
|
+
* camera frame permanently. */
|
|
158
|
+
test("a resized workspace_ref keeps its attachment id on the rebuilt block", async () => {
|
|
159
|
+
const stored = await uploadAttachment(
|
|
160
|
+
"frame.png",
|
|
161
|
+
"image/png",
|
|
162
|
+
oversizedPngBase64(),
|
|
163
|
+
);
|
|
164
|
+
const referenceBlock = {
|
|
165
|
+
type: "image",
|
|
166
|
+
source: {
|
|
167
|
+
type: "workspace_ref",
|
|
168
|
+
media_type: "image/png",
|
|
169
|
+
attachmentId: stored.id,
|
|
170
|
+
sizeBytes: 128,
|
|
171
|
+
// Persisted references carry the dimension hints the provider caps are
|
|
172
|
+
// judged against (see `attachmentsToReferenceBlocks`).
|
|
173
|
+
width: 12000,
|
|
174
|
+
height: 9000,
|
|
175
|
+
},
|
|
176
|
+
} as Extract<ContentBlock, { type: "image" }>;
|
|
177
|
+
|
|
178
|
+
const replacement = await unsendableImageReplacement(referenceBlock);
|
|
179
|
+
|
|
180
|
+
// The source was flattened to the shrunk inline bytes...
|
|
181
|
+
expect(replacement).toMatchObject({
|
|
182
|
+
type: "image",
|
|
183
|
+
source: { type: "base64", data: SHRUNK_DATA },
|
|
184
|
+
});
|
|
185
|
+
// ...and the link to the attachment row survived the flattening.
|
|
186
|
+
expect((replacement as { _attachmentId?: string })._attachmentId).toBe(
|
|
187
|
+
stored.id,
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
/** An inline resize input carries its id on the top-level field instead. */
|
|
192
|
+
test("a resized inline block keeps its top-level attachment id", async () => {
|
|
193
|
+
const inlineBlock = {
|
|
194
|
+
type: "image",
|
|
195
|
+
source: {
|
|
196
|
+
type: "base64",
|
|
197
|
+
media_type: "image/png",
|
|
198
|
+
data: oversizedPngBase64(),
|
|
199
|
+
},
|
|
200
|
+
_attachmentId: "att-inline-1",
|
|
201
|
+
} as Extract<ContentBlock, { type: "image" }>;
|
|
202
|
+
|
|
203
|
+
const replacement = await unsendableImageReplacement(inlineBlock);
|
|
204
|
+
|
|
205
|
+
expect(replacement).toMatchObject({
|
|
206
|
+
type: "image",
|
|
207
|
+
source: { type: "base64", data: SHRUNK_DATA },
|
|
208
|
+
_attachmentId: "att-inline-1",
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
150
212
|
/** Re-running is a no-op: the downscaled payload is within limits. */
|
|
151
213
|
test("is idempotent after a downscale rewrite", async () => {
|
|
152
214
|
// GIVEN a conversation whose oversized screenshot was already downscaled
|
|
@@ -392,6 +392,25 @@ describe("unsendableImageReplacement", () => {
|
|
|
392
392
|
});
|
|
393
393
|
});
|
|
394
394
|
|
|
395
|
+
/** Relabeling rebuilds the block, and the rebuilt form is written back to
|
|
396
|
+
* the stored row. Losing the internal attachment id there would untag a
|
|
397
|
+
* camera frame for good, so the relabel carries it across. */
|
|
398
|
+
test("relabeling preserves the internal attachment id", async () => {
|
|
399
|
+
const pngData = makePngBase64(1024, 768);
|
|
400
|
+
const mislabeled = {
|
|
401
|
+
...(imageBlock(pngData, "image/jpeg") as Extract<
|
|
402
|
+
ContentBlock,
|
|
403
|
+
{ type: "image" }
|
|
404
|
+
>),
|
|
405
|
+
_attachmentId: "att-frame-1",
|
|
406
|
+
};
|
|
407
|
+
const replacement = await unsendableImageReplacement(mislabeled);
|
|
408
|
+
expect(replacement).toMatchObject({
|
|
409
|
+
type: "image",
|
|
410
|
+
_attachmentId: "att-frame-1",
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
395
414
|
/** Relabeling a persisted (workspace_ref) image keeps the reference shape —
|
|
396
415
|
* inlining it would bake the full payload into the stored message row. */
|
|
397
416
|
test("relabels a mislabeled workspace_ref without inlining the payload", async () => {
|
|
@@ -271,10 +271,10 @@ describe("multi-file plugin apps compile on open", () => {
|
|
|
271
271
|
expect(result.html).toContain("Compiled Board");
|
|
272
272
|
expect(result.html).not.toContain("not been compiled");
|
|
273
273
|
expect(result.origin).toBe("plugin:acme");
|
|
274
|
-
// The daemon must not write dist/ into the read-only plugin tree
|
|
274
|
+
// The daemon must not write dist/ into the read-only plugin tree. That is
|
|
275
275
|
// the monitor's job. The on-open build happened in a throwaway temp dir.
|
|
276
276
|
expect(existsSync(join(appDir, "dist"))).toBe(false);
|
|
277
|
-
});
|
|
277
|
+
}, 15_000);
|
|
278
278
|
|
|
279
279
|
test("apps_open on a malformed plugin app (no src/, no dist/) serves the fallback, not a 500", async () => {
|
|
280
280
|
// An app dir with neither a root index.html nor src/ nor dist/ is
|
|
@@ -86,6 +86,7 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
86
86
|
"../../../../../messaging/providers/slack/message-metadata.js",
|
|
87
87
|
"../../../../../persistence/auto-analysis-constants.js",
|
|
88
88
|
"../../../../../persistence/checkpoints.js",
|
|
89
|
+
"../../../../../persistence/conversation-types.js",
|
|
89
90
|
"../../../../../persistence/db-connection.js",
|
|
90
91
|
"../../../../../persistence/embeddings/embed.js",
|
|
91
92
|
"../../../../../persistence/embeddings/embedding-backend.js",
|
|
@@ -2,7 +2,7 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
mock.module("../persistence/attachments-store.js", () => ({
|
|
4
4
|
getAttachmentsByIds: () => [],
|
|
5
|
-
|
|
5
|
+
resolveAttachmentsForPersist: () => [],
|
|
6
6
|
}));
|
|
7
7
|
|
|
8
8
|
mock.module("../channels/gateway-guardian-requests.js", () => ({
|
|
@@ -27,7 +27,7 @@ let validateShouldFail = false;
|
|
|
27
27
|
|
|
28
28
|
mock.module("../persistence/attachments-store.js", () => ({
|
|
29
29
|
getAttachmentsByIds: () => [],
|
|
30
|
-
|
|
30
|
+
resolveAttachmentsForPersist: () => [],
|
|
31
31
|
attachmentExists: () => false,
|
|
32
32
|
linkAttachmentToMessage: () => "att-stored",
|
|
33
33
|
scopeAttachmentToMessageConversation: () => null,
|
|
@@ -97,11 +97,6 @@ mock.module("../persistence/attachments-store.js", () => ({
|
|
|
97
97
|
linkAttachmentToMessage: () => {},
|
|
98
98
|
}));
|
|
99
99
|
|
|
100
|
-
mock.module("../memory/retriever.js", () => ({
|
|
101
|
-
buildMemoryRecall: async () => null,
|
|
102
|
-
injectMemoryRecallAsUserBlock: (msgs: Message[]) => msgs,
|
|
103
|
-
}));
|
|
104
|
-
|
|
105
100
|
mock.module("../memory/query-builder.js", () => ({
|
|
106
101
|
buildMemoryQuery: () => "",
|
|
107
102
|
}));
|
|
@@ -165,23 +165,6 @@ mock.module("../persistence/attachments-store.js", () => ({
|
|
|
165
165
|
uploadAttachment: () => ({ id: "att-1" }),
|
|
166
166
|
linkAttachmentToMessage: () => {},
|
|
167
167
|
}));
|
|
168
|
-
mock.module("../memory/retriever.js", () => ({
|
|
169
|
-
buildMemoryRecall: async () => ({
|
|
170
|
-
enabled: false,
|
|
171
|
-
degraded: false,
|
|
172
|
-
reason: null,
|
|
173
|
-
provider: "mock",
|
|
174
|
-
model: "mock",
|
|
175
|
-
injectedText: "",
|
|
176
|
-
semanticHits: 0,
|
|
177
|
-
mergedCount: 0,
|
|
178
|
-
selectedCount: 0,
|
|
179
|
-
injectedTokens: 0,
|
|
180
|
-
latencyMs: 0,
|
|
181
|
-
topCandidates: [],
|
|
182
|
-
}),
|
|
183
|
-
injectMemoryRecallAsUserBlock: (msgs: Message[]) => msgs,
|
|
184
|
-
}));
|
|
185
168
|
mock.module("../memory/query-builder.js", () => ({
|
|
186
169
|
buildMemoryQuery: () => "",
|
|
187
170
|
}));
|
|
@@ -109,6 +109,12 @@ describe("QdrantManager", () => {
|
|
|
109
109
|
});
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
+
test("falls back to the x64 zip release on Windows arm64", () => {
|
|
113
|
+
expect(resolveQdrantReleaseAsset("win32", "arm64")).toEqual(
|
|
114
|
+
resolveQdrantReleaseAsset("win32", "x64"),
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
112
118
|
// ── getUrl ───────────────────────────────────────────────────
|
|
113
119
|
|
|
114
120
|
describe("getUrl", () => {
|