@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
|
@@ -6,6 +6,92 @@ import {
|
|
|
6
6
|
enrichMessageWithSourcePaths,
|
|
7
7
|
} from "../agent/attachments.js";
|
|
8
8
|
import { createUserMessage } from "../agent/message-types.js";
|
|
9
|
+
import {
|
|
10
|
+
attachmentIdFragment,
|
|
11
|
+
mediaBlockAttachmentId,
|
|
12
|
+
} from "../providers/types.js";
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// attachmentIdFragment
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
describe("attachmentIdFragment", () => {
|
|
19
|
+
test("yields the property when an id is present", () => {
|
|
20
|
+
expect(attachmentIdFragment("att-1")).toEqual({ _attachmentId: "att-1" });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("yields nothing for a missing or empty id", () => {
|
|
24
|
+
// Absent rather than present-and-empty: the read side requires a non-empty
|
|
25
|
+
// string before it treats the field as an id.
|
|
26
|
+
expect(attachmentIdFragment(undefined)).toEqual({});
|
|
27
|
+
expect(attachmentIdFragment("")).toEqual({});
|
|
28
|
+
expect("_attachmentId" in attachmentIdFragment("")).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// mediaBlockAttachmentId
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
describe("mediaBlockAttachmentId", () => {
|
|
37
|
+
test("reads a reference's id off its source", () => {
|
|
38
|
+
expect(
|
|
39
|
+
mediaBlockAttachmentId({
|
|
40
|
+
type: "image",
|
|
41
|
+
source: {
|
|
42
|
+
type: "workspace_ref",
|
|
43
|
+
media_type: "image/png",
|
|
44
|
+
attachmentId: "att-ref",
|
|
45
|
+
sizeBytes: 10,
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
).toBe("att-ref");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("reads an inline block's id off the top-level field", () => {
|
|
52
|
+
expect(
|
|
53
|
+
mediaBlockAttachmentId({
|
|
54
|
+
type: "image",
|
|
55
|
+
source: { type: "base64", media_type: "image/png", data: "AAAA" },
|
|
56
|
+
_attachmentId: "att-inline",
|
|
57
|
+
}),
|
|
58
|
+
).toBe("att-inline");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("is undefined for a block that came from no attachment", () => {
|
|
62
|
+
expect(
|
|
63
|
+
mediaBlockAttachmentId({
|
|
64
|
+
type: "image",
|
|
65
|
+
source: { type: "base64", media_type: "image/png", data: "AAAA" },
|
|
66
|
+
}),
|
|
67
|
+
).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("covers file blocks in both shapes", () => {
|
|
71
|
+
expect(
|
|
72
|
+
mediaBlockAttachmentId({
|
|
73
|
+
type: "file",
|
|
74
|
+
source: {
|
|
75
|
+
type: "workspace_ref",
|
|
76
|
+
media_type: "application/pdf",
|
|
77
|
+
attachmentId: "att-file-ref",
|
|
78
|
+
sizeBytes: 10,
|
|
79
|
+
},
|
|
80
|
+
}),
|
|
81
|
+
).toBe("att-file-ref");
|
|
82
|
+
expect(
|
|
83
|
+
mediaBlockAttachmentId({
|
|
84
|
+
type: "file",
|
|
85
|
+
source: {
|
|
86
|
+
type: "base64",
|
|
87
|
+
media_type: "application/pdf",
|
|
88
|
+
data: "AAAA",
|
|
89
|
+
},
|
|
90
|
+
_attachmentId: "att-file-inline",
|
|
91
|
+
}),
|
|
92
|
+
).toBe("att-file-inline");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
9
95
|
|
|
10
96
|
// ---------------------------------------------------------------------------
|
|
11
97
|
// attachmentsToReferenceBlocks
|
|
@@ -152,6 +238,42 @@ describe("attachmentsToContentBlocks", () => {
|
|
|
152
238
|
const blocks = await attachmentsToContentBlocks([]);
|
|
153
239
|
expect(blocks).toHaveLength(0);
|
|
154
240
|
});
|
|
241
|
+
|
|
242
|
+
test("stamps the attachment id on image and file blocks alike", async () => {
|
|
243
|
+
const blocks = await attachmentsToContentBlocks([
|
|
244
|
+
{
|
|
245
|
+
id: "att-img",
|
|
246
|
+
filename: "photo.jpg",
|
|
247
|
+
mimeType: "image/jpeg",
|
|
248
|
+
data: "imgdata",
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: "att-doc",
|
|
252
|
+
filename: "doc.pdf",
|
|
253
|
+
mimeType: "application/pdf",
|
|
254
|
+
data: "pdfdata",
|
|
255
|
+
},
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
expect(blocks[0].type).toBe("image");
|
|
259
|
+
expect((blocks[0] as { _attachmentId?: string })._attachmentId).toBe(
|
|
260
|
+
"att-img",
|
|
261
|
+
);
|
|
262
|
+
expect(blocks[1].type).toBe("file");
|
|
263
|
+
expect((blocks[1] as { _attachmentId?: string })._attachmentId).toBe(
|
|
264
|
+
"att-doc",
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("omits the attachment id when the upload has none", async () => {
|
|
269
|
+
const blocks = await attachmentsToContentBlocks([
|
|
270
|
+
{ filename: "photo.jpg", mimeType: "image/jpeg", data: "imgdata" },
|
|
271
|
+
{ filename: "doc.pdf", mimeType: "application/pdf", data: "pdfdata" },
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
expect("_attachmentId" in blocks[0]).toBe(false);
|
|
275
|
+
expect("_attachmentId" in blocks[1]).toBe(false);
|
|
276
|
+
});
|
|
155
277
|
});
|
|
156
278
|
|
|
157
279
|
// ---------------------------------------------------------------------------
|
|
@@ -3,7 +3,7 @@ import { tmpdir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
5
5
|
|
|
6
|
-
import { resolveBundledDir } from "../util/bundled-asset.js";
|
|
6
|
+
import { isBunVirtualPath, resolveBundledDir } from "../util/bundled-asset.js";
|
|
7
7
|
|
|
8
8
|
let tempDir: string;
|
|
9
9
|
|
|
@@ -17,6 +17,13 @@ afterEach(() => {
|
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
describe("resolveBundledDir", () => {
|
|
20
|
+
test("recognizes Bun virtual paths on Unix and Windows", () => {
|
|
21
|
+
expect(isBunVirtualPath("/$bunfs/root/src/config")).toBeTrue();
|
|
22
|
+
expect(isBunVirtualPath("B:\\~BUN\\root")).toBeTrue();
|
|
23
|
+
expect(isBunVirtualPath("B:/~BUN/root/src/config")).toBeTrue();
|
|
24
|
+
expect(isBunVirtualPath("C:\\dev\\vellum-assistant")).toBeFalse();
|
|
25
|
+
});
|
|
26
|
+
|
|
20
27
|
test("source mode: returns join(callerDir, relativePath) when callerDir is a normal path", () => {
|
|
21
28
|
const result = resolveBundledDir(
|
|
22
29
|
"/some/source/path",
|
|
@@ -82,6 +89,20 @@ describe("resolveBundledDir", () => {
|
|
|
82
89
|
expect(result).toBe(join(binDir, "templates"));
|
|
83
90
|
});
|
|
84
91
|
|
|
92
|
+
test("resolves Windows Bun virtual paths beside the executable", () => {
|
|
93
|
+
const binDir = join(tempDir, "bin");
|
|
94
|
+
mkdirSync(join(binDir, "default-plugins"), { recursive: true });
|
|
95
|
+
|
|
96
|
+
process.execPath = join(binDir, "vellum-daemon.exe");
|
|
97
|
+
|
|
98
|
+
const result = resolveBundledDir(
|
|
99
|
+
"B:\\~BUN\\root",
|
|
100
|
+
".",
|
|
101
|
+
"default-plugins",
|
|
102
|
+
);
|
|
103
|
+
expect(result).toBe(join(binDir, "default-plugins"));
|
|
104
|
+
});
|
|
105
|
+
|
|
85
106
|
test("falls back to source path when neither Resources nor execDir have the asset", () => {
|
|
86
107
|
const binDir = join(tempDir, "bin");
|
|
87
108
|
mkdirSync(binDir, { recursive: true });
|
|
@@ -16,7 +16,7 @@ describe("parseCallbackData", () => {
|
|
|
16
16
|
expect(result).not.toBeNull();
|
|
17
17
|
expect(result!.action).toBe(expectedAction);
|
|
18
18
|
expect(result!.requestId).toBe("req-123");
|
|
19
|
-
expect(result!.source).toBe("
|
|
19
|
+
expect(result!.source).toBe("button");
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
test.each<[string, string]>([
|
|
@@ -33,12 +33,19 @@ describe("parseCallbackData", () => {
|
|
|
33
33
|
},
|
|
34
34
|
);
|
|
35
35
|
|
|
36
|
-
test("
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
test("every channel's button press attributes as the button modality", () => {
|
|
37
|
+
for (const channel of ["slack", "telegram", "whatsapp", "discord"]) {
|
|
38
|
+
const result = parseCallbackData("apr:req-789:approve_once", channel);
|
|
39
|
+
expect(result).not.toBeNull();
|
|
40
|
+
expect(result!.action).toBe("approve_once");
|
|
41
|
+
expect(result!.requestId).toBe("req-789");
|
|
42
|
+
expect(result!.source).toBe("button");
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("an in-app surface press attributes as vellum_surface", () => {
|
|
47
|
+
const result = parseCallbackData("apr:req-789:approve_once", "vellum");
|
|
48
|
+
expect(result!.source).toBe("vellum_surface");
|
|
42
49
|
});
|
|
43
50
|
|
|
44
51
|
test("returns null for unknown action", () => {
|
|
@@ -241,7 +241,7 @@ describe("handleChannelDecision", () => {
|
|
|
241
241
|
const mockConv = conversationMocks.get("conv-1") as Conversation;
|
|
242
242
|
const decision: ApprovalDecisionResult = {
|
|
243
243
|
action: "reject",
|
|
244
|
-
source: "
|
|
244
|
+
source: "button",
|
|
245
245
|
};
|
|
246
246
|
|
|
247
247
|
const result = handleChannelDecision("conv-1", decision);
|
|
@@ -261,7 +261,7 @@ describe("handleChannelDecision", () => {
|
|
|
261
261
|
const newerMock = conversationMocks.get("conv-1") as Conversation;
|
|
262
262
|
const decision: ApprovalDecisionResult = {
|
|
263
263
|
action: "approve_once",
|
|
264
|
-
source: "
|
|
264
|
+
source: "button",
|
|
265
265
|
requestId: "req-newer",
|
|
266
266
|
};
|
|
267
267
|
|
|
@@ -280,7 +280,7 @@ describe("handleChannelDecision", () => {
|
|
|
280
280
|
registerPendingConfirmation("req-1", "conv-1", "shell");
|
|
281
281
|
const decision: ApprovalDecisionResult = {
|
|
282
282
|
action: "approve_once",
|
|
283
|
-
source: "
|
|
283
|
+
source: "button",
|
|
284
284
|
requestId: "req-missing",
|
|
285
285
|
};
|
|
286
286
|
|
|
@@ -10,6 +10,7 @@ import { getDb } from "../persistence/db-connection.js";
|
|
|
10
10
|
import { initializeDb } from "../persistence/db-init.js";
|
|
11
11
|
import {
|
|
12
12
|
clearPayload,
|
|
13
|
+
findConversationByProviderMessageId,
|
|
13
14
|
findMessageBySourceId,
|
|
14
15
|
linkMessage,
|
|
15
16
|
recordInbound,
|
|
@@ -122,6 +123,63 @@ describe("channel-delivery-store", () => {
|
|
|
122
123
|
expect(row!.sourceMessageId).toBe("src-42");
|
|
123
124
|
});
|
|
124
125
|
|
|
126
|
+
test("resolves an assistant post by its provider id on a non-Slack channel", () => {
|
|
127
|
+
const chatId = "999000111222333444";
|
|
128
|
+
const minted = recordInbound("discord", chatId, "evt-seed");
|
|
129
|
+
const db = getDb();
|
|
130
|
+
db.insert(messages)
|
|
131
|
+
.values({
|
|
132
|
+
id: "assistant-post-1",
|
|
133
|
+
conversationId: minted.conversationId,
|
|
134
|
+
role: "assistant",
|
|
135
|
+
content: "outbound reply",
|
|
136
|
+
createdAt: Date.now(),
|
|
137
|
+
metadata: JSON.stringify({
|
|
138
|
+
providerMeta: JSON.stringify({
|
|
139
|
+
source: "discord",
|
|
140
|
+
conversationExternalId: chatId,
|
|
141
|
+
messageId: "1234567890123456789",
|
|
142
|
+
additionalMessageIds: ["2234567890123456789"],
|
|
143
|
+
eventKind: "message",
|
|
144
|
+
}),
|
|
145
|
+
}),
|
|
146
|
+
})
|
|
147
|
+
.run();
|
|
148
|
+
|
|
149
|
+
expect(
|
|
150
|
+
findConversationByProviderMessageId(
|
|
151
|
+
"discord",
|
|
152
|
+
chatId,
|
|
153
|
+
"1234567890123456789",
|
|
154
|
+
),
|
|
155
|
+
).toBe(minted.conversationId);
|
|
156
|
+
// A split reply posts several provider messages from one row; any of
|
|
157
|
+
// their ids resolves it.
|
|
158
|
+
expect(
|
|
159
|
+
findConversationByProviderMessageId(
|
|
160
|
+
"discord",
|
|
161
|
+
chatId,
|
|
162
|
+
"2234567890123456789",
|
|
163
|
+
),
|
|
164
|
+
).toBe(minted.conversationId);
|
|
165
|
+
// The scan is scoped to the reaction's own channel address: the same id
|
|
166
|
+
// is invisible from another channel's or another chat's key space.
|
|
167
|
+
expect(
|
|
168
|
+
findConversationByProviderMessageId(
|
|
169
|
+
"telegram",
|
|
170
|
+
chatId,
|
|
171
|
+
"1234567890123456789",
|
|
172
|
+
),
|
|
173
|
+
).toBeNull();
|
|
174
|
+
expect(
|
|
175
|
+
findConversationByProviderMessageId(
|
|
176
|
+
"discord",
|
|
177
|
+
"other-chat",
|
|
178
|
+
"1234567890123456789",
|
|
179
|
+
),
|
|
180
|
+
).toBeNull();
|
|
181
|
+
});
|
|
182
|
+
|
|
125
183
|
test("same chat on same channel reuses the same conversation", () => {
|
|
126
184
|
const r1 = recordInbound("telegram", "chat-1", "msg-1");
|
|
127
185
|
const r2 = recordInbound("telegram", "chat-1", "msg-2");
|
|
@@ -243,6 +243,7 @@ describe("channel inbound disk pressure gate", () => {
|
|
|
243
243
|
content: "reaction:thumbsup",
|
|
244
244
|
actorExternalId: "slack-user-1",
|
|
245
245
|
callbackData: "reaction:thumbsup",
|
|
246
|
+
sourceMetadata: { messageId: "1700000000.1" },
|
|
246
247
|
replyCallbackUrl: "https://gateway.test/deliver/slack",
|
|
247
248
|
}),
|
|
248
249
|
);
|
|
@@ -174,9 +174,9 @@ describe("slack remote probe (auth.test)", () => {
|
|
|
174
174
|
|
|
175
175
|
/**
|
|
176
176
|
* Slack can install an app with a fraction of the manifest's scopes while
|
|
177
|
-
* auth.test still succeeds
|
|
178
|
-
*
|
|
179
|
-
*
|
|
177
|
+
* auth.test still succeeds, leaving the first real API call to fail with
|
|
178
|
+
* missing_scope. auth.test passing is therefore not evidence the install is
|
|
179
|
+
* usable.
|
|
180
180
|
*/
|
|
181
181
|
describe("slack auth.test response validation", () => {
|
|
182
182
|
test("reports an unexpected shape instead of trusting the field types", async () => {
|
|
@@ -123,18 +123,6 @@ mock.module("../persistence/attachments-store.js", () => ({
|
|
|
123
123
|
linkAttachmentToMessage: () => {},
|
|
124
124
|
}));
|
|
125
125
|
|
|
126
|
-
mock.module("../memory/retriever.js", () => ({
|
|
127
|
-
buildMemoryRecall: async () => ({
|
|
128
|
-
enabled: false,
|
|
129
|
-
degraded: false,
|
|
130
|
-
injectedText: "",
|
|
131
|
-
semanticHits: 0,
|
|
132
|
-
injectedTokens: 0,
|
|
133
|
-
latencyMs: 0,
|
|
134
|
-
}),
|
|
135
|
-
injectMemoryRecallAsUserBlock: (msgs: Message[]) => msgs,
|
|
136
|
-
}));
|
|
137
|
-
|
|
138
126
|
// Per-test compaction result — set before calling forceCompact().
|
|
139
127
|
let mockCompactResult: ContextWindowResult = {
|
|
140
128
|
messages: [],
|
|
@@ -149,10 +149,12 @@ describe("compactor records llm_request_logs with call_site=compactionAgent", ()
|
|
|
149
149
|
});
|
|
150
150
|
|
|
151
151
|
test("successful compaction call stamps call_site = compactionAgent", async () => {
|
|
152
|
-
await runAssistantDrivenCompaction(args(makeProvider()));
|
|
152
|
+
const result = await runAssistantDrivenCompaction(args(makeProvider()));
|
|
153
153
|
|
|
154
154
|
expect(recordRequestLogCalls.length).toBe(1);
|
|
155
155
|
expect(recordRequestLogCalls[0]!.callSite).toBe("compactionAgent");
|
|
156
|
+
expect(result.summaryCallSite).toBe("compactionAgent");
|
|
157
|
+
expect(result.summaryResolutionCallSite).toBe("mainAgent");
|
|
156
158
|
expect(recordRequestLogCalls[0]!.conversationId).toBe(
|
|
157
159
|
"conv-compaction-log-1",
|
|
158
160
|
);
|
|
@@ -228,6 +230,10 @@ describe("compaction result reports what actually served the summary", () => {
|
|
|
228
230
|
expect(result.summaryActualInferenceProfile).toBe("backupProfile");
|
|
229
231
|
// The primary's resolution is still reported, just outranked downstream.
|
|
230
232
|
expect(result.summaryOverrideProfile).toBe("primaryProfile");
|
|
233
|
+
// Request log and usage attribution share the observability call site.
|
|
234
|
+
expect(result.summaryCallSite).toBe("compactionAgent");
|
|
235
|
+
expect(result.summaryResolutionCallSite).toBe("mainAgent");
|
|
236
|
+
expect(recordRequestLogCalls[0]!.callSite).toBe("compactionAgent");
|
|
231
237
|
// Same value the request log recorded: one source of truth, not two.
|
|
232
238
|
expect(recordRequestLogCalls[0]!.provider).toBe("anthropic");
|
|
233
239
|
});
|
|
@@ -125,6 +125,21 @@ describe("buildRetainedImageBlocks corrupt-bytes gate", () => {
|
|
|
125
125
|
expect(blocks).toHaveLength(0);
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
test("a retained block carries the attachment id it rehydrated from", async () => {
|
|
129
|
+
// The rebuilt block is inline bytes, so this id is the only handle left on
|
|
130
|
+
// the row it came from. Camera-frame retention matches on it: without it a
|
|
131
|
+
// frame compaction chose to keep would be invisible to every later pass.
|
|
132
|
+
const conv = createConversation();
|
|
133
|
+
await addImageMessage(conv.id, "good.png", PNG_1X1_BASE64);
|
|
134
|
+
const manifest = collectImageManifest(conv.id, "guardian");
|
|
135
|
+
|
|
136
|
+
const { blocks } = await buildRetainedImageBlocks(["good.png"], manifest);
|
|
137
|
+
|
|
138
|
+
expect(blocks).toHaveLength(1);
|
|
139
|
+
expect(blocks[0]._attachmentId).toBe(manifest[0].attachmentId);
|
|
140
|
+
expect(blocks[0]._attachmentId).toBeTruthy();
|
|
141
|
+
});
|
|
142
|
+
|
|
128
143
|
test("unresolvable filenames still land in missing", async () => {
|
|
129
144
|
const conv = createConversation();
|
|
130
145
|
await addImageMessage(conv.id, "good.png", PNG_1X1_BASE64);
|
|
@@ -811,6 +811,40 @@ describe("AssistantConfigSchema", () => {
|
|
|
811
811
|
expect(result.success).toBe(false);
|
|
812
812
|
});
|
|
813
813
|
|
|
814
|
+
test("applies sight defaults", () => {
|
|
815
|
+
expect(AssistantConfigSchema.parse({}).sight).toEqual({
|
|
816
|
+
keepLatestFrames: 2,
|
|
817
|
+
});
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
test("accepts a custom sight.keepLatestFrames", () => {
|
|
821
|
+
const result = AssistantConfigSchema.parse({
|
|
822
|
+
sight: { keepLatestFrames: 4 },
|
|
823
|
+
});
|
|
824
|
+
expect(result.sight.keepLatestFrames).toBe(4);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
test("keeps an out-of-range sight.keepLatestFrames for the reader to clamp", () => {
|
|
828
|
+
// Rejecting it would reset the key to the default, which is fewer frames
|
|
829
|
+
// than either the config or the guardrail asked for. The clamp lives at
|
|
830
|
+
// read (`resolveSightKeepLatestFrames`).
|
|
831
|
+
const result = AssistantConfigSchema.parse({
|
|
832
|
+
sight: { keepLatestFrames: 99 },
|
|
833
|
+
});
|
|
834
|
+
expect(result.sight.keepLatestFrames).toBe(99);
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
test("rejects a non-integer sight.keepLatestFrames", () => {
|
|
838
|
+
expect(
|
|
839
|
+
AssistantConfigSchema.safeParse({ sight: { keepLatestFrames: 2.5 } })
|
|
840
|
+
.success,
|
|
841
|
+
).toBe(false);
|
|
842
|
+
expect(
|
|
843
|
+
AssistantConfigSchema.safeParse({ sight: { keepLatestFrames: "two" } })
|
|
844
|
+
.success,
|
|
845
|
+
).toBe(false);
|
|
846
|
+
});
|
|
847
|
+
|
|
814
848
|
// ── commitMessageLLM config ──────────────────────────────────────────
|
|
815
849
|
|
|
816
850
|
test("default commitMessageLLM values are correct", () => {
|
|
@@ -937,6 +971,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
937
971
|
mode: "open-mic",
|
|
938
972
|
vad: {
|
|
939
973
|
speechEnergyThreshold: 800,
|
|
974
|
+
noiseFloorMargin: 3,
|
|
940
975
|
silenceThresholdMs: 1200,
|
|
941
976
|
maxTurnDurationMs: 30000,
|
|
942
977
|
bargeInMinSpeechMs: 250,
|
|
@@ -39,6 +39,24 @@ function channelCount(type: string, address: string): number {
|
|
|
39
39
|
return row.n;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
function contactExists(contactId: string): boolean {
|
|
43
|
+
// bun:sqlite .get() returns null (not undefined) when no row matches.
|
|
44
|
+
const row = getSqlite()
|
|
45
|
+
.query("SELECT 1 AS one FROM contacts WHERE id = ?")
|
|
46
|
+
.get(contactId) as { one: number } | null;
|
|
47
|
+
return row !== null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Contacts parenting zero channels: the guardian-duplicate shape. */
|
|
51
|
+
function channelLessContactCount(): number {
|
|
52
|
+
const row = getSqlite()
|
|
53
|
+
.query(
|
|
54
|
+
"SELECT COUNT(*) AS n FROM contacts c WHERE NOT EXISTS (SELECT 1 FROM contact_channels ch WHERE ch.contact_id = c.id)",
|
|
55
|
+
)
|
|
56
|
+
.get() as { n: number };
|
|
57
|
+
return row.n;
|
|
58
|
+
}
|
|
59
|
+
|
|
42
60
|
function workspaceDir(): string {
|
|
43
61
|
const dir = process.env.VELLUM_WORKSPACE_DIR;
|
|
44
62
|
if (!dir) {
|
|
@@ -112,6 +130,34 @@ describe("identity-mirror faithful-replica semantics", () => {
|
|
|
112
130
|
expect(result?.contact.userFile).toBeNull();
|
|
113
131
|
});
|
|
114
132
|
|
|
133
|
+
test("an op carrying the gateway channel id adopts it onto a divergent legacy row", () => {
|
|
134
|
+
upsertContactChannel({
|
|
135
|
+
sourceChannel: "slack",
|
|
136
|
+
externalUserId: "UADOPTID",
|
|
137
|
+
displayName: "Alice",
|
|
138
|
+
contactId: "co-adopt-id",
|
|
139
|
+
channelId: "legacy-ch",
|
|
140
|
+
refreshDisplayName: true,
|
|
141
|
+
userFileOnCreate: null,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// The gateway's row for the same identity lives under a different UUID;
|
|
145
|
+
// the mirror adopts it so both stores key the channel identically.
|
|
146
|
+
upsertContactChannel({
|
|
147
|
+
sourceChannel: "slack",
|
|
148
|
+
externalUserId: "UADOPTID",
|
|
149
|
+
displayName: "Alice",
|
|
150
|
+
contactId: "co-adopt-id",
|
|
151
|
+
channelId: "gw-ch-aligned",
|
|
152
|
+
refreshDisplayName: true,
|
|
153
|
+
userFileOnCreate: null,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(channelOwnerById("gw-ch-aligned")).toBe("co-adopt-id");
|
|
157
|
+
expect(channelOwnerById("legacy-ch")).toBeUndefined();
|
|
158
|
+
expect(channelCount("slack", "UADOPTID")).toBe(1);
|
|
159
|
+
});
|
|
160
|
+
|
|
115
161
|
test("Finding B: an explicit (gateway-minted) channel id is reused verbatim", () => {
|
|
116
162
|
const result = upsertContactChannel({
|
|
117
163
|
sourceChannel: "slack",
|
|
@@ -215,10 +261,102 @@ describe("mirror channel-reparenting race semantics", () => {
|
|
|
215
261
|
reassignConflictingChannels: false,
|
|
216
262
|
});
|
|
217
263
|
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
264
|
+
// Ownership never moves without reassign and no duplicate channel is
|
|
265
|
+
// created; the row's id follows the latest gateway-sourced op (id
|
|
266
|
+
// adoption), which is what keeps the two stores' keys aligned.
|
|
267
|
+
expect(channelOwnerById("gw-ch-race-2")).toBe("co-race-1");
|
|
268
|
+
expect(channelOwnerById("gw-ch-race-1")).toBeUndefined();
|
|
221
269
|
expect(channelCount("slack", "URACE")).toBe(1);
|
|
270
|
+
// The losing seed adopts contact #1 rather than minting a channel-less
|
|
271
|
+
// contact under its own id.
|
|
272
|
+
expect(contactExists("co-race-2")).toBe(false);
|
|
273
|
+
expect(channelLessContactCount()).toBe(0);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("adoption and re-seen seeds never clobber curated record fields", () => {
|
|
277
|
+
// First-seen bot sender: classification and notes land on CREATE.
|
|
278
|
+
const created = upsertContactChannel({
|
|
279
|
+
sourceChannel: "slack",
|
|
280
|
+
externalUserId: "UCURATED",
|
|
281
|
+
externalChatId: "DCURATED",
|
|
282
|
+
displayName: "Peer Assistant",
|
|
283
|
+
contactId: "co-curated",
|
|
284
|
+
channelId: "gw-ch-curated",
|
|
285
|
+
contactType: "assistant",
|
|
286
|
+
notes: "Automated Slack bot",
|
|
287
|
+
refreshDisplayName: true,
|
|
288
|
+
userFileOnCreate: null,
|
|
289
|
+
reassignConflictingChannels: false,
|
|
290
|
+
});
|
|
291
|
+
expect(created?.contact.contactType).toBe("assistant");
|
|
292
|
+
expect(created?.contact.notes).toBe("Automated Slack bot");
|
|
293
|
+
|
|
294
|
+
// A later seed for the same identity (mirror row present) carries record
|
|
295
|
+
// fields for gap recovery, but they are create-only: the stored
|
|
296
|
+
// classification and notes stand.
|
|
297
|
+
const reseen = upsertContactChannel({
|
|
298
|
+
sourceChannel: "slack",
|
|
299
|
+
externalUserId: "UCURATED",
|
|
300
|
+
contactId: "co-curated",
|
|
301
|
+
displayName: "Peer Assistant",
|
|
302
|
+
contactType: "human",
|
|
303
|
+
notes: "seed-refresh notes that must not land",
|
|
304
|
+
refreshDisplayName: true,
|
|
305
|
+
userFileOnCreate: null,
|
|
306
|
+
reassignConflictingChannels: false,
|
|
307
|
+
});
|
|
308
|
+
expect(reseen?.contact.contactType).toBe("assistant");
|
|
309
|
+
expect(reseen?.contact.notes).toBe("Automated Slack bot");
|
|
310
|
+
|
|
311
|
+
// Adoption (a create-shaped op under a foreign id for the same identity)
|
|
312
|
+
// is equally forbidden from touching the adopted contact's record fields.
|
|
313
|
+
const adopted = upsertContactChannel({
|
|
314
|
+
sourceChannel: "slack",
|
|
315
|
+
externalUserId: "UCURATED",
|
|
316
|
+
contactId: "co-curated-foreign",
|
|
317
|
+
displayName: "Peer Assistant",
|
|
318
|
+
contactType: "human",
|
|
319
|
+
notes: "adoption notes that must not land",
|
|
320
|
+
refreshDisplayName: true,
|
|
321
|
+
userFileOnCreate: null,
|
|
322
|
+
reassignConflictingChannels: false,
|
|
323
|
+
});
|
|
324
|
+
expect(adopted?.contact.id).toBe("co-curated");
|
|
325
|
+
expect(adopted?.contact.contactType).toBe("assistant");
|
|
326
|
+
expect(adopted?.contact.notes).toBe("Automated Slack bot");
|
|
327
|
+
expect(contactExists("co-curated-foreign")).toBe(false);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("explicit-id create over a conflicting channel adopts the owner instead of minting an empty contact", () => {
|
|
331
|
+
upsertContactChannel({
|
|
332
|
+
sourceChannel: "slack",
|
|
333
|
+
externalUserId: "UADOPT",
|
|
334
|
+
externalChatId: "DADOPT",
|
|
335
|
+
displayName: "Owner",
|
|
336
|
+
contactId: "co-adopt-owner",
|
|
337
|
+
channelId: "gw-ch-adopt",
|
|
338
|
+
refreshDisplayName: true,
|
|
339
|
+
userFileOnCreate: null,
|
|
340
|
+
reassignConflictingChannels: false,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// A divergence-shaped write: the gateway hands down a contact id the
|
|
344
|
+
// mirror has never seen, for an identity the mirror already parents.
|
|
345
|
+
const result = upsertContactChannel({
|
|
346
|
+
sourceChannel: "slack",
|
|
347
|
+
externalUserId: "UADOPT",
|
|
348
|
+
displayName: "Owner Renamed",
|
|
349
|
+
contactId: "co-adopt-foreign",
|
|
350
|
+
refreshDisplayName: true,
|
|
351
|
+
userFileOnCreate: null,
|
|
352
|
+
reassignConflictingChannels: false,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
expect(result?.contact.id).toBe("co-adopt-owner");
|
|
356
|
+
expect(result?.contact.displayName).toBe("Owner Renamed");
|
|
357
|
+
expect(channelOwnerById("gw-ch-adopt")).toBe("co-adopt-owner");
|
|
358
|
+
expect(contactExists("co-adopt-foreign")).toBe(false);
|
|
359
|
+
expect(channelLessContactCount()).toBe(0);
|
|
222
360
|
});
|
|
223
361
|
|
|
224
362
|
test("INVITE-binding path (reassignConflictingChannels:true) DOES reparent", () => {
|
|
@@ -83,19 +83,6 @@ mock.module("../persistence/conversation-queries.js", () => ({
|
|
|
83
83
|
listConversations: () => [],
|
|
84
84
|
}));
|
|
85
85
|
|
|
86
|
-
mock.module("../memory/retriever.js", () => ({
|
|
87
|
-
buildMemoryRecall: async () => ({
|
|
88
|
-
enabled: false,
|
|
89
|
-
degraded: false,
|
|
90
|
-
injectedText: "",
|
|
91
|
-
|
|
92
|
-
semanticHits: 0,
|
|
93
|
-
injectedTokens: 0,
|
|
94
|
-
latencyMs: 0,
|
|
95
|
-
}),
|
|
96
|
-
injectMemoryRecallAsUserBlock: (msgs: Message[]) => msgs,
|
|
97
|
-
}));
|
|
98
|
-
|
|
99
86
|
mock.module("../plugins/defaults/compaction/window-manager.js", () => ({
|
|
100
87
|
ContextWindowManager: class {
|
|
101
88
|
estimateInputTokens() {
|
|
@@ -165,18 +165,6 @@ mock.module("../persistence/conversation-disk-view.js", () => ({
|
|
|
165
165
|
rebuildConversationDiskViewFromDbState: () => {},
|
|
166
166
|
}));
|
|
167
167
|
|
|
168
|
-
mock.module("../memory/retriever.js", () => ({
|
|
169
|
-
buildMemoryRecall: async () => ({
|
|
170
|
-
enabled: false,
|
|
171
|
-
degraded: false,
|
|
172
|
-
injectedText: "",
|
|
173
|
-
semanticHits: 0,
|
|
174
|
-
injectedTokens: 0,
|
|
175
|
-
latencyMs: 0,
|
|
176
|
-
}),
|
|
177
|
-
injectMemoryRecallAsUserBlock: (msgs: Message[]) => msgs,
|
|
178
|
-
}));
|
|
179
|
-
|
|
180
168
|
mock.module("../apps/app-store.js", () => ({
|
|
181
169
|
getApp: () => null,
|
|
182
170
|
listAppFiles: () => [],
|