@vellumai/assistant 0.10.0 → 0.10.1-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 +36 -37
- package/bun.lock +3 -0
- package/docs/workflows.md +12 -7
- package/eslint-rules/cli-no-daemon-internals.js +6 -0
- package/node_modules/@slack/types/LICENSE +23 -0
- package/node_modules/@slack/types/README.md +32 -0
- package/node_modules/@slack/types/dist/block-kit/block-elements.d.ts +953 -0
- package/node_modules/@slack/types/dist/block-kit/block-elements.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/block-elements.js +4 -0
- package/node_modules/@slack/types/dist/block-kit/block-elements.js.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/blocks.d.ts +474 -0
- package/node_modules/@slack/types/dist/block-kit/blocks.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/blocks.js +3 -0
- package/node_modules/@slack/types/dist/block-kit/blocks.js.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/composition-objects.d.ts +237 -0
- package/node_modules/@slack/types/dist/block-kit/composition-objects.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/composition-objects.js +4 -0
- package/node_modules/@slack/types/dist/block-kit/composition-objects.js.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/extensions.d.ts +88 -0
- package/node_modules/@slack/types/dist/block-kit/extensions.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/extensions.js +3 -0
- package/node_modules/@slack/types/dist/block-kit/extensions.js.map +1 -0
- package/node_modules/@slack/types/dist/calls.d.ts +26 -0
- package/node_modules/@slack/types/dist/calls.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/calls.js +6 -0
- package/node_modules/@slack/types/dist/calls.js.map +1 -0
- package/node_modules/@slack/types/dist/chunk.d.ts +52 -0
- package/node_modules/@slack/types/dist/chunk.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/chunk.js +3 -0
- package/node_modules/@slack/types/dist/chunk.js.map +1 -0
- package/node_modules/@slack/types/dist/common/bot-profile.d.ts +12 -0
- package/node_modules/@slack/types/dist/common/bot-profile.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/common/bot-profile.js +3 -0
- package/node_modules/@slack/types/dist/common/bot-profile.js.map +1 -0
- package/node_modules/@slack/types/dist/common/status-emoji-display-info.d.ts +6 -0
- package/node_modules/@slack/types/dist/common/status-emoji-display-info.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/common/status-emoji-display-info.js +3 -0
- package/node_modules/@slack/types/dist/common/status-emoji-display-info.js.map +1 -0
- package/node_modules/@slack/types/dist/dialog.d.ts +36 -0
- package/node_modules/@slack/types/dist/dialog.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/dialog.js +3 -0
- package/node_modules/@slack/types/dist/dialog.js.map +1 -0
- package/node_modules/@slack/types/dist/events/app.d.ts +204 -0
- package/node_modules/@slack/types/dist/events/app.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/app.js +3 -0
- package/node_modules/@slack/types/dist/events/app.js.map +1 -0
- package/node_modules/@slack/types/dist/events/assistant.d.ts +29 -0
- package/node_modules/@slack/types/dist/events/assistant.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/assistant.js +3 -0
- package/node_modules/@slack/types/dist/events/assistant.js.map +1 -0
- package/node_modules/@slack/types/dist/events/call.d.ts +8 -0
- package/node_modules/@slack/types/dist/events/call.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/call.js +3 -0
- package/node_modules/@slack/types/dist/events/call.js.map +1 -0
- package/node_modules/@slack/types/dist/events/channel.d.ts +85 -0
- package/node_modules/@slack/types/dist/events/channel.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/channel.js +3 -0
- package/node_modules/@slack/types/dist/events/channel.js.map +1 -0
- package/node_modules/@slack/types/dist/events/dnd.d.ts +24 -0
- package/node_modules/@slack/types/dist/events/dnd.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/dnd.js +3 -0
- package/node_modules/@slack/types/dist/events/dnd.js.map +1 -0
- package/node_modules/@slack/types/dist/events/email.d.ts +6 -0
- package/node_modules/@slack/types/dist/events/email.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/email.js +3 -0
- package/node_modules/@slack/types/dist/events/email.js.map +1 -0
- package/node_modules/@slack/types/dist/events/emoji.d.ts +11 -0
- package/node_modules/@slack/types/dist/events/emoji.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/emoji.js +3 -0
- package/node_modules/@slack/types/dist/events/emoji.js.map +1 -0
- package/node_modules/@slack/types/dist/events/entity-details-requested.d.ts +21 -0
- package/node_modules/@slack/types/dist/events/entity-details-requested.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/entity-details-requested.js +3 -0
- package/node_modules/@slack/types/dist/events/entity-details-requested.js.map +1 -0
- package/node_modules/@slack/types/dist/events/file.d.ts +60 -0
- package/node_modules/@slack/types/dist/events/file.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/file.js +4 -0
- package/node_modules/@slack/types/dist/events/file.js.map +1 -0
- package/node_modules/@slack/types/dist/events/function.d.ts +33 -0
- package/node_modules/@slack/types/dist/events/function.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/function.js +3 -0
- package/node_modules/@slack/types/dist/events/function.js.map +1 -0
- package/node_modules/@slack/types/dist/events/grid-migration.d.ts +9 -0
- package/node_modules/@slack/types/dist/events/grid-migration.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/grid-migration.js +3 -0
- package/node_modules/@slack/types/dist/events/grid-migration.js.map +1 -0
- package/node_modules/@slack/types/dist/events/group.d.ts +55 -0
- package/node_modules/@slack/types/dist/events/group.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/group.js +3 -0
- package/node_modules/@slack/types/dist/events/group.js.map +1 -0
- package/node_modules/@slack/types/dist/events/im.d.ts +26 -0
- package/node_modules/@slack/types/dist/events/im.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/im.js +3 -0
- package/node_modules/@slack/types/dist/events/im.js.map +1 -0
- package/node_modules/@slack/types/dist/events/index.d.ts +60 -0
- package/node_modules/@slack/types/dist/events/index.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/index.js +43 -0
- package/node_modules/@slack/types/dist/events/index.js.map +1 -0
- package/node_modules/@slack/types/dist/events/invite.d.ts +20 -0
- package/node_modules/@slack/types/dist/events/invite.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/invite.js +3 -0
- package/node_modules/@slack/types/dist/events/invite.js.map +1 -0
- package/node_modules/@slack/types/dist/events/link-shared.d.ts +16 -0
- package/node_modules/@slack/types/dist/events/link-shared.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/link-shared.js +3 -0
- package/node_modules/@slack/types/dist/events/link-shared.js.map +1 -0
- package/node_modules/@slack/types/dist/events/member.d.ts +19 -0
- package/node_modules/@slack/types/dist/events/member.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/member.js +3 -0
- package/node_modules/@slack/types/dist/events/member.js.map +1 -0
- package/node_modules/@slack/types/dist/events/message-metadata.d.ts +38 -0
- package/node_modules/@slack/types/dist/events/message-metadata.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/message-metadata.js +3 -0
- package/node_modules/@slack/types/dist/events/message-metadata.js.map +1 -0
- package/node_modules/@slack/types/dist/events/message.d.ts +306 -0
- package/node_modules/@slack/types/dist/events/message.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/message.js +3 -0
- package/node_modules/@slack/types/dist/events/message.js.map +1 -0
- package/node_modules/@slack/types/dist/events/pin.d.ts +60 -0
- package/node_modules/@slack/types/dist/events/pin.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/pin.js +3 -0
- package/node_modules/@slack/types/dist/events/pin.js.map +1 -0
- package/node_modules/@slack/types/dist/events/reaction.d.ts +23 -0
- package/node_modules/@slack/types/dist/events/reaction.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/reaction.js +3 -0
- package/node_modules/@slack/types/dist/events/reaction.js.map +1 -0
- package/node_modules/@slack/types/dist/events/shared-channel.d.ts +134 -0
- package/node_modules/@slack/types/dist/events/shared-channel.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/shared-channel.js +3 -0
- package/node_modules/@slack/types/dist/events/shared-channel.js.map +1 -0
- package/node_modules/@slack/types/dist/events/star.d.ts +13 -0
- package/node_modules/@slack/types/dist/events/star.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/star.js +3 -0
- package/node_modules/@slack/types/dist/events/star.js.map +1 -0
- package/node_modules/@slack/types/dist/events/steps-from-apps.d.ts +82 -0
- package/node_modules/@slack/types/dist/events/steps-from-apps.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/steps-from-apps.js +3 -0
- package/node_modules/@slack/types/dist/events/steps-from-apps.js.map +1 -0
- package/node_modules/@slack/types/dist/events/subteam.d.ts +66 -0
- package/node_modules/@slack/types/dist/events/subteam.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/subteam.js +3 -0
- package/node_modules/@slack/types/dist/events/subteam.js.map +1 -0
- package/node_modules/@slack/types/dist/events/team.d.ts +99 -0
- package/node_modules/@slack/types/dist/events/team.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/team.js +3 -0
- package/node_modules/@slack/types/dist/events/team.js.map +1 -0
- package/node_modules/@slack/types/dist/events/token.d.ts +8 -0
- package/node_modules/@slack/types/dist/events/token.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/token.js +3 -0
- package/node_modules/@slack/types/dist/events/token.js.map +1 -0
- package/node_modules/@slack/types/dist/events/user.d.ts +313 -0
- package/node_modules/@slack/types/dist/events/user.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/user.js +3 -0
- package/node_modules/@slack/types/dist/events/user.js.map +1 -0
- package/node_modules/@slack/types/dist/index.d.ts +12 -0
- package/node_modules/@slack/types/dist/index.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/index.js +28 -0
- package/node_modules/@slack/types/dist/index.js.map +1 -0
- package/node_modules/@slack/types/dist/message-attachments.d.ts +171 -0
- package/node_modules/@slack/types/dist/message-attachments.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/message-attachments.js +3 -0
- package/node_modules/@slack/types/dist/message-attachments.js.map +1 -0
- package/node_modules/@slack/types/dist/message-metadata.d.ts +281 -0
- package/node_modules/@slack/types/dist/message-metadata.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/message-metadata.js +27 -0
- package/node_modules/@slack/types/dist/message-metadata.js.map +1 -0
- package/node_modules/@slack/types/dist/views.d.ts +71 -0
- package/node_modules/@slack/types/dist/views.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/views.js +3 -0
- package/node_modules/@slack/types/dist/views.js.map +1 -0
- package/node_modules/@slack/types/package.json +47 -0
- package/node_modules/@vellumai/gateway-client/bun.lock +3 -0
- package/node_modules/@vellumai/gateway-client/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +69 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +65 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +162 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +8 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +4 -2
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +3 -2
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +78 -0
- package/openapi.yaml +345 -18
- package/package.json +2 -1
- package/scripts/memory-inspect.ts +24 -14
- package/src/__tests__/access-request-seed-content-blocks.test.ts +83 -103
- package/src/__tests__/activation-early-marking.test.ts +1 -1
- package/src/__tests__/actor-token-service.test.ts +3 -3
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +1 -40
- package/src/__tests__/agent-loop-compaction-events.test.ts +0 -1
- package/src/__tests__/agent-loop-compaction-strip.test.ts +0 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +0 -1
- package/src/__tests__/agent-loop-pushes-post-hook-prompt.test.ts +306 -0
- package/src/__tests__/agent-loop-regrowth-guard.test.ts +0 -1
- package/src/__tests__/agent-loop.test.ts +3 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
- package/src/__tests__/anthropic-provider.test.ts +143 -9
- package/src/__tests__/app-builder-skill-instructions.test.ts +47 -5
- package/src/__tests__/app-conversation-ids-backfill.test.ts +1 -1
- package/src/__tests__/app-source-watcher.test.ts +30 -10
- package/src/__tests__/approval-cascade.test.ts +6 -0
- package/src/__tests__/approval-interception-trust-gates.test.ts +151 -0
- package/src/__tests__/approval-primitive.test.ts +1 -1
- package/src/__tests__/approval-routes-http.test.ts +1 -1
- package/src/__tests__/assistant-attachments.test.ts +155 -0
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +2 -4
- package/src/__tests__/assistant-events-sse-hardening.test.ts +1 -1
- package/src/__tests__/assistant-events-sse-shed.test.ts +1 -1
- package/src/__tests__/attachment-upload-trusted-source.test.ts +13 -8
- package/src/__tests__/attachments-store.test.ts +1 -1
- package/src/__tests__/audit-log-rotation.test.ts +50 -54
- package/src/__tests__/auth-fallback-events-store.test.ts +1 -1
- package/src/__tests__/auto-analysis-end-to-end.test.ts +9 -14
- package/src/__tests__/background-shell-bash.test.ts +4 -1
- package/src/__tests__/background-shell-host-bash.test.ts +17 -3
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -0
- package/src/__tests__/call-controller.test.ts +1 -1
- package/src/__tests__/call-conversation-messages.test.ts +1 -1
- package/src/__tests__/call-domain.test.ts +1 -1
- package/src/__tests__/call-pointer-messages.test.ts +3 -4
- package/src/__tests__/call-recovery.test.ts +1 -1
- package/src/__tests__/call-routes-http.test.ts +1 -1
- package/src/__tests__/call-store.test.ts +1 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
- package/src/__tests__/canonical-guardian-store.test.ts +24 -1
- package/src/__tests__/channel-approval-routes.test.ts +73 -1119
- package/src/__tests__/channel-delivery-store.test.ts +1 -1
- package/src/__tests__/channel-guardian.test.ts +265 -641
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +1 -2
- package/src/__tests__/channel-retry-sweep.test.ts +1 -1
- package/src/__tests__/compaction-events.test.ts +6 -0
- package/src/__tests__/compaction-trail-store.test.ts +6 -5
- package/src/__tests__/compaction.benchmark.test.ts +0 -1
- package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
- package/src/__tests__/config-loader-backfill.test.ts +183 -51
- package/src/__tests__/config-schema.test.ts +34 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -2
- package/src/__tests__/contact-store-user-file.test.ts +2 -2
- package/src/__tests__/contacts-relay-reads.test.ts +409 -0
- package/src/__tests__/contacts-tools.test.ts +4 -4
- package/src/__tests__/contacts-write.test.ts +1 -2
- package/src/__tests__/context-search-conversations-source.test.ts +1 -1
- package/src/__tests__/context-window-manager-compact-retry.test.ts +6 -2
- package/src/__tests__/context-window-manager-overflow-rung.test.ts +6 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop.test.ts +3 -0
- package/src/__tests__/conversation-attachments.test.ts +2 -5
- package/src/__tests__/conversation-attention-store.test.ts +1 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +1 -2
- package/src/__tests__/conversation-clear-safety.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +6 -0
- package/src/__tests__/conversation-crud-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +12 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +1 -1
- package/src/__tests__/conversation-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +10 -8
- package/src/__tests__/conversation-fork-retrospective.test.ts +250 -0
- package/src/__tests__/conversation-fork-route.test.ts +1 -1
- package/src/__tests__/conversation-inference-profile-list.test.ts +1 -1
- package/src/__tests__/conversation-inference-profile-route.test.ts +1 -1
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-lifecycle.test.ts +117 -0
- package/src/__tests__/conversation-list-source.test.ts +3 -3
- package/src/__tests__/conversation-process-callsite.test.ts +6 -14
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-queue.test.ts +6 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +115 -12
- package/src/__tests__/conversation-slash-queue.test.ts +6 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
- package/src/__tests__/conversation-speed-override.test.ts +6 -0
- package/src/__tests__/conversation-starter-routes.test.ts +5 -5
- package/src/__tests__/conversation-store.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -1
- package/src/__tests__/conversation-sync-tags.test.ts +1 -1
- package/src/__tests__/conversation-usage.test.ts +1 -1
- package/src/__tests__/conversation-wipe.test.ts +9 -8
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/conversations-import-system-filter.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +17 -0
- package/src/__tests__/credential-security-invariants.test.ts +0 -1
- package/src/__tests__/db-acp-history.test.ts +2 -2
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +5 -7
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +6 -7
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +5 -10
- package/src/__tests__/db-migration-rollback.test.ts +129 -39
- package/src/__tests__/db-proxy-transaction.test.ts +1 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +0 -11
- package/src/__tests__/db-test-helpers.ts +36 -19
- package/src/__tests__/delete-propagation.test.ts +1 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +26 -8
- package/src/__tests__/disk-pressure-tools.test.ts +41 -1
- package/src/__tests__/dm-backfill.test.ts +1 -1
- package/src/__tests__/drop-capability-card-state-migration.test.ts +0 -8
- package/src/__tests__/edit-propagation.test.ts +1 -1
- package/src/__tests__/emit-signal-routing-intent.test.ts +83 -0
- package/src/__tests__/empty-response-hook.test.ts +42 -0
- package/src/__tests__/events-client-registration.test.ts +1 -1
- package/src/__tests__/followup-tools.test.ts +1 -1
- package/src/__tests__/gemini-count-tokens.test.ts +70 -0
- package/src/__tests__/guardian-action-sweep.test.ts +9 -2
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-card-withdrawal.test.ts +1 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +1 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -1
- package/src/__tests__/guardian-outbound-http.test.ts +7 -12
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
- package/src/__tests__/guardian-routing-state.test.ts +1 -2
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
- package/src/__tests__/headless-browser-mode.test.ts +2 -2
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
- package/src/__tests__/heartbeat-service.test.ts +6 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +98 -0
- package/src/__tests__/http-conversation-lineage.test.ts +1 -1
- package/src/__tests__/image-recovery-hook.test.ts +1 -1
- package/src/__tests__/inbound-invite-redemption.test.ts +1 -2
- package/src/__tests__/inbound-trust-verdict.test.ts +254 -0
- package/src/__tests__/inference-profile-reaper.test.ts +1 -1
- package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
- package/src/__tests__/inference-profile-session-ipc.test.ts +1 -1
- package/src/__tests__/injector-chain.test.ts +1 -1
- package/src/__tests__/injector-disk-pressure.test.ts +11 -6
- package/src/__tests__/internal-telemetry-routes.test.ts +1 -1
- package/src/__tests__/invite-redemption-service.test.ts +244 -43
- package/src/__tests__/invite-routes-http.test.ts +35 -186
- package/src/__tests__/invite-service-ipc.test.ts +287 -0
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +5 -5
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +9 -12
- package/src/__tests__/list-messages-attachments.test.ts +42 -1
- package/src/__tests__/list-messages-client-message-id.test.ts +1 -1
- package/src/__tests__/list-messages-hidden-metadata.test.ts +1 -1
- package/src/__tests__/list-messages-page-latest.test.ts +1 -1
- package/src/__tests__/list-messages-tool-merge.test.ts +1 -1
- package/src/__tests__/llm-context-route-provider.test.ts +69 -4
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +9 -5
- package/src/__tests__/llm-request-log-call-site.test.ts +6 -6
- package/src/__tests__/llm-request-log-turn-query.test.ts +27 -13
- package/src/__tests__/llm-usage-store.test.ts +40 -1
- package/src/__tests__/log-export-routes.test.ts +1 -1
- package/src/__tests__/log-export-workspace.test.ts +3 -3
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +5 -5
- package/src/__tests__/memory-recall-log-store.test.ts +1 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +3 -4
- package/src/__tests__/messages-after-tiebreaker.test.ts +1 -1
- package/src/__tests__/migration-import-from-url.test.ts +2 -2
- package/src/__tests__/mtime-cache.test.ts +375 -0
- package/src/__tests__/non-member-access-request.test.ts +1 -2
- package/src/__tests__/notification-candidate-guardian-context.test.ts +203 -0
- package/src/__tests__/notification-guardian-path.test.ts +1 -1
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
- package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
- package/src/__tests__/oauth-provider-visibility.test.ts +1 -1
- package/src/__tests__/oauth-store.test.ts +1 -1
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +1 -1
- package/src/__tests__/persist-unsendable-image.test.ts +1 -1
- package/src/__tests__/persona-resolver.test.ts +39 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
- package/src/__tests__/playbook-execution.test.ts +1 -1
- package/src/__tests__/playbook-tools.test.ts +1 -1
- package/src/__tests__/plugin-api-model-profiles.test.ts +74 -21
- package/src/__tests__/plugin-bootstrap.test.ts +78 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +25 -5
- package/src/__tests__/provider-usage-tracking.test.ts +1 -1
- package/src/__tests__/prune-old-conversations-job.test.ts +1 -1
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/relay-server.test.ts +357 -56
- package/src/__tests__/runtime-attachment-metadata.test.ts +10 -1
- package/src/__tests__/runtime-events-sse-bilingual.test.ts +7 -9
- package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
- package/src/__tests__/runtime-events-sse.test.ts +1 -1
- package/src/__tests__/schedule-retry.test.ts +1 -1
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -1
- package/src/__tests__/schedule-routes.test.ts +1 -1
- package/src/__tests__/schedule-store.test.ts +1 -1
- package/src/__tests__/schedule-tools.test.ts +1 -1
- package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
- package/src/__tests__/scheduler-recurrence.test.ts +1 -1
- package/src/__tests__/scheduler-reuse-conversation.test.ts +1 -1
- package/src/__tests__/scheduler-wake.test.ts +2 -1
- package/src/__tests__/scoped-approval-grants.test.ts +1 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +5 -5
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +0 -8
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +1 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +40 -1
- package/src/__tests__/settings-routes.test.ts +11 -10
- package/src/__tests__/skill-load-tool.test.ts +72 -0
- package/src/__tests__/slack-inbound-verification.test.ts +1 -3
- package/src/__tests__/slack-messaging-token-resolution.test.ts +13 -2
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +1 -1
- package/src/__tests__/subagent-tool-gate-mode.test.ts +2 -73
- package/src/__tests__/subagent-tools.test.ts +1 -31
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/system-storage-cleanup-skill.test.ts +56 -0
- package/src/__tests__/task-compiler.test.ts +1 -1
- package/src/__tests__/task-management-tools.test.ts +1 -1
- package/src/__tests__/task-memory-cleanup.test.ts +9 -6
- package/src/__tests__/task-scheduler.test.ts +1 -1
- package/src/__tests__/thread-backfill.test.ts +1 -1
- package/src/__tests__/tool-approval-handler.test.ts +1 -1
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +2 -0
- package/src/__tests__/tool-executor.test.ts +32 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +1 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +73 -1
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +34 -34
- package/src/__tests__/trusted-contact-multichannel.test.ts +1 -2
- package/src/__tests__/trusted-contact-verification.test.ts +1 -1
- package/src/__tests__/turn-boundary-resolution.test.ts +3 -3
- package/src/__tests__/turn-events-store.test.ts +1 -1
- package/src/__tests__/twilio-routes.test.ts +2 -3
- package/src/__tests__/usage-cache-backfill-migration.test.ts +20 -10
- package/src/__tests__/usage-routes.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +34 -29
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
- package/src/__tests__/voice-invite-redemption.test.ts +134 -36
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
- package/src/__tests__/voice-session-bridge.test.ts +1 -1
- package/src/__tests__/workspace-git-service.test.ts +114 -1
- package/src/__tests__/workspace-heartbeat-service.test.ts +45 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +1 -1
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +88 -18
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +6 -6
- package/src/__tests__/workspace-migration-109-swap-quality-profile-to-glm-5p2.test.ts +281 -0
- package/src/__tests__/workspace-migration-110-flip-balanced-profile-to-together.test.ts +167 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +55 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +1 -1
- package/src/a2a/__tests__/task-store.test.ts +1 -1
- package/src/acp/__tests__/session-manager-persistence.test.ts +1 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +22 -11
- package/src/acp/__tests__/session-manager-startup.test.ts +1 -1
- package/src/acp/__tests__/session-manager.test.ts +72 -1
- package/src/acp/index.ts +10 -0
- package/src/acp/session-manager.ts +35 -0
- package/src/agent/loop.ts +45 -27
- package/src/api/index.ts +0 -6
- package/src/approvals/AGENTS.md +1 -2
- package/src/approvals/guardian-decision-primitive.ts +13 -210
- package/src/approvals/guardian-request-resolvers.ts +104 -58
- package/src/background-wake/wake-intent-hooks.test.ts +1 -1
- package/src/calls/__tests__/inbound-trust-reader.test.ts +110 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +88 -62
- package/src/calls/inbound-trust-reader.ts +40 -0
- package/src/calls/relay-server.ts +65 -23
- package/src/calls/relay-setup-router.ts +20 -6
- package/src/calls/relay-verification.ts +7 -7
- package/src/cli/commands/contacts.ts +6 -24
- package/src/cli/commands/db/__tests__/repair.test.ts +15 -6
- package/src/cli/commands/db/__tests__/status.test.ts +7 -3
- package/src/cli/commands/db/status.ts +212 -33
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +6 -1
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-retrospective.ts +129 -0
- package/src/cli/commands/memory/memory-v3.ts +176 -4
- package/src/cli/commands/plugins.ts +268 -11
- package/src/cli/lib/__tests__/install-from-github.test.ts +40 -0
- package/src/cli/lib/__tests__/plugin-pin-history.test.ts +162 -0
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +158 -0
- package/src/cli/lib/install-from-github.ts +47 -6
- package/src/cli/lib/plugin-marketplace.ts +11 -0
- package/src/cli/lib/plugin-pin-history.ts +257 -0
- package/src/cli/lib/toggle-plugin.ts +146 -0
- package/src/config/__tests__/sync-gated-profiles.test.ts +2 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +15 -33
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +3 -8
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +64 -37
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +1 -1
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +14 -72
- package/src/config/bundled-skills/app-builder/references/examples/README.md +1 -2
- package/src/config/bundled-skills/contacts/SKILL.md +7 -12
- package/src/config/bundled-skills/messaging/tools/shared.ts +4 -1
- package/src/config/bundled-skills/system-storage-cleanup/SKILL.md +74 -0
- package/src/config/bundled-skills/workflows/SKILL.md +4 -3
- package/src/config/call-site-defaults.ts +11 -2
- package/src/config/feature-flag-registry.json +0 -8
- package/src/config/profile-dispatchability.ts +11 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/llm.ts +2 -0
- package/src/config/schemas/memory-lifecycle.ts +5 -3
- package/src/config/schemas/timeouts.ts +24 -0
- package/src/config/seed-inference-profiles.ts +133 -45
- package/src/config/sync-gated-profiles.ts +13 -1
- package/src/contacts/contact-store.ts +21 -0
- package/src/contacts/member-status.ts +9 -0
- package/src/credential-health/credential-health-service.ts +1 -5
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +44 -0
- package/src/daemon/app-source-watcher.ts +31 -18
- package/src/daemon/assistant-attachments.ts +94 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +3 -0
- package/src/daemon/conversation-agent-loop.ts +9 -36
- package/src/daemon/conversation-runtime-assembly.ts +91 -66
- package/src/daemon/conversation-tool-setup.ts +20 -63
- package/src/daemon/conversation.ts +144 -52
- package/src/daemon/event-loop-watchdog.test.ts +85 -0
- package/src/daemon/event-loop-watchdog.ts +133 -0
- package/src/daemon/external-plugins-bootstrap.ts +26 -80
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +1 -1
- package/src/daemon/handlers/config-channels.ts +32 -18
- package/src/daemon/handlers/conversations.ts +7 -0
- package/src/daemon/handlers/shared.ts +7 -0
- package/src/daemon/lifecycle.ts +16 -3
- package/src/daemon/message-types/inbox.ts +0 -6
- package/src/daemon/message-types/messages.ts +0 -4
- package/src/daemon/message-types/surfaces.ts +18 -8
- package/src/daemon/server.ts +0 -4
- package/src/daemon/tool-setup-types.ts +0 -7
- package/src/daemon/trust-context.ts +6 -0
- package/src/daemon/wake-conversation-ops.ts +70 -0
- package/src/daemon/workspace-tools-watcher.ts +7 -3
- package/src/documents/document-comments-store.test.ts +1 -1
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +1 -1
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +6 -0
- package/src/heartbeat/heartbeat-service.ts +3 -4
- package/src/ipc/__tests__/attachment-ipc.test.ts +1 -1
- package/src/ipc/__tests__/browser-ipc.test.ts +73 -2
- package/src/ipc/__tests__/watcher-ipc.test.ts +59 -39
- package/src/ipc/assistant-server.ts +8 -0
- package/src/ipc/gateway-client.ts +2 -1
- package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +58 -0
- package/src/ipc/routes/invite-ipc-routes.ts +66 -0
- package/src/live-voice/__tests__/live-voice-archive.test.ts +1 -1
- package/src/memory/__tests__/activation-session-store.test.ts +1 -1
- package/src/memory/__tests__/auto-analysis-guard.test.ts +1 -1
- package/src/memory/__tests__/conversation-group-migration.test.ts +1 -1
- package/src/memory/__tests__/conversation-queries.test.ts +1 -1
- package/src/memory/__tests__/db-async-query.test.ts +1 -1
- package/src/memory/__tests__/db-logs-attach.test.ts +110 -0
- package/src/memory/__tests__/db-maintenance.test.ts +28 -36
- package/src/memory/__tests__/db-memory-attach.test.ts +113 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +1 -1
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +1 -1
- package/src/memory/__tests__/fork-message-copy.test.ts +232 -0
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +3 -0
- package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +5 -5
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +8 -6
- package/src/memory/__tests__/memory-retrospective-job.test.ts +30 -37
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +69 -66
- package/src/memory/__tests__/memory-retrospective-state.test.ts +1 -1
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +1 -1
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +1 -1
- package/src/memory/__tests__/onboarding-events-store.test.ts +1 -1
- package/src/memory/__tests__/table-relocation.test.ts +129 -0
- package/src/memory/conversation-crud.ts +461 -152
- package/src/memory/db-async-query.ts +89 -5
- package/src/memory/db-connection.ts +101 -18
- package/src/memory/db-init.ts +409 -234
- package/src/memory/db-maintenance.ts +43 -38
- package/src/memory/db-singleton.ts +45 -19
- package/src/memory/fork-message-copy.ts +170 -0
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +92 -0
- package/src/memory/graph/bootstrap.test.ts +6 -3
- package/src/memory/graph/retriever.test.ts +12 -12
- package/src/memory/graph/store.test.ts +15 -25
- package/src/memory/graph/store.ts +23 -14
- package/src/memory/graph/tool-handlers.ts +34 -5
- package/src/memory/graph/tools.ts +5 -2
- package/src/memory/indexer.ts +21 -9
- package/src/memory/job-handlers/cleanup.ts +10 -3
- package/src/memory/job-handlers/embedding.test.ts +4 -4
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +4 -4
- package/src/memory/jobs/embed-pkb-file.test.ts +7 -7
- package/src/memory/jobs-store.ts +36 -24
- package/src/memory/llm-request-log-store.ts +51 -19
- package/src/memory/llm-usage-store.ts +31 -1
- package/src/memory/memory-retrospective-job.ts +27 -19
- package/src/memory/memory-retrospective-startup-cleanup.ts +10 -2
- package/src/memory/migrations/{100-core-tables.ts → 000-core-tables.ts} +6 -10
- package/src/memory/migrations/104-core-indexes.ts +1 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +189 -196
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +98 -105
- package/src/memory/migrations/134-contacts-notes-column.ts +66 -69
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +19 -22
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +227 -230
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +204 -209
- package/src/memory/migrations/141-rename-verification-table.ts +45 -48
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +16 -23
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +23 -30
- package/src/memory/migrations/144-rename-voice-to-phone.ts +133 -136
- package/src/memory/migrations/145-drop-accounts-table.ts +4 -7
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +79 -82
- package/src/memory/migrations/148-drop-reminders-table.ts +3 -6
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +71 -78
- package/src/memory/migrations/157-invite-contact-id.ts +73 -76
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +44 -58
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +36 -43
- package/src/memory/migrations/174-rename-thread-starters-table.ts +30 -37
- package/src/memory/migrations/176-drop-capability-card-state.ts +17 -22
- package/src/memory/migrations/177-create-trace-events-table.ts +23 -28
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +36 -43
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +14 -21
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +17 -24
- package/src/memory/migrations/192-contacts-user-file-column.ts +6 -9
- package/src/memory/migrations/193-add-source-type-columns.ts +33 -36
- package/src/memory/migrations/194-memory-recall-logs.ts +34 -39
- package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +59 -66
- package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +41 -48
- package/src/memory/migrations/204-rename-memory-graph-type-values.ts +11 -18
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +76 -83
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +50 -57
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +6 -11
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +4 -9
- package/src/memory/migrations/217-conversation-host-access.ts +13 -18
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +86 -93
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +41 -48
- package/src/memory/migrations/230-acp-session-history.ts +23 -28
- package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +58 -62
- package/src/memory/migrations/232-activation-state.ts +11 -16
- package/src/memory/migrations/233-document-conversations.ts +20 -25
- package/src/memory/migrations/234-memory-v2-activation-logs.ts +26 -31
- package/src/memory/migrations/235-slack-compaction-watermark.ts +5 -10
- package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +6 -11
- package/src/memory/migrations/237-heartbeat-runs.ts +22 -27
- package/src/memory/migrations/239-trace-events-created-at-index.ts +4 -9
- package/src/memory/migrations/242-message-bookmarks.ts +17 -22
- package/src/memory/migrations/245-memory-retrospective-state.ts +8 -13
- package/src/memory/migrations/249-normalize-slack-external-content.ts +37 -41
- package/src/memory/migrations/251-a2a-tasks.ts +27 -32
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +12 -17
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +10 -15
- package/src/memory/migrations/256-memory-v2-injection-events.ts +70 -74
- package/src/memory/migrations/259-conversation-cleaned-at.ts +4 -9
- package/src/memory/migrations/260-rename-cleaned-at.ts +11 -16
- package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +3 -8
- package/src/memory/migrations/262-memory-v3-coactivation.ts +21 -26
- package/src/memory/migrations/263-memory-v3-auto-edges.ts +14 -19
- package/src/memory/migrations/270-schedule-description.ts +7 -12
- package/src/memory/migrations/272-acp-session-history-cwd.ts +8 -13
- package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +8 -13
- package/src/memory/migrations/297-move-llm-request-logs-to-logs-db.ts +111 -0
- package/src/memory/migrations/298-move-memory-jobs-to-memory-db.ts +128 -0
- package/src/memory/migrations/299-canonical-guardian-deliveries-conversation-index.ts +19 -0
- package/src/memory/migrations/__tests__/297-move-llm-request-logs.test.ts +180 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +333 -7
- package/src/memory/migrations/helpers/relocation.ts +227 -0
- package/src/memory/migrations/registry.ts +63 -0
- package/src/memory/migrations/run-migrations.ts +187 -16
- package/src/memory/migrations/validate-migration-state.ts +50 -145
- package/src/memory/raw-query.ts +47 -2
- package/src/memory/skill-loaded-events-store.test.ts +1 -1
- package/src/memory/task-memory-cleanup.ts +62 -41
- package/src/memory/tool-executed-events-store.test.ts +1 -1
- package/src/memory/turn-trace-store.test.ts +1 -1
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +16 -15
- package/src/memory/v2/__tests__/harness-compare.test.ts +1 -1
- package/src/memory/v2/__tests__/harness-oracle.test.ts +1 -1
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +1 -1
- package/src/memory/v2/__tests__/sweep-job.test.ts +2 -2
- package/src/memory/v3-eval/__tests__/eval-packets.test.ts +38 -0
- package/src/memory/v3-eval/__tests__/eval-tally.test.ts +139 -0
- package/src/memory/v3-eval/eval-packets.ts +197 -12
- package/src/memory/v3-eval/eval-tally.ts +234 -0
- package/src/messaging/provider.ts +10 -0
- package/src/messaging/providers/gmail/adapter.ts +1 -0
- package/src/messaging/providers/gmail/client.ts +14 -0
- package/src/messaging/providers/index.ts +1 -1
- package/src/messaging/providers/slack/send.test.ts +87 -39
- package/src/messaging/providers/slack/send.ts +84 -105
- package/src/notifications/README.md +9 -5
- package/src/notifications/__tests__/deterministic-checks.test.ts +43 -1
- package/src/notifications/adapters/slack.ts +12 -10
- package/src/notifications/approval-card-builder.ts +81 -20
- package/src/notifications/approval-card-data.ts +8 -5
- package/src/notifications/canonical-delivery-recorder.ts +7 -5
- package/src/notifications/conversation-candidates.ts +24 -59
- package/src/notifications/copy-composer.ts +48 -68
- package/src/notifications/deterministic-checks.ts +19 -16
- package/src/notifications/emit-signal.ts +29 -1
- package/src/notifications/trusted-contact-payloads.ts +70 -0
- package/src/oauth/byo-connection.test.ts +9 -0
- package/src/oauth/connection-resolver.test.ts +146 -6
- package/src/oauth/connection-resolver.ts +132 -5
- package/src/oauth/oauth-store.ts +16 -3
- package/src/oauth/scope-utils.ts +21 -0
- package/src/plugin-api/index.ts +9 -4
- package/src/plugin-api/model-profiles.test.ts +123 -0
- package/src/plugin-api/model-profiles.ts +5 -1
- package/src/plugin-api/vision-support.test.ts +149 -0
- package/src/plugin-api/vision-support.ts +78 -0
- package/src/plugins/defaults/compaction/window-manager.ts +45 -64
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +13 -4
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +302 -0
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +103 -0
- package/src/plugins/defaults/image-fallback/package.json +14 -0
- package/src/plugins/defaults/image-fallback/src/caption-cache.ts +49 -0
- package/src/plugins/defaults/image-fallback/src/image-persist.ts +59 -0
- package/src/plugins/defaults/image-fallback/src/vision-caption.ts +120 -0
- package/src/plugins/defaults/index.ts +23 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +14 -1
- package/src/plugins/defaults/memory-retrieval/injectors.ts +4 -4
- package/src/plugins/external-plugin-loader.ts +47 -6
- package/src/plugins/mtime-cache.ts +772 -0
- package/src/plugins/pipeline.ts +7 -2
- package/src/plugins/registry.ts +16 -5
- package/src/plugins/user-loader.ts +22 -76
- package/src/prompts/persona-resolver.ts +29 -11
- package/src/prompts/system-prompt.ts +1 -1
- package/src/prompts/templates/system-sections.ts +4 -4
- package/src/providers/__tests__/count-tokens-forwarding.test.ts +98 -0
- package/src/providers/anthropic/client.ts +254 -185
- package/src/providers/call-site-routing.ts +10 -0
- package/src/providers/gemini/client.ts +43 -0
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/inference/connections.ts +6 -1
- package/src/providers/model-catalog.ts +37 -0
- package/src/providers/platform-proxy/constants.ts +5 -0
- package/src/providers/ratelimit.ts +9 -0
- package/src/providers/retry.ts +10 -0
- package/src/providers/together/client.ts +35 -0
- package/src/providers/types.ts +16 -0
- package/src/providers/usage-tracking.ts +7 -0
- package/src/runtime/AGENTS.md +9 -1
- package/src/runtime/__tests__/agent-wake.test.ts +259 -4
- package/src/runtime/__tests__/slack-block-formatting.test.ts +39 -10
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +417 -0
- package/src/runtime/actor-trust-resolver.ts +8 -16
- package/src/runtime/agent-wake.ts +183 -60
- package/src/runtime/channel-reply-delivery.ts +6 -3
- package/src/runtime/guardian-decision-types.ts +3 -22
- package/src/runtime/http-server.ts +1 -15
- package/src/runtime/invite-redemption-service.ts +155 -6
- package/src/runtime/invite-service.ts +113 -62
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +3 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +277 -0
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +140 -0
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +26 -7
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +14 -10
- package/src/runtime/routes/__tests__/contact-routes-update-channel-relay.test.ts +164 -0
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +8 -8
- package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +1 -3
- package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +240 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +4 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +143 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +1 -1
- package/src/runtime/routes/acp-routes-list.test.ts +4 -0
- package/src/runtime/routes/acp-routes.test.ts +5 -6
- package/src/runtime/routes/attachment-routes.ts +21 -17
- package/src/runtime/routes/browser-routes.ts +19 -1
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -9
- package/src/runtime/routes/channel-verification-routes.ts +12 -1
- package/src/runtime/routes/contact-routes.ts +275 -164
- package/src/runtime/routes/conversation-query-routes.ts +15 -5
- package/src/runtime/routes/conversation-routes.ts +24 -3
- package/src/runtime/routes/conversation-starter-routes.ts +7 -8
- package/src/runtime/routes/guardian-approval-interception.ts +13 -274
- package/src/runtime/routes/inbound-message-handler.ts +20 -15
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +285 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +45 -34
- package/src/runtime/routes/inbound-stages/admission-policy.ts +20 -5
- package/src/runtime/routes/log-export-routes.ts +2 -2
- package/src/runtime/routes/memory-eval-routes.ts +92 -0
- package/src/runtime/routes/memory-item-routes.test.ts +12 -11
- package/src/runtime/routes/migration-routes.ts +51 -40
- package/src/runtime/routes/plugins-routes.ts +164 -8
- package/src/runtime/routes/schedule-routes.ts +1 -0
- package/src/runtime/routes/usage-routes.ts +3 -0
- package/src/runtime/routes/work-items-routes.test.ts +1 -1
- package/src/runtime/slack-block-formatting.ts +46 -48
- package/src/runtime/trust-verdict-consumer.ts +172 -0
- package/src/schedule/scheduler.ts +6 -9
- package/src/telemetry/usage-telemetry-reporter.test.ts +1 -1
- package/src/tools/ask-question/ask-question-tool.test.ts +60 -52
- package/src/tools/ask-question/ask-question-tool.ts +14 -73
- package/src/tools/browser/__tests__/browser-status.test.ts +20 -0
- package/src/tools/browser/browser-execution.ts +16 -4
- package/src/tools/document/document-comment-tool.test.ts +1 -1
- package/src/tools/executor.ts +15 -3
- package/src/tools/host-terminal/host-shell.ts +28 -9
- package/src/tools/memory/register.test.ts +32 -0
- package/src/tools/skills/load.ts +43 -2
- package/src/tools/subagent/spawn.ts +4 -10
- package/src/tools/terminal/shell.ts +16 -5
- package/src/tools/types.ts +1 -0
- package/src/util/fs-watcher-error.ts +36 -0
- package/src/util/logs-db-path.ts +22 -0
- package/src/util/memory-db-path.ts +23 -0
- package/src/watcher/providers/gmail.ts +7 -2
- package/src/workflows/engine-integration.test.ts +1 -1
- package/src/workflows/engine.test.ts +1 -1
- package/src/workflows/engine.ts +22 -0
- package/src/workflows/fanout-load.test.ts +1 -1
- package/src/workflows/journal-store.test.ts +1 -1
- package/src/workflows/leaf-runner.test.ts +40 -1
- package/src/workflows/leaf-runner.ts +26 -1
- package/src/workspace/git-service.ts +144 -29
- package/src/workspace/migrations/109-swap-quality-profile-to-glm-5p2.ts +121 -0
- package/src/workspace/migrations/110-flip-balanced-profile-to-together.ts +82 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/migrations/runner.ts +32 -2
- package/src/__tests__/access-request-decision.test.ts +0 -375
- package/src/__tests__/guardian-grant-minting.test.ts +0 -607
- package/src/__tests__/plugin-source-watcher.test.ts +0 -302
- package/src/api/events/turn-profile-auto-routed.ts +0 -28
- package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +0 -107
- package/src/daemon/plugin-source-watcher.ts +0 -278
- package/src/daemon/switch-inference-profile-tool.ts +0 -62
- package/src/memory/guardian-approvals.ts +0 -361
- package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +0 -66
- package/src/memory/migrations/038-actor-token-records.ts +0 -45
- package/src/memory/migrations/039-actor-refresh-token-records.ts +0 -57
- package/src/memory/migrations/103-complex-migrations.ts +0 -23
- package/src/memory/migrations/113-late-migrations.ts +0 -30
- package/src/memory/migrations/index.ts +0 -301
- package/src/runtime/routes/access-request-decision.ts +0 -297
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +0 -963
- package/src/runtime/routes/channel-guardian-routes.ts +0 -19
- package/src/runtime/routes/guardian-expiry-sweep.ts +0 -132
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ImageContent,
|
|
5
|
+
Message,
|
|
6
|
+
ModelProfileInfo,
|
|
7
|
+
UserPromptSubmitContext,
|
|
8
|
+
} from "@vellumai/plugin-api";
|
|
9
|
+
|
|
10
|
+
// ─── Mocks ──────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
// Control doesSupportVision per-profile from the test.
|
|
13
|
+
let visionProfiles: Set<string>;
|
|
14
|
+
let mockProfiles: ModelProfileInfo[];
|
|
15
|
+
let sendMessageResponse = {
|
|
16
|
+
content: [{ type: "text", text: "A red chart showing Q3 revenue." }],
|
|
17
|
+
};
|
|
18
|
+
let providerResolves = true;
|
|
19
|
+
|
|
20
|
+
const fakeProvider = {
|
|
21
|
+
name: "mock-vision-provider",
|
|
22
|
+
async sendMessage() {
|
|
23
|
+
return sendMessageResponse;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Mock @vellumai/plugin-api — only the runtime handles the plugin imports.
|
|
28
|
+
// `extractAllText` stays real (imported from the relative path, not plugin-api).
|
|
29
|
+
mock.module("@vellumai/plugin-api", () => ({
|
|
30
|
+
doesSupportVision: (profile: ModelProfileInfo) => visionProfiles.has(profile.key),
|
|
31
|
+
getModelProfiles: () => mockProfiles,
|
|
32
|
+
getConfiguredProvider: async () => (providerResolves ? fakeProvider : null),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
// Mock the image-persist module to avoid filesystem side effects in tests.
|
|
36
|
+
let mockPersistPath: string | null = "/workspace/data/attachments/mock-hash.png";
|
|
37
|
+
mock.module("../src/image-persist.js", () => ({
|
|
38
|
+
persistImage: () => mockPersistPath,
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
// ─── Imports (after mocks are registered) ───────────────────────────────────
|
|
42
|
+
|
|
43
|
+
const userPromptSubmit = (await import("../hooks/user-prompt-submit.js")).default;
|
|
44
|
+
const { findVisionProfile } = await import("../src/vision-caption.js");
|
|
45
|
+
const { resetCaptionCacheForTests } = await import("../src/caption-cache.js");
|
|
46
|
+
|
|
47
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
const logger = {
|
|
50
|
+
info() {},
|
|
51
|
+
warn() {},
|
|
52
|
+
error() {},
|
|
53
|
+
debug() {},
|
|
54
|
+
warnOnce() {},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function profile(
|
|
58
|
+
key: string,
|
|
59
|
+
overrides: Partial<ModelProfileInfo> = {},
|
|
60
|
+
): ModelProfileInfo {
|
|
61
|
+
return {
|
|
62
|
+
key,
|
|
63
|
+
label: key,
|
|
64
|
+
description: null,
|
|
65
|
+
isActive: false,
|
|
66
|
+
isDisabled: false,
|
|
67
|
+
isMix: false,
|
|
68
|
+
...overrides,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function imageBlock(data = "base64data"): ImageContent {
|
|
73
|
+
return {
|
|
74
|
+
type: "image",
|
|
75
|
+
source: { type: "base64", media_type: "image/png", data },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function imageMsg(data = "base64data"): Message {
|
|
80
|
+
return { role: "user", content: [imageBlock(data)] };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function textMsg(text: string): Message {
|
|
84
|
+
return { role: "user", content: [{ type: "text", text }] };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function makeCtx(
|
|
88
|
+
overrides: Partial<UserPromptSubmitContext> = {},
|
|
89
|
+
): UserPromptSubmitContext {
|
|
90
|
+
return {
|
|
91
|
+
conversationId: "c1",
|
|
92
|
+
userMessageId: "m1",
|
|
93
|
+
requestId: "r1",
|
|
94
|
+
modelProfileKey: "text-only",
|
|
95
|
+
isNonInteractive: false,
|
|
96
|
+
prompt: "What is in this image?",
|
|
97
|
+
originalMessages: [],
|
|
98
|
+
latestMessages: [],
|
|
99
|
+
logger,
|
|
100
|
+
...overrides,
|
|
101
|
+
} as unknown as UserPromptSubmitContext;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── Setup ──────────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
visionProfiles = new Set<string>(["vision-profile"]);
|
|
108
|
+
mockProfiles = [
|
|
109
|
+
profile("text-only", { label: "Text Only", isActive: true }),
|
|
110
|
+
profile("vision-profile", { label: "Vision" }),
|
|
111
|
+
];
|
|
112
|
+
sendMessageResponse = {
|
|
113
|
+
content: [{ type: "text", text: "A red chart showing Q3 revenue." }],
|
|
114
|
+
};
|
|
115
|
+
providerResolves = true;
|
|
116
|
+
mockPersistPath = "/workspace/data/attachments/mock-hash.png";
|
|
117
|
+
resetCaptionCacheForTests();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ─── Tests ──────────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
describe("image-fallback user-prompt-submit hook", () => {
|
|
123
|
+
test("is a no-op when the active model supports vision", async () => {
|
|
124
|
+
visionProfiles = new Set(["text-only"]); // active profile supports vision
|
|
125
|
+
const messages = [imageMsg()];
|
|
126
|
+
const ctx = makeCtx({ latestMessages: messages });
|
|
127
|
+
await userPromptSubmit(ctx);
|
|
128
|
+
expect(ctx.latestMessages[0].content[0].type).toBe("image");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("does not gate on isNonInteractive — captions even for background runs", async () => {
|
|
132
|
+
const messages = [imageMsg()];
|
|
133
|
+
const ctx = makeCtx({ latestMessages: messages, isNonInteractive: true });
|
|
134
|
+
await userPromptSubmit(ctx);
|
|
135
|
+
expect(ctx.latestMessages[0].content[0].type).toBe("text");
|
|
136
|
+
expect((ctx.latestMessages[0].content[0] as { text: string }).text).toContain(
|
|
137
|
+
"[Image:",
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("replaces image blocks with captions when active model is text-only", async () => {
|
|
142
|
+
const messages = [imageMsg("img1")];
|
|
143
|
+
const ctx = makeCtx({ latestMessages: messages });
|
|
144
|
+
await userPromptSubmit(ctx);
|
|
145
|
+
expect(ctx.latestMessages[0].content[0].type).toBe("text");
|
|
146
|
+
expect((ctx.latestMessages[0].content[0] as { text: string }).text).toContain(
|
|
147
|
+
"[Image: A red chart showing Q3 revenue.]",
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("references the saved image path in the caption text", async () => {
|
|
152
|
+
const messages = [imageMsg("img1")];
|
|
153
|
+
const ctx = makeCtx({ latestMessages: messages });
|
|
154
|
+
await userPromptSubmit(ctx);
|
|
155
|
+
const text = (ctx.latestMessages[0].content[0] as { text: string }).text;
|
|
156
|
+
expect(text).toContain("(saved to /workspace/data/attachments/");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("works without a saved path when persist fails", async () => {
|
|
160
|
+
mockPersistPath = null;
|
|
161
|
+
const messages = [imageMsg("img1")];
|
|
162
|
+
const ctx = makeCtx({ latestMessages: messages });
|
|
163
|
+
await userPromptSubmit(ctx);
|
|
164
|
+
const text = (ctx.latestMessages[0].content[0] as { text: string }).text;
|
|
165
|
+
expect(text).toContain("[Image: A red chart showing Q3 revenue.]");
|
|
166
|
+
expect(text).not.toContain("(saved to");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("preserves non-image blocks and captions only images", async () => {
|
|
170
|
+
const messages: Message[] = [
|
|
171
|
+
{
|
|
172
|
+
role: "user",
|
|
173
|
+
content: [
|
|
174
|
+
{ type: "text", text: "Look at this:" },
|
|
175
|
+
imageBlock("img1"),
|
|
176
|
+
{ type: "text", text: "What do you see?" },
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
const ctx = makeCtx({ latestMessages: messages });
|
|
181
|
+
await userPromptSubmit(ctx);
|
|
182
|
+
expect((ctx.latestMessages[0].content[0] as { text: string }).text).toBe(
|
|
183
|
+
"Look at this:",
|
|
184
|
+
);
|
|
185
|
+
expect(ctx.latestMessages[0].content[1].type).toBe("text");
|
|
186
|
+
expect((ctx.latestMessages[0].content[1] as { text: string }).text).toContain(
|
|
187
|
+
"[Image:",
|
|
188
|
+
);
|
|
189
|
+
expect((ctx.latestMessages[0].content[2] as { text: string }).text).toBe(
|
|
190
|
+
"What do you see?",
|
|
191
|
+
);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("uses fail-open placeholder when no vision profile is configured", async () => {
|
|
195
|
+
visionProfiles = new Set<string>(); // no vision profiles
|
|
196
|
+
const messages = [imageMsg()];
|
|
197
|
+
const ctx = makeCtx({ latestMessages: messages });
|
|
198
|
+
await userPromptSubmit(ctx);
|
|
199
|
+
expect(ctx.latestMessages[0].content[0].type).toBe("text");
|
|
200
|
+
expect((ctx.latestMessages[0].content[0] as { text: string }).text).toContain(
|
|
201
|
+
"no vision-capable model",
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("uses fail-open placeholder when provider resolution returns null", async () => {
|
|
206
|
+
providerResolves = false;
|
|
207
|
+
const messages = [imageMsg()];
|
|
208
|
+
const ctx = makeCtx({ latestMessages: messages });
|
|
209
|
+
await userPromptSubmit(ctx);
|
|
210
|
+
expect(ctx.latestMessages[0].content[0].type).toBe("text");
|
|
211
|
+
expect((ctx.latestMessages[0].content[0] as { text: string }).text).toContain(
|
|
212
|
+
"captioning failed",
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("caches captions — second call with same image does not invoke provider", async () => {
|
|
217
|
+
let callCount = 0;
|
|
218
|
+
const trackingProvider = {
|
|
219
|
+
name: "mock-vision-provider",
|
|
220
|
+
async sendMessage() {
|
|
221
|
+
callCount++;
|
|
222
|
+
return sendMessageResponse;
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
// Override the mock to track calls.
|
|
226
|
+
mock.module("@vellumai/plugin-api", () => ({
|
|
227
|
+
doesSupportVision: (p: ModelProfileInfo) => visionProfiles.has(p.key),
|
|
228
|
+
getModelProfiles: () => mockProfiles,
|
|
229
|
+
getConfiguredProvider: async () => trackingProvider,
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
const messages1 = [imageMsg("same-data")];
|
|
233
|
+
const ctx1 = makeCtx({ latestMessages: messages1 });
|
|
234
|
+
await userPromptSubmit(ctx1);
|
|
235
|
+
expect(callCount).toBe(1);
|
|
236
|
+
|
|
237
|
+
// Second turn with the same image — should hit cache, no new provider call.
|
|
238
|
+
const messages2 = [imageMsg("same-data")];
|
|
239
|
+
const ctx2 = makeCtx({ latestMessages: messages2 });
|
|
240
|
+
await userPromptSubmit(ctx2);
|
|
241
|
+
expect(callCount).toBe(1); // still 1 — cache hit
|
|
242
|
+
|
|
243
|
+
// Restore the original mock for other tests.
|
|
244
|
+
mock.module("@vellumai/plugin-api", () => ({
|
|
245
|
+
doesSupportVision: (p: ModelProfileInfo) => visionProfiles.has(p.key),
|
|
246
|
+
getModelProfiles: () => mockProfiles,
|
|
247
|
+
getConfiguredProvider: async () => (providerResolves ? fakeProvider : null),
|
|
248
|
+
}));
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("handles multiple images across multiple messages", async () => {
|
|
252
|
+
const messages: Message[] = [
|
|
253
|
+
imageMsg("img-a"),
|
|
254
|
+
textMsg("and another:"),
|
|
255
|
+
{
|
|
256
|
+
role: "user",
|
|
257
|
+
content: [imageBlock("img-b"), { type: "text", text: "both?" }],
|
|
258
|
+
},
|
|
259
|
+
];
|
|
260
|
+
const ctx = makeCtx({ latestMessages: messages });
|
|
261
|
+
await userPromptSubmit(ctx);
|
|
262
|
+
expect(ctx.latestMessages[0].content[0].type).toBe("text");
|
|
263
|
+
expect((ctx.latestMessages[0].content[0] as { text: string }).text).toContain(
|
|
264
|
+
"[Image:",
|
|
265
|
+
);
|
|
266
|
+
expect(ctx.latestMessages[2].content[0].type).toBe("text");
|
|
267
|
+
expect((ctx.latestMessages[2].content[0] as { text: string }).text).toContain(
|
|
268
|
+
"[Image:",
|
|
269
|
+
);
|
|
270
|
+
expect((ctx.latestMessages[2].content[1] as { text: string }).text).toBe(
|
|
271
|
+
"both?",
|
|
272
|
+
);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("resolves active profile via isActive when modelProfileKey is null", async () => {
|
|
276
|
+
const messages = [imageMsg()];
|
|
277
|
+
const ctx = makeCtx({ latestMessages: messages, modelProfileKey: null });
|
|
278
|
+
await userPromptSubmit(ctx);
|
|
279
|
+
// The active profile is "text-only" (isActive: true), which doesn't support
|
|
280
|
+
// vision, so images should be captioned.
|
|
281
|
+
expect(ctx.latestMessages[0].content[0].type).toBe("text");
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe("findVisionProfile", () => {
|
|
286
|
+
test("returns the first enabled vision-capable profile", () => {
|
|
287
|
+
expect(findVisionProfile()).toBe("vision-profile");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("skips disabled vision profiles", () => {
|
|
291
|
+
mockProfiles = [
|
|
292
|
+
profile("text-only", { label: "Text", isActive: true }),
|
|
293
|
+
profile("vision-profile", { label: "Vision", isDisabled: true }),
|
|
294
|
+
];
|
|
295
|
+
expect(findVisionProfile()).toBeNull();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("returns null when no profiles support vision", () => {
|
|
299
|
+
visionProfiles = new Set<string>();
|
|
300
|
+
expect(findVisionProfile()).toBeNull();
|
|
301
|
+
});
|
|
302
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `user-prompt-submit` hook: when the active model is text-only,
|
|
3
|
+
* captions image blocks via a vision-capable profile and substitutes the
|
|
4
|
+
* caption as a text block so the model can still reason about the image's
|
|
5
|
+
* content.
|
|
6
|
+
*
|
|
7
|
+
* The hook runs once per user turn, after the assistant assembles
|
|
8
|
+
* `latestMessages` and before they flow into `agentLoop.run()`. It:
|
|
9
|
+
*
|
|
10
|
+
* 1. Resolves the active profile from `modelProfileKey` (or the workspace's
|
|
11
|
+
* active profile when the key is `null`) and checks `doesSupportVision`.
|
|
12
|
+
* If the model already handles images, the hook is a no-op.
|
|
13
|
+
* 2. Finds a vision-capable profile for captioning via `findVisionProfile`.
|
|
14
|
+
* If none exists, images are replaced with a fail-open placeholder so the
|
|
15
|
+
* model at least knows an image was present.
|
|
16
|
+
* 3. Persists each image to the workspace attachments directory (content-hash
|
|
17
|
+
* deduped) so the original image is accessible to future vision-capable
|
|
18
|
+
* turns or subagents.
|
|
19
|
+
* 4. Captions each `ImageContent` block through the `vision` call site (with
|
|
20
|
+
* an in-memory content-hash cache to avoid re-captioning across turns), and
|
|
21
|
+
* replaces the block with `[Image: <caption>] (saved to <path>)`.
|
|
22
|
+
*
|
|
23
|
+
* Fail-open is the dominant error mode: a captioning failure leaves a
|
|
24
|
+
* placeholder text block (with the saved image path) rather than the raw
|
|
25
|
+
* image (which would cause a provider rejection on a text-only model) or
|
|
26
|
+
* dropping the image entirely (which would lose information).
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
doesSupportVision,
|
|
31
|
+
getModelProfiles,
|
|
32
|
+
type ImageContent,
|
|
33
|
+
type PluginHookFn,
|
|
34
|
+
type UserPromptSubmitContext,
|
|
35
|
+
} from "@vellumai/plugin-api";
|
|
36
|
+
|
|
37
|
+
import { persistImage } from "../src/image-persist.js";
|
|
38
|
+
import { captionImage, findVisionProfile } from "../src/vision-caption.js";
|
|
39
|
+
|
|
40
|
+
const userPromptSubmit: PluginHookFn<UserPromptSubmitContext> = async (ctx) => {
|
|
41
|
+
// Resolve the active profile from modelProfileKey, falling back to the
|
|
42
|
+
// workspace's active profile when the key is null (profile unchanged since
|
|
43
|
+
// the last notified turn).
|
|
44
|
+
const profiles = getModelProfiles();
|
|
45
|
+
const activeProfile =
|
|
46
|
+
ctx.modelProfileKey != null
|
|
47
|
+
? profiles.find((p) => p.key === ctx.modelProfileKey)
|
|
48
|
+
: profiles.find((p) => p.isActive);
|
|
49
|
+
if (activeProfile == null) return;
|
|
50
|
+
|
|
51
|
+
// If the active model already supports vision, nothing to do.
|
|
52
|
+
if (doesSupportVision(activeProfile)) return;
|
|
53
|
+
|
|
54
|
+
// Find a vision-capable profile for captioning.
|
|
55
|
+
const visionProfileKey = findVisionProfile();
|
|
56
|
+
|
|
57
|
+
// Scan all messages for image blocks and replace them with captions.
|
|
58
|
+
let imageCount = 0;
|
|
59
|
+
for (const message of ctx.latestMessages) {
|
|
60
|
+
for (let i = 0; i < message.content.length; i++) {
|
|
61
|
+
const block = message.content[i];
|
|
62
|
+
if (block.type !== "image") continue;
|
|
63
|
+
|
|
64
|
+
imageCount++;
|
|
65
|
+
const image = block as ImageContent;
|
|
66
|
+
|
|
67
|
+
// Persist the image to the workspace so it's accessible to future
|
|
68
|
+
// vision-capable turns or subagents.
|
|
69
|
+
const savedPath = persistImage(
|
|
70
|
+
image.source.data,
|
|
71
|
+
image.source.media_type,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (visionProfileKey != null) {
|
|
75
|
+
const caption = await captionImage(image, visionProfileKey, ctx.logger);
|
|
76
|
+
const pathSuffix = savedPath != null ? ` (saved to ${savedPath})` : "";
|
|
77
|
+
message.content[i] = {
|
|
78
|
+
type: "text",
|
|
79
|
+
text:
|
|
80
|
+
caption != null
|
|
81
|
+
? `[Image: ${caption}]${pathSuffix}`
|
|
82
|
+
: `[Image: captioning failed — unable to describe]${pathSuffix}`,
|
|
83
|
+
};
|
|
84
|
+
} else {
|
|
85
|
+
// No vision profile configured at all — fail-open placeholder.
|
|
86
|
+
const pathSuffix = savedPath != null ? ` (saved to ${savedPath})` : "";
|
|
87
|
+
message.content[i] = {
|
|
88
|
+
type: "text",
|
|
89
|
+
text: `[Image: no vision-capable model configured to describe this image]${pathSuffix}`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (imageCount > 0) {
|
|
96
|
+
ctx.logger.info(
|
|
97
|
+
{ plugin: "image-fallback", imageCount },
|
|
98
|
+
"Replaced image blocks with text captions for text-only model",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export default userPromptSubmit;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "default-image-fallback",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "First-party default plugin that captions image attachments via a vision-capable profile when the active model is text-only, substituting the caption as a text block so the model can still reason about the image's content.",
|
|
5
|
+
"private": true,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20.12.0"
|
|
10
|
+
},
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"@vellumai/plugin-api": "^0.8.0"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory LRU cache for image captions, keyed by the sha-256 hash of the
|
|
3
|
+
* image's base64 data.
|
|
4
|
+
*
|
|
5
|
+
* The cache survives across turns within a session, which is the primary
|
|
6
|
+
* correctness concern: without it, the same image would be re-captioned
|
|
7
|
+
* (and re-billed) on every turn because `user-prompt-submit` rebuilds
|
|
8
|
+
* `latestMessages` from the stored conversation each time. Re-captioning
|
|
9
|
+
* on a daemon restart is acceptable — captions are cheap and the image
|
|
10
|
+
* is still in the history.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
|
|
15
|
+
const MAX_ENTRIES = 500;
|
|
16
|
+
|
|
17
|
+
const cache = new Map<string, string>();
|
|
18
|
+
|
|
19
|
+
/** sha-256 hex digest of an image's base64 payload. */
|
|
20
|
+
export function imageHash(data: string): string {
|
|
21
|
+
return createHash("sha256").update(data).digest("hex");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Look up a cached caption. `undefined` = miss; a string (even empty) = hit. */
|
|
25
|
+
export function getCachedCaption(hash: string): string | undefined {
|
|
26
|
+
const value = cache.get(hash);
|
|
27
|
+
if (value !== undefined) {
|
|
28
|
+
// Move to end (most-recently-used) for LRU eviction.
|
|
29
|
+
cache.delete(hash);
|
|
30
|
+
cache.set(hash, value);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Store a caption, evicting the least-recently-used entry if at capacity. */
|
|
36
|
+
export function setCachedCaption(hash: string, caption: string): void {
|
|
37
|
+
if (cache.size >= MAX_ENTRIES) {
|
|
38
|
+
const oldest = cache.keys().next();
|
|
39
|
+
if (!oldest.done) {
|
|
40
|
+
cache.delete(oldest.value as string);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
cache.set(hash, caption);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Test-only: clear the cache. */
|
|
47
|
+
export function resetCaptionCacheForTests(): void {
|
|
48
|
+
cache.clear();
|
|
49
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persist image data to the workspace attachments directory.
|
|
3
|
+
*
|
|
4
|
+
* When the active model is text-only, the image-fallback plugin captions the
|
|
5
|
+
* image and substitutes a text block. Saving the raw image to disk and
|
|
6
|
+
* referencing the path in the caption text means a future turn with a
|
|
7
|
+
* vision-capable model (or a subagent) could still access the original image
|
|
8
|
+
* via file_read, and the user can find the image at a known location.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
|
|
14
|
+
import { imageHash } from "./caption-cache.js";
|
|
15
|
+
|
|
16
|
+
/** The workspace attachments directory. */
|
|
17
|
+
const ATTACHMENTS_DIR = "/workspace/data/attachments";
|
|
18
|
+
|
|
19
|
+
/** File extension for a given media type, falling back to `.bin`. */
|
|
20
|
+
function extensionForMediaType(mediaType: string): string {
|
|
21
|
+
switch (mediaType) {
|
|
22
|
+
case "image/png":
|
|
23
|
+
return ".png";
|
|
24
|
+
case "image/jpeg":
|
|
25
|
+
return ".jpg";
|
|
26
|
+
case "image/gif":
|
|
27
|
+
return ".gif";
|
|
28
|
+
case "image/webp":
|
|
29
|
+
return ".webp";
|
|
30
|
+
default:
|
|
31
|
+
return ".bin";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Save an image's base64 data to the attachments dir if not already present.
|
|
37
|
+
* Returns the absolute file path, or `null` when the write fails.
|
|
38
|
+
*/
|
|
39
|
+
export function persistImage(
|
|
40
|
+
data: string,
|
|
41
|
+
mediaType: string,
|
|
42
|
+
): string | null {
|
|
43
|
+
try {
|
|
44
|
+
mkdirSync(ATTACHMENTS_DIR, { recursive: true });
|
|
45
|
+
|
|
46
|
+
const hash = imageHash(data);
|
|
47
|
+
const ext = extensionForMediaType(mediaType);
|
|
48
|
+
const filename = `${hash}${ext}`;
|
|
49
|
+
const filepath = join(ATTACHMENTS_DIR, filename);
|
|
50
|
+
|
|
51
|
+
// Skip if already saved (content-hash dedup).
|
|
52
|
+
if (existsSync(filepath)) return filepath;
|
|
53
|
+
|
|
54
|
+
writeFileSync(filepath, Buffer.from(data, "base64"));
|
|
55
|
+
return filepath;
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vision-based image captioning for the image-fallback plugin.
|
|
3
|
+
*
|
|
4
|
+
* When the active model cannot process images, this module finds a
|
|
5
|
+
* vision-capable profile in the workspace's configured profiles and runs a
|
|
6
|
+
* one-shot captioning call through the assistant's own inference (no
|
|
7
|
+
* plugin-supplied API key). The caption replaces the image block in the
|
|
8
|
+
* outgoing message history.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
doesSupportVision,
|
|
13
|
+
getConfiguredProvider,
|
|
14
|
+
getModelProfiles,
|
|
15
|
+
type ImageContent,
|
|
16
|
+
type PluginLogger,
|
|
17
|
+
} from "@vellumai/plugin-api";
|
|
18
|
+
|
|
19
|
+
import { extractAllText } from "../../../../providers/provider-send-message.js";
|
|
20
|
+
import {
|
|
21
|
+
getCachedCaption,
|
|
22
|
+
imageHash,
|
|
23
|
+
setCachedCaption,
|
|
24
|
+
} from "./caption-cache.js";
|
|
25
|
+
|
|
26
|
+
const CAPTION_TIMEOUT_MS = 30_000;
|
|
27
|
+
|
|
28
|
+
const CAPTION_SYSTEM_PROMPT =
|
|
29
|
+
"You are a vision assistant. Describe the image concisely in 1-2 sentences. " +
|
|
30
|
+
"Focus on the key visual content, text, charts, or UI elements that would be " +
|
|
31
|
+
"relevant for a text-based assistant to understand and reason about.";
|
|
32
|
+
|
|
33
|
+
const CAPTION_USER_PROMPT = "Describe this image concisely for a text-only assistant.";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Find a vision-capable, enabled profile key for captioning.
|
|
37
|
+
*
|
|
38
|
+
* Scans the workspace's profiles in `getModelProfiles()` order (the same order
|
|
39
|
+
* the `/model` picker shows them) and returns the first enabled profile whose
|
|
40
|
+
* resolved model supports vision. Returns `null` when no vision profile exists
|
|
41
|
+
* — the hook fails-open in that case, leaving a placeholder text block.
|
|
42
|
+
*/
|
|
43
|
+
export function findVisionProfile(): string | null {
|
|
44
|
+
for (const profile of getModelProfiles()) {
|
|
45
|
+
if (profile.isDisabled) continue;
|
|
46
|
+
if (doesSupportVision(profile)) {
|
|
47
|
+
return profile.key;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Caption a single image block via a vision-capable profile.
|
|
55
|
+
*
|
|
56
|
+
* @param image The image content block to caption.
|
|
57
|
+
* @param profileKey Key of a vision-capable profile (from {@link findVisionProfile}).
|
|
58
|
+
* @param logger Turn-scoped logger for attribution.
|
|
59
|
+
* @returns The caption text, or `null` when captioning failed (caller should
|
|
60
|
+
* use a fail-open placeholder).
|
|
61
|
+
*/
|
|
62
|
+
export async function captionImage(
|
|
63
|
+
image: ImageContent,
|
|
64
|
+
profileKey: string,
|
|
65
|
+
logger: PluginLogger,
|
|
66
|
+
): Promise<string | null> {
|
|
67
|
+
const hash = imageHash(image.source.data);
|
|
68
|
+
const cached = getCachedCaption(hash);
|
|
69
|
+
if (cached !== undefined) {
|
|
70
|
+
return cached;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const provider = await getConfiguredProvider("vision", {
|
|
75
|
+
overrideProfile: profileKey,
|
|
76
|
+
forceOverrideProfile: true,
|
|
77
|
+
});
|
|
78
|
+
if (!provider) {
|
|
79
|
+
logger.warn(
|
|
80
|
+
{ plugin: "image-fallback" },
|
|
81
|
+
"No provider resolved for vision captioning profile",
|
|
82
|
+
);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const response = await provider.sendMessage(
|
|
87
|
+
[
|
|
88
|
+
{
|
|
89
|
+
role: "user",
|
|
90
|
+
content: [image, { type: "text", text: CAPTION_USER_PROMPT }],
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
{
|
|
94
|
+
systemPrompt: CAPTION_SYSTEM_PROMPT,
|
|
95
|
+
config: {
|
|
96
|
+
callSite: "vision",
|
|
97
|
+
overrideProfile: profileKey,
|
|
98
|
+
forceOverrideProfile: true,
|
|
99
|
+
tool_choice: { type: "none" },
|
|
100
|
+
},
|
|
101
|
+
signal: AbortSignal.timeout(CAPTION_TIMEOUT_MS),
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const caption = extractAllText(response).trim();
|
|
106
|
+
if (caption.length > 0) {
|
|
107
|
+
setCachedCaption(hash, caption);
|
|
108
|
+
return caption;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
logger.warn({ plugin: "image-fallback" }, "Vision captioning returned empty text");
|
|
112
|
+
return null;
|
|
113
|
+
} catch (err) {
|
|
114
|
+
logger.warn(
|
|
115
|
+
{ plugin: "image-fallback", err },
|
|
116
|
+
"Vision captioning call failed",
|
|
117
|
+
);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|