@vellumai/assistant 0.11.3 → 0.11.4-dev.202608190019.b94dbf2
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/AGENTS.md +9 -3
- package/ARCHITECTURE.md +32 -10
- package/docs/architecture/memory.md +43 -5
- package/docs/architecture/security.md +96 -152
- package/docs/architecture/turn-actor.md +70 -0
- package/docs/browser-use-architecture-phase2.md +128 -56
- package/docs/flux-turn-detection-spike.md +248 -0
- package/docs/guardian-request-flow.md +35 -0
- package/docs/stt-provider-onboarding.md +3 -1
- package/knip.json +3 -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/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -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/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -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/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -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/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +289 -113
- package/package.json +1 -1
- package/scripts/voice-ttft-spike.ts +3 -3
- package/scripts/write-plugin-api-shim.ts +10 -0
- package/src/__tests__/app-compiler.test.ts +38 -3
- package/src/__tests__/app-control-flow.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +2 -2
- package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
- package/src/__tests__/attachments-store.test.ts +21 -12
- package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
- package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
- package/src/__tests__/call-site-routing-provider.test.ts +1 -1
- package/src/__tests__/channel-availability-routes.test.ts +14 -1
- package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
- package/src/__tests__/channel-delivery-store.test.ts +14 -14
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
- package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/compaction-events.test.ts +8 -10
- package/src/__tests__/config-loader-backfill.test.ts +3 -3
- package/src/__tests__/config-schema.test.ts +25 -10
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop.test.ts +32 -21
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-attention-store.test.ts +63 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
- package/src/__tests__/conversation-fork-crud.test.ts +69 -0
- package/src/__tests__/conversation-fork-referential.test.ts +67 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
- package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
- package/src/__tests__/conversation-queue.test.ts +154 -4
- 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-guardian-reply.test.ts +9 -9
- 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-runtime-assembly.test.ts +187 -102
- package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
- package/src/__tests__/conversation-slash-queue.test.ts +3 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
- package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +7 -10
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/custom-profile-ensure.test.ts +5 -1
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
- package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
- package/src/__tests__/document-append-idempotency.test.ts +233 -0
- package/src/__tests__/document-sync-tags.test.ts +0 -75
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -11
- package/src/__tests__/file-ops-service.test.ts +163 -30
- package/src/__tests__/filesystem-tools.test.ts +23 -24
- package/src/__tests__/gateway-only-guard.test.ts +2 -5
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/helpers/mock-actor-context.ts +49 -0
- package/src/__tests__/helpers/mock-conversation.ts +13 -1
- package/src/__tests__/host-file-read-tool.test.ts +16 -19
- package/src/__tests__/http-user-message-parity.test.ts +1 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
- package/src/__tests__/injector-chain.test.ts +63 -41
- package/src/__tests__/injector-disk-pressure.test.ts +11 -23
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/llm-context-resolution.test.ts +73 -1
- package/src/__tests__/llm-schema.test.ts +5 -2
- package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
- package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
- package/src/__tests__/media-generate-image.test.ts +131 -21
- package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
- package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
- package/src/__tests__/mtime-cache.test.ts +3 -1
- package/src/__tests__/non-member-access-request.test.ts +0 -20
- package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
- package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
- package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
- package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
- package/src/__tests__/proxy-approval-callback.test.ts +1 -0
- package/src/__tests__/qdrant-manager.test.ts +14 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
- package/src/__tests__/run-due-schedules.test.ts +21 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
- package/src/__tests__/schedule-routes.test.ts +23 -0
- package/src/__tests__/schedule-store.test.ts +17 -0
- package/src/__tests__/scoped-approval-grants.test.ts +11 -6
- package/src/__tests__/secret-ingress-channel.test.ts +0 -1
- package/src/__tests__/secret-ingress-http.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +3 -3
- package/src/__tests__/skills.test.ts +32 -0
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
- package/src/__tests__/starter-task-flow.test.ts +5 -4
- package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
- package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
- package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
- package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
- package/src/__tests__/subagent-tools.test.ts +81 -101
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +68 -39
- package/src/__tests__/turn-events-store.test.ts +43 -0
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
- package/src/__tests__/ui-shape-teaching.test.ts +33 -0
- package/src/__tests__/ui-visual-surface.test.ts +1 -1
- package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
- package/src/__tests__/ui-work-result-surface.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +3 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/visible-app-context.test.ts +16 -9
- package/src/__tests__/voice-config-update.test.ts +40 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
- package/src/__tests__/voice-session-bridge.test.ts +85 -29
- package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
- package/src/__tests__/worker-plugin-surface.test.ts +77 -0
- package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
- package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
- package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
- package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
- package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
- package/src/acp/__tests__/auth-required.test.ts +161 -0
- package/src/acp/acp-claude-oauth.ts +19 -2
- package/src/acp/agent-process.test.ts +100 -0
- package/src/acp/agent-process.ts +29 -26
- package/src/acp/auth-required.ts +102 -0
- package/src/acp/session-manager.test.ts +119 -0
- package/src/acp/session-manager.ts +76 -3
- package/src/api/events/acp-auth-required.ts +55 -0
- package/src/api/events/host-file.ts +2 -2
- package/src/api/index.ts +7 -0
- package/src/api/surfaces.ts +12 -3
- package/src/apps/app-store.ts +3 -0
- package/src/bundler/package-resolver.ts +2 -30
- package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
- package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
- package/src/calls/call-controller.ts +19 -3
- package/src/calls/call-setup-flow.ts +0 -1
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/calls/voice-session-bridge.ts +115 -41
- package/src/calls/voice-triage-escalate.ts +105 -2
- package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
- package/src/channels/config.ts +13 -0
- package/src/channels/plugin-channel-declarations.ts +108 -0
- package/src/channels/types.ts +30 -0
- package/src/cli/AGENTS.md +5 -2
- package/src/cli/bundled-modules.ts +29 -0
- package/src/cli/commands/credentials.help.ts +2 -2
- package/src/cli/commands/db/repair.ts +4 -8
- package/src/cli/commands/domain.ts +6 -3
- package/src/cli/commands/email.ts +6 -3
- package/src/cli/commands/inference-providers.ts +1 -1
- package/src/cli/commands/keys.ts +8 -3
- package/src/cli/commands/mcp.help.ts +13 -4
- package/src/cli/commands/mcp.ts +9 -0
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
- package/src/cli/commands/memory/index.help.ts +84 -28
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-v3.ts +64 -0
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/plugins.ts +85 -36
- package/src/cli/commands/schedules.ts +35 -1
- package/src/cli/commands/stt.help.ts +27 -2
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
- package/src/cli/lib/bundled-marketplace.json +14 -1
- package/src/cli/lib/upgrade-plugin.ts +42 -0
- package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
- package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
- package/src/config/__tests__/default-provider.test.ts +6 -1
- package/src/config/__tests__/profile-materialization.test.ts +75 -19
- package/src/config/assistant-feature-flags.ts +36 -15
- package/src/config/balanced-model-experiment.ts +35 -0
- package/src/config/bundled-skills/acp/SKILL.md +6 -7
- package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
- package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
- package/src/config/bundled-skills/subagent/SKILL.md +17 -12
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
- package/src/config/call-site-defaults.ts +11 -5
- package/src/config/default-profile-catalog.ts +179 -16
- package/src/config/default-profile-names.ts +4 -1
- package/src/config/default-provider-resolution.ts +4 -0
- package/src/config/feature-flag-registry.json +12 -12
- package/src/config/llm-context-resolution.ts +11 -3
- package/src/config/llm-resolver.ts +28 -1
- package/src/config/profile-materialization.ts +70 -22
- package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
- package/src/config/schemas/call-site-catalog.ts +4 -4
- package/src/config/schemas/live-voice.ts +57 -23
- package/src/config/schemas/llm.ts +59 -32
- package/src/config/schemas/mcp.ts +23 -0
- package/src/config/schemas/plugin-updates.ts +6 -2
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/skills.ts +9 -2
- package/src/context/outbound-sanitize.ts +96 -1
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
- package/src/daemon/conversation-agent-loop.ts +31 -19
- package/src/daemon/conversation-messaging.ts +5 -1
- package/src/daemon/conversation-notifiers.ts +20 -10
- package/src/daemon/conversation-process.ts +20 -6
- package/src/daemon/conversation-runtime-assembly.ts +12 -6
- package/src/daemon/conversation-store.ts +14 -4
- package/src/daemon/conversation-surfaces.ts +75 -26
- package/src/daemon/conversation-tool-setup.ts +3 -6
- package/src/daemon/conversation.ts +154 -51
- package/src/daemon/doordash-steps.ts +2 -2
- package/src/daemon/lifecycle.ts +14 -1
- package/src/daemon/mcp-reload-service.ts +36 -6
- package/src/daemon/process-message.ts +13 -26
- package/src/daemon/providers-setup.ts +6 -3
- package/src/daemon/trust-context-types.ts +29 -0
- package/src/daemon/trust-context.ts +12 -7
- package/src/daemon/wake-conversation-ops.ts +3 -2
- package/src/daemon/windows-compiled-entry.ts +4 -0
- package/src/documents/document-store.ts +143 -240
- package/src/hooks/hook-loader.ts +3 -3
- package/src/hooks/registry.ts +103 -45
- package/src/hooks/types.ts +5 -0
- package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
- package/src/inbound/oauth-callback-url.ts +61 -0
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/gateway-flag-listener.ts +17 -3
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
- package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
- package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
- package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
- package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
- package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
- package/src/live-voice/live-voice-archive.ts +2 -0
- package/src/live-voice/live-voice-manager.ts +16 -3
- package/src/live-voice/live-voice-metrics.ts +57 -32
- package/src/live-voice/live-voice-photo.ts +1 -2
- package/src/live-voice/live-voice-session.ts +598 -323
- package/src/live-voice/progress-narration.ts +277 -0
- package/src/live-voice/protocol.ts +21 -1
- package/src/live-voice/windows-compiled-live-voice.ts +4 -0
- package/src/mcp/__tests__/effective-config.test.ts +238 -0
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
- package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
- package/src/mcp/client.ts +16 -11
- package/src/mcp/effective-config.ts +113 -0
- package/src/mcp/manager.ts +11 -6
- package/src/mcp/mcp-auth-orchestrator.ts +13 -22
- package/src/mcp/mcp-oauth-provider.ts +205 -240
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
- package/src/monitoring/control.ts +1 -0
- package/src/monitoring/db-integrity-sample.ts +4 -5
- package/src/monitoring/plugin-auto-update.ts +128 -24
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/approval-card-data.ts +33 -0
- package/src/notifications/signal.ts +1 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +15 -11
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/prompter.ts +1 -5
- package/src/permissions/question-guardian-request.test.ts +14 -6
- package/src/permissions/question-guardian-request.ts +1 -2
- package/src/persistence/attachments-store.ts +8 -1
- package/src/persistence/bookmark-crud.ts +3 -7
- package/src/persistence/conversation-attention-store.ts +16 -45
- package/src/persistence/conversation-crud.ts +33 -4
- package/src/persistence/conversation-lineage.ts +9 -0
- package/src/persistence/conversation-queries.ts +182 -52
- package/src/persistence/delivery-crud.ts +150 -36
- package/src/persistence/embeddings/qdrant-manager.ts +84 -49
- package/src/persistence/external-conversation-store.ts +32 -4
- package/src/persistence/llm-request-log-store.ts +4 -10
- package/src/persistence/llm-usage-store.ts +8 -3
- package/src/persistence/message-reads.test.ts +197 -0
- package/src/persistence/message-reads.ts +211 -0
- package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
- package/src/persistence/real-user-turn-filter.ts +27 -3
- package/src/persistence/schema/documents.ts +4 -4
- package/src/persistence/steps.ts +9 -0
- package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
- package/src/plugin-api/constants.ts +8 -0
- package/src/plugin-api/conversation-turn.ts +195 -5
- package/src/plugin-api/index.ts +38 -6
- package/src/plugin-api/resolve-credential.ts +3 -2
- package/src/plugin-api/store-credential.ts +146 -0
- package/src/plugin-api/vision-support.test.ts +1 -1
- package/src/plugin-api/webhook-url.ts +13 -11
- package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
- package/src/plugins/defaults/main.ts +21 -7
- package/src/plugins/defaults/memory/AGENTS.md +15 -3
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/buffer-format.ts +165 -0
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
- package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
- package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
- package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
- package/src/plugins/defaults/memory/indexer.ts +3 -1
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
- package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
- package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/defaults/memory/v3/injector.ts +8 -0
- package/src/plugins/defaults/memory/v3/prune.ts +2 -0
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
- package/src/plugins/defaults/memory/worker-control.ts +1 -0
- package/src/plugins/defaults/memory/worker.ts +6 -3
- package/src/plugins/defaults/worker-entrypoints.ts +3 -0
- package/src/plugins/external-plugin-loader.ts +47 -0
- package/src/plugins/mcp-servers.ts +361 -0
- package/src/plugins/mtime-cache.ts +37 -46
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/worker-plugin-surface.ts +33 -0
- package/src/prompts/templates/system-sections.ts +0 -7
- package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
- package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
- package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
- package/src/providers/__tests__/retry-callsite.test.ts +25 -2
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/call-site-routing.ts +30 -3
- package/src/providers/connection-resolution.ts +194 -11
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/inference/auth.ts +6 -0
- package/src/providers/inference/connection-availability.ts +24 -2
- package/src/providers/inference/connections.ts +2 -0
- package/src/providers/model-catalog.ts +3 -3
- package/src/providers/model-intents.ts +28 -8
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/chat-completions-provider.ts +133 -36
- package/src/providers/openai/codex-models.ts +2 -1
- package/src/providers/provider-send-message.ts +32 -3
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
- package/src/providers/speech-to-text/provider-catalog.ts +99 -8
- package/src/providers/speech-to-text/resolve.ts +25 -2
- package/src/routes/control.ts +1 -0
- package/src/routes/route-host-client.ts +1 -0
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +48 -15
- package/src/runtime/AGENTS.md +16 -17
- package/src/runtime/access-request-helper.ts +9 -12
- package/src/runtime/agent-wake.ts +18 -15
- package/src/runtime/effective-capabilities.test.ts +43 -0
- package/src/runtime/effective-capabilities.ts +27 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/pre-first-message-gate.ts +4 -0
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
- package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
- package/src/runtime/routes/channel-availability-routes.ts +32 -14
- package/src/runtime/routes/channel-route-shared.ts +0 -6
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
- package/src/runtime/routes/conversation-list-routes.ts +181 -23
- package/src/runtime/routes/conversation-management-routes.ts +17 -12
- package/src/runtime/routes/conversation-query-routes.ts +40 -27
- package/src/runtime/routes/conversation-routes.ts +43 -29
- package/src/runtime/routes/credential-prompt-routes.ts +4 -7
- package/src/runtime/routes/credential-routes.ts +26 -82
- package/src/runtime/routes/default-provider-routes.ts +15 -0
- package/src/runtime/routes/documents-routes.ts +3 -222
- package/src/runtime/routes/inbound-message-handler.ts +21 -45
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
- package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
- package/src/runtime/routes/inference-profiles-routes.ts +20 -11
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/mcp-auth-routes.ts +148 -57
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
- package/src/runtime/routes/playground/inject-failures.ts +2 -2
- package/src/runtime/routes/playground/reset-circuit.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +38 -8
- package/src/runtime/routes/schedule-routes.ts +94 -7
- package/src/runtime/routes/settings-routes.ts +6 -6
- package/src/runtime/routes/stt-routes.ts +31 -25
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
- package/src/runtime/routes/user-route-dispatcher.ts +69 -15
- package/src/runtime/routes/user-route-import.ts +108 -0
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/routes/workspace-routes.ts +0 -9
- package/src/runtime/routes/workspace-utils.ts +3 -13
- package/src/runtime/services/conversation-serializer.ts +7 -2
- package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
- package/src/schedule/plugin-schedule-availability.ts +58 -0
- package/src/schedule/plugin-schedule-declarations.ts +23 -27
- package/src/schedule/plugin-schedule-reconciler.ts +12 -3
- package/src/schedule/schedule-store.ts +5 -1
- package/src/schedule/scheduler.ts +9 -4
- package/src/schedule/worker-control.ts +1 -0
- package/src/schedule/worker.ts +6 -0
- package/src/security/oauth2.ts +6 -22
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
- package/src/stt/__tests__/types.test.ts +94 -0
- package/src/stt/daemon-batch-transcriber.ts +10 -0
- package/src/stt/stt-stream-session.ts +8 -4
- package/src/stt/types.ts +103 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
- package/src/subagent/consult-context.ts +11 -11
- package/src/subagent/consult-prompt.ts +26 -35
- package/src/subagent/manager.ts +21 -40
- package/src/subagent/notify.ts +7 -1
- package/src/subagent/types.ts +15 -12
- package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
- package/src/tools/acp/spawn.test.ts +97 -0
- package/src/tools/acp/spawn.ts +38 -4
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/document/document-tool.ts +12 -3
- package/src/tools/executor.ts +53 -15
- package/src/tools/filesystem/read.ts +27 -10
- package/src/tools/host-filesystem/read.ts +27 -15
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/registry.ts +2 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
- package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
- package/src/tools/shared/filesystem/types.ts +5 -5
- package/src/tools/skills/scaffold-managed.ts +25 -7
- package/src/tools/subagent/spawn.ts +28 -88
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
- package/src/tools/workflows/run-workflow.ts +1 -2
- package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
- package/src/tts/reasoning-tag-filter.ts +89 -0
- package/src/util/__tests__/worker-process-command.test.ts +37 -0
- package/src/util/logger.ts +16 -0
- package/src/util/think-tag-stream.ts +95 -0
- package/src/util/worker-process.ts +37 -4
- package/src/windows-compiled-cli.ts +32 -0
- package/src/windows-compiled-entry.ts +4 -0
- package/src/windows-compiled-logger.ts +6 -0
- package/src/windows-compiled-worker-entry.ts +29 -0
- package/src/workspace/byok-default-profile-ensure.ts +76 -24
- package/src/workspace/custom-profile-ensure.ts +4 -24
- package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
- package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
- package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
- package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
- package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/provider-commit-message-generator.ts +7 -5
- package/src/__tests__/document-workspace-file.test.ts +0 -467
- package/src/live-voice/__tests__/front-decision.test.ts +0 -645
- package/src/live-voice/front-decision.ts +0 -476
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
- package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
- package/src/subagent/consult-transcript.ts +0 -90
|
@@ -1,102 +1,182 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `runConversationTurn` against the real conversation store, the real agent
|
|
3
|
+
* loop and the real database. The only thing stood in for is the provider's
|
|
4
|
+
* HTTP boundary — `resolveProviderFromConnection` hands back a scripted mock
|
|
5
|
+
* — so everything these tests read back (the `conversations` row, the user
|
|
6
|
+
* message and the metadata stamped on it, the channel binding) is what
|
|
7
|
+
* production wrote, not a stub mirroring what production is believed to do.
|
|
8
|
+
*/
|
|
9
|
+
import {
|
|
10
|
+
afterEach,
|
|
11
|
+
beforeEach,
|
|
12
|
+
describe,
|
|
13
|
+
expect,
|
|
14
|
+
mock,
|
|
15
|
+
spyOn,
|
|
16
|
+
test,
|
|
17
|
+
} from "bun:test";
|
|
2
18
|
|
|
19
|
+
import type { AssistantEvent } from "../api/index.js";
|
|
20
|
+
import { clearAllActiveConversations } from "../daemon/conversation-store.js";
|
|
21
|
+
import {
|
|
22
|
+
conversationMetadataSyncTag,
|
|
23
|
+
SYNC_TAGS,
|
|
24
|
+
} from "../daemon/message-types/sync.js";
|
|
3
25
|
import {
|
|
4
26
|
createConversation,
|
|
5
|
-
ensureConversationExists,
|
|
6
27
|
getConversation,
|
|
28
|
+
getMessages,
|
|
29
|
+
parseMessageMetadata,
|
|
7
30
|
} from "../persistence/conversation-crud.js";
|
|
31
|
+
import { getConversationByKey } from "../persistence/conversation-key-store.js";
|
|
8
32
|
import {
|
|
9
33
|
isEchoSuppressedUserMessage,
|
|
10
34
|
isReplyPushIneligibleUserMessage,
|
|
11
35
|
} from "../persistence/conversation-types.js";
|
|
12
36
|
import { getDb } from "../persistence/db-connection.js";
|
|
13
37
|
import { initializeDb } from "../persistence/db-init.js";
|
|
38
|
+
import { buildScopedConversationKey } from "../persistence/delivery-crud.js";
|
|
39
|
+
import { getBindingByConversation } from "../persistence/external-conversation-store.js";
|
|
40
|
+
import { createConnection } from "../providers/inference/connections.js";
|
|
41
|
+
import * as providerRegistry from "../providers/registry.js";
|
|
42
|
+
import type { Provider, ProviderResponse } from "../providers/types.js";
|
|
43
|
+
import { createMockProvider, textResponse } from "./helpers/mock-provider.js";
|
|
44
|
+
import { setConfig } from "./helpers/set-config.js";
|
|
45
|
+
import { waitFor } from "./helpers/wait-for.js";
|
|
14
46
|
|
|
15
47
|
await initializeDb();
|
|
16
48
|
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
|
-
// Stub the heavy machinery: the in-memory conversation build (provider wiring,
|
|
31
|
-
// system prompt, history hydration) and the SSE event fan-out. The agent turn
|
|
32
|
-
// itself is a no-op — this test only asserts that the `conversations` row is
|
|
33
|
-
// persisted before the turn runs. The real `getOrCreateConversation` now
|
|
34
|
-
// creates the DB row before hydrating, so the mock mirrors that by calling
|
|
35
|
-
// `ensureConversationExists`. The persistence module is intentionally NOT
|
|
36
|
-
// mocked so the real `ensureConversationExists` runs against the real DB.
|
|
37
|
-
let lastProcessMessageConversationId: string | undefined;
|
|
38
|
-
let lastProcessMessageOptions: Record<string, unknown> | undefined;
|
|
39
|
-
let lastEnqueueOptions: Record<string, unknown> | undefined;
|
|
40
|
-
let conversationIsProcessing = false;
|
|
41
|
-
mock.module("../daemon/conversation-store.js", () => ({
|
|
42
|
-
getOrCreateConversation: async (
|
|
43
|
-
conversationId: string,
|
|
44
|
-
options?: { conversationType?: string },
|
|
45
|
-
) => {
|
|
46
|
-
if (!getConversation(conversationId)) {
|
|
47
|
-
if (options?.conversationType) {
|
|
48
|
-
createConversation({
|
|
49
|
-
id: conversationId,
|
|
50
|
-
conversationType: options.conversationType as
|
|
51
|
-
| "standard"
|
|
52
|
-
| "background",
|
|
53
|
-
});
|
|
54
|
-
} else {
|
|
55
|
-
ensureConversationExists(conversationId, "vellum");
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
abortController: undefined,
|
|
60
|
-
isProcessing: () => conversationIsProcessing,
|
|
61
|
-
async processMessage(processOptions: Record<string, unknown>) {
|
|
62
|
-
// The row must already exist by the time the turn persists its user
|
|
63
|
-
// message — record the id so the FK precondition can be asserted.
|
|
64
|
-
lastProcessMessageConversationId = conversationId;
|
|
65
|
-
lastProcessMessageOptions = processOptions;
|
|
66
|
-
return "user-message-id";
|
|
67
|
-
},
|
|
68
|
-
enqueueMessage: (enqueueOptions: Record<string, unknown>) => {
|
|
69
|
-
lastEnqueueOptions = enqueueOptions;
|
|
70
|
-
return { rejected: false };
|
|
71
|
-
},
|
|
72
|
-
};
|
|
49
|
+
// A conversation is built against the default provider, which resolves from
|
|
50
|
+
// config through a connection row. Both are seeded for real so the store takes
|
|
51
|
+
// the same path it takes in the daemon; only the provider the connection
|
|
52
|
+
// resolves to is scripted.
|
|
53
|
+
setConfig("llm", {
|
|
54
|
+
profiles: {
|
|
55
|
+
test: {
|
|
56
|
+
provider: "anthropic",
|
|
57
|
+
provider_connection: "test-conn",
|
|
58
|
+
model: "claude-opus-4-6",
|
|
59
|
+
},
|
|
73
60
|
},
|
|
74
|
-
|
|
61
|
+
activeProfile: "test",
|
|
62
|
+
});
|
|
63
|
+
setConfig("memory", { enabled: false, v2: { enabled: false } });
|
|
64
|
+
|
|
65
|
+
createConnection(getDb(), {
|
|
66
|
+
name: "test-conn",
|
|
67
|
+
provider: "anthropic",
|
|
68
|
+
auth: { type: "api_key", credential: "credential/test/api_key" },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// The SSE fan-out is the one collaborator with nowhere to write in a test —
|
|
72
|
+
// every publish the turn makes (including the list invalidations asserted
|
|
73
|
+
// below, which reach clients through the real `resource-sync-events`) funnels
|
|
74
|
+
// through here.
|
|
75
|
+
const broadcasts: AssistantEvent[] = [];
|
|
76
|
+
mock.module(
|
|
77
|
+
"../runtime/assistant-event-hub.js",
|
|
78
|
+
(): Partial<typeof import("../runtime/assistant-event-hub.js")> => ({
|
|
79
|
+
broadcastMessage: (msg: AssistantEvent) => {
|
|
80
|
+
broadcasts.push(msg);
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
75
84
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
const { provider: scriptedProvider } = createMockProvider([
|
|
86
|
+
textResponse("scripted reply"),
|
|
87
|
+
]);
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
let gate: Promise<void> | undefined;
|
|
90
|
+
let releaseGate: (() => void) | undefined;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Park the next turn inside the provider call until released, so a second turn
|
|
94
|
+
* arrives while the conversation is genuinely processing rather than while a
|
|
95
|
+
* stub reports that it is.
|
|
96
|
+
*/
|
|
97
|
+
function holdTheTurnOpen(): void {
|
|
98
|
+
let resolveGate!: () => void;
|
|
99
|
+
gate = new Promise<void>((resolve) => {
|
|
100
|
+
resolveGate = resolve;
|
|
101
|
+
});
|
|
102
|
+
releaseGate = () => {
|
|
103
|
+
gate = undefined;
|
|
104
|
+
releaseGate = undefined;
|
|
105
|
+
resolveGate();
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// A test that fails before releasing must not leave the next one parked on a
|
|
110
|
+
// gate it never opened.
|
|
111
|
+
afterEach(() => releaseGate?.());
|
|
112
|
+
|
|
113
|
+
const providerCalls: { tools?: { name: string }[] }[] = [];
|
|
114
|
+
|
|
115
|
+
const provider: Provider = {
|
|
116
|
+
name: "scripted",
|
|
117
|
+
async sendMessage(messages, options): Promise<ProviderResponse> {
|
|
118
|
+
providerCalls.push({ tools: options?.tools });
|
|
119
|
+
await gate;
|
|
120
|
+
return scriptedProvider.sendMessage(messages, options);
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
spyOn(providerRegistry, "resolveProviderFromConnection").mockResolvedValue(
|
|
125
|
+
provider,
|
|
126
|
+
);
|
|
83
127
|
|
|
84
|
-
// Import under test AFTER the mocks are registered so its dynamic imports
|
|
85
|
-
// resolve to the stubs above.
|
|
86
128
|
const { runConversationTurn } =
|
|
87
129
|
await import("../plugin-api/conversation-turn.js");
|
|
88
130
|
|
|
131
|
+
/**
|
|
132
|
+
* The list-level invalidations a turn published, as the tag sets clients
|
|
133
|
+
* consume. Only a shape-changing reason carries the list umbrella tag, so a
|
|
134
|
+
* reason that merely edits an existing row (a generated title) is excluded —
|
|
135
|
+
* these are the announcements that a conversation appeared or vanished.
|
|
136
|
+
*/
|
|
137
|
+
function listInvalidations(): string[][] {
|
|
138
|
+
return broadcasts
|
|
139
|
+
.filter((msg) => msg.type === "sync_changed")
|
|
140
|
+
.map((msg) => msg.tags)
|
|
141
|
+
.filter((tags) => tags.includes(SYNC_TAGS.conversationsList));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** The tag set `publishConversationListAndMetadataChanged` emits for a new row. */
|
|
145
|
+
function creationTags(conversationId: string): string[] {
|
|
146
|
+
return [
|
|
147
|
+
SYNC_TAGS.conversationsList,
|
|
148
|
+
conversationMetadataSyncTag(conversationId),
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** User message rows of a conversation, oldest first. */
|
|
153
|
+
function userRows(conversationId: string) {
|
|
154
|
+
return getMessages(conversationId).filter((row) => row.role === "user");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The metadata a turn persisted on a message row, parsed as consumers read it. */
|
|
158
|
+
function metadataOf(
|
|
159
|
+
row: { metadata: string | null } | undefined,
|
|
160
|
+
): Record<string, unknown> | undefined {
|
|
161
|
+
return parseMessageMetadata(row?.metadata ?? null);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function resetDb(): void {
|
|
165
|
+
// In-memory conversations outlive the rows they were hydrated from, and a
|
|
166
|
+
// reused id would otherwise hand the next test a Conversation whose
|
|
167
|
+
// `conversations` row this wipe just deleted.
|
|
168
|
+
clearAllActiveConversations();
|
|
169
|
+
const db = getDb();
|
|
170
|
+
db.run("DELETE FROM messages");
|
|
171
|
+
db.run("DELETE FROM external_conversation_bindings");
|
|
172
|
+
db.run("DELETE FROM conversation_keys");
|
|
173
|
+
db.run("DELETE FROM conversations");
|
|
174
|
+
broadcasts.length = 0;
|
|
175
|
+
providerCalls.length = 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
89
178
|
describe("runConversationTurn persistence", () => {
|
|
90
|
-
beforeEach(
|
|
91
|
-
const db = getDb();
|
|
92
|
-
db.run("DELETE FROM messages");
|
|
93
|
-
db.run("DELETE FROM conversations");
|
|
94
|
-
listChangedCalls.length = 0;
|
|
95
|
-
lastProcessMessageConversationId = undefined;
|
|
96
|
-
lastProcessMessageOptions = undefined;
|
|
97
|
-
lastEnqueueOptions = undefined;
|
|
98
|
-
conversationIsProcessing = false;
|
|
99
|
-
});
|
|
179
|
+
beforeEach(resetDb);
|
|
100
180
|
|
|
101
181
|
test("persists a conversations row for a freshly-minted conversation", async () => {
|
|
102
182
|
const result = await runConversationTurn({
|
|
@@ -104,16 +184,20 @@ describe("runConversationTurn persistence", () => {
|
|
|
104
184
|
});
|
|
105
185
|
|
|
106
186
|
// The row exists on disk — not just as an in-memory Conversation object —
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
187
|
+
// and the user message the turn persisted found its FK target there.
|
|
188
|
+
expect(getConversation(result.conversationId)?.id).toBe(
|
|
189
|
+
result.conversationId,
|
|
190
|
+
);
|
|
191
|
+
expect(userRows(result.conversationId).map((row) => row.id)).toEqual([
|
|
192
|
+
result.userMessageId,
|
|
193
|
+
]);
|
|
111
194
|
|
|
112
195
|
// Siblings/sidebars are told about the new conversation, mirroring the
|
|
113
196
|
// send-message route.
|
|
114
|
-
expect(
|
|
115
|
-
|
|
116
|
-
|
|
197
|
+
expect(listInvalidations()).toEqual([creationTags(result.conversationId)]);
|
|
198
|
+
expect(
|
|
199
|
+
broadcasts.filter((msg) => msg.type === "conversation_list_invalidated"),
|
|
200
|
+
).toEqual([{ type: "conversation_list_invalidated", reason: "created" }]);
|
|
117
201
|
});
|
|
118
202
|
|
|
119
203
|
test("adopts a caller-supplied conversation id verbatim when no row exists", async () => {
|
|
@@ -126,12 +210,12 @@ describe("runConversationTurn persistence", () => {
|
|
|
126
210
|
|
|
127
211
|
expect(result.conversationId).toBe(conversationId);
|
|
128
212
|
expect(getConversation(conversationId)?.id).toBe(conversationId);
|
|
129
|
-
expect(
|
|
213
|
+
expect(listInvalidations()).toEqual([creationTags(conversationId)]);
|
|
130
214
|
});
|
|
131
215
|
|
|
132
216
|
test("is a no-op for an already-persisted conversation row", async () => {
|
|
133
217
|
const existing = createConversation({ title: "already here" });
|
|
134
|
-
|
|
218
|
+
broadcasts.length = 0;
|
|
135
219
|
|
|
136
220
|
const result = await runConversationTurn({
|
|
137
221
|
conversationId: existing.id,
|
|
@@ -141,7 +225,7 @@ describe("runConversationTurn persistence", () => {
|
|
|
141
225
|
expect(result.conversationId).toBe(existing.id);
|
|
142
226
|
// Row is untouched and no duplicate "created" invalidation fires.
|
|
143
227
|
expect(getConversation(existing.id)?.title).toBe("already here");
|
|
144
|
-
expect(
|
|
228
|
+
expect(listInvalidations()).toEqual([]);
|
|
145
229
|
});
|
|
146
230
|
});
|
|
147
231
|
|
|
@@ -151,28 +235,24 @@ describe("runConversationTurn persistence", () => {
|
|
|
151
235
|
// eligibility predicate's verdict on the stamped metadata, so the marker and
|
|
152
236
|
// the gate that reads it cannot drift apart.
|
|
153
237
|
describe("runConversationTurn provenance", () => {
|
|
154
|
-
beforeEach(
|
|
155
|
-
const db = getDb();
|
|
156
|
-
db.run("DELETE FROM messages");
|
|
157
|
-
db.run("DELETE FROM conversations");
|
|
158
|
-
lastProcessMessageOptions = undefined;
|
|
159
|
-
lastEnqueueOptions = undefined;
|
|
160
|
-
conversationIsProcessing = false;
|
|
161
|
-
});
|
|
238
|
+
beforeEach(resetDb);
|
|
162
239
|
|
|
163
240
|
test("stamps the initiating row automated so its reply raises no push", async () => {
|
|
164
241
|
const existing = createConversation({ title: "standard conversation" });
|
|
165
242
|
|
|
166
|
-
await runConversationTurn({
|
|
243
|
+
const result = await runConversationTurn({
|
|
167
244
|
conversationId: existing.id,
|
|
168
245
|
content: [{ type: "text", text: "transcript excerpt" }],
|
|
169
246
|
});
|
|
170
247
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
248
|
+
// The scripted reply came back, so the marker below was stamped by a turn
|
|
249
|
+
// that ran rather than by one that fell over into an error row.
|
|
250
|
+
expect(result.content).toMatchObject([
|
|
251
|
+
{ type: "text", text: "scripted reply" },
|
|
252
|
+
]);
|
|
253
|
+
|
|
254
|
+
const metadata = metadataOf(userRows(existing.id)[0]);
|
|
255
|
+
expect(metadata).toMatchObject({ automated: true });
|
|
176
256
|
expect(isReplyPushIneligibleUserMessage(metadata)).toBe(true);
|
|
177
257
|
// Not an echo-suppression marker: the row still renders in the transcript.
|
|
178
258
|
expect(isEchoSuppressedUserMessage(metadata)).toBe(false);
|
|
@@ -180,16 +260,291 @@ describe("runConversationTurn provenance", () => {
|
|
|
180
260
|
|
|
181
261
|
test("stamps the same marker on a turn queued behind a busy conversation", async () => {
|
|
182
262
|
const existing = createConversation({ title: "busy conversation" });
|
|
183
|
-
|
|
263
|
+
holdTheTurnOpen();
|
|
264
|
+
|
|
265
|
+
const inFlight = runConversationTurn({
|
|
266
|
+
conversationId: existing.id,
|
|
267
|
+
content: [{ type: "text", text: "first excerpt" }],
|
|
268
|
+
});
|
|
269
|
+
await waitFor(() => userRows(existing.id).length === 1, {
|
|
270
|
+
timeoutMs: 5_000,
|
|
271
|
+
message: "the first turn never reached the provider",
|
|
272
|
+
});
|
|
184
273
|
|
|
185
274
|
const result = await runConversationTurn({
|
|
186
275
|
conversationId: existing.id,
|
|
187
276
|
content: [{ type: "text", text: "transcript excerpt" }],
|
|
188
277
|
});
|
|
189
|
-
|
|
190
278
|
expect(result.queued).toBe(true);
|
|
191
|
-
|
|
192
|
-
|
|
279
|
+
|
|
280
|
+
// The queue drains after the in-flight turn releases, and the marker has
|
|
281
|
+
// to survive that trip: it is stamped now but read off the persisted row
|
|
282
|
+
// much later.
|
|
283
|
+
releaseGate?.();
|
|
284
|
+
await inFlight;
|
|
285
|
+
await waitFor(() => userRows(existing.id).length === 2, {
|
|
286
|
+
timeoutMs: 5_000,
|
|
287
|
+
message: "the queued turn never drained",
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const metadata = metadataOf(userRows(existing.id)[1]);
|
|
291
|
+
expect(metadata).toMatchObject({ automated: true });
|
|
193
292
|
expect(isReplyPushIneligibleUserMessage(metadata)).toBe(true);
|
|
293
|
+
}, 20_000);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Addressing a turn by chat rather than by conversation id.
|
|
298
|
+
*
|
|
299
|
+
* The point of these is not that the turn runs (that is the same code path
|
|
300
|
+
* either way) but that the conversation it runs in is the one the rest of
|
|
301
|
+
* the assistant already addresses by those coordinates. A caller keeping its
|
|
302
|
+
* own chat-to-conversation map would pass every test above and still be
|
|
303
|
+
* invisible to conversation reset, to the deny lanes, and to the channel
|
|
304
|
+
* metadata the conversation list renders.
|
|
305
|
+
*/
|
|
306
|
+
describe("runConversationTurn channel binding", () => {
|
|
307
|
+
const CHANNEL = {
|
|
308
|
+
sourceChannel: "plugin" as const,
|
|
309
|
+
externalChatId: "imessage:+12025550142",
|
|
310
|
+
displayName: "Ada",
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
beforeEach(resetDb);
|
|
314
|
+
|
|
315
|
+
test("resolves the chat to the conversation inbound would have used", async () => {
|
|
316
|
+
const result = await runConversationTurn({
|
|
317
|
+
channel: CHANNEL,
|
|
318
|
+
content: [{ type: "text", text: "hello" }],
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// The public name for this conversation, which is what reset and the deny
|
|
322
|
+
// lanes address it by.
|
|
323
|
+
expect(
|
|
324
|
+
getConversationByKey(
|
|
325
|
+
buildScopedConversationKey(
|
|
326
|
+
CHANNEL.sourceChannel,
|
|
327
|
+
CHANNEL.externalChatId,
|
|
328
|
+
),
|
|
329
|
+
)?.conversationId,
|
|
330
|
+
).toBe(result.conversationId);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("keeps the same chat in the same conversation across turns", async () => {
|
|
334
|
+
const first = await runConversationTurn({
|
|
335
|
+
channel: CHANNEL,
|
|
336
|
+
content: [{ type: "text", text: "hello" }],
|
|
337
|
+
});
|
|
338
|
+
const second = await runConversationTurn({
|
|
339
|
+
channel: CHANNEL,
|
|
340
|
+
content: [{ type: "text", text: "still me" }],
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
expect(second.conversationId).toBe(first.conversationId);
|
|
344
|
+
// Only the first turn minted a conversation.
|
|
345
|
+
expect(listInvalidations()).toEqual([creationTags(first.conversationId)]);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test("gives a different chat its own conversation", async () => {
|
|
349
|
+
const ada = await runConversationTurn({
|
|
350
|
+
channel: CHANNEL,
|
|
351
|
+
content: [{ type: "text", text: "hello" }],
|
|
352
|
+
});
|
|
353
|
+
const grace = await runConversationTurn({
|
|
354
|
+
channel: { ...CHANNEL, externalChatId: "imessage:+12025550188" },
|
|
355
|
+
content: [{ type: "text", text: "hello" }],
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
expect(grace.conversationId).not.toBe(ada.conversationId);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("records the channel metadata the conversation list reads", async () => {
|
|
362
|
+
const result = await runConversationTurn({
|
|
363
|
+
channel: { ...CHANNEL, externalUserId: "+12025550142" },
|
|
364
|
+
content: [{ type: "text", text: "hello" }],
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
const binding = getBindingByConversation(result.conversationId);
|
|
368
|
+
expect(binding).toMatchObject({
|
|
369
|
+
sourceChannel: "plugin",
|
|
370
|
+
externalChatId: CHANNEL.externalChatId,
|
|
371
|
+
externalUserId: "+12025550142",
|
|
372
|
+
displayName: "Ada",
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test("follows the vendor when a display name changes", async () => {
|
|
377
|
+
await runConversationTurn({
|
|
378
|
+
channel: CHANNEL,
|
|
379
|
+
content: [{ type: "text", text: "hello" }],
|
|
380
|
+
});
|
|
381
|
+
const result = await runConversationTurn({
|
|
382
|
+
channel: { ...CHANNEL, displayName: "Ada L." },
|
|
383
|
+
content: [{ type: "text", text: "renamed" }],
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
expect(getBindingByConversation(result.conversationId)?.displayName).toBe(
|
|
387
|
+
"Ada L.",
|
|
388
|
+
);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test("runs the turn on the channel it was addressed by", async () => {
|
|
392
|
+
// Runtime assembly reads the per-turn channel first and only then the
|
|
393
|
+
// conversation's origin, so a turn into a conversation whose origin was
|
|
394
|
+
// never recorded would otherwise run as `vellum`. That is the wrong
|
|
395
|
+
// channel for the message rows and for the permission cascade the tools
|
|
396
|
+
// are approved against.
|
|
397
|
+
const result = await runConversationTurn({
|
|
398
|
+
channel: CHANNEL,
|
|
399
|
+
content: [{ type: "text", text: "hello" }],
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const rows = getMessages(result.conversationId);
|
|
403
|
+
expect(metadataOf(rows[0])).toMatchObject({
|
|
404
|
+
userMessageChannel: "plugin",
|
|
405
|
+
assistantMessageChannel: "plugin",
|
|
406
|
+
userMessageInterface: "plugin",
|
|
407
|
+
assistantMessageInterface: "plugin",
|
|
408
|
+
});
|
|
409
|
+
expect(rows[1].role).toBe("assistant");
|
|
410
|
+
expect(metadataOf(rows[1])).toMatchObject({
|
|
411
|
+
userMessageChannel: "plugin",
|
|
412
|
+
assistantMessageChannel: "plugin",
|
|
413
|
+
userMessageInterface: "plugin",
|
|
414
|
+
assistantMessageInterface: "plugin",
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test("puts core tools on the wire for a plugin-channel turn", async () => {
|
|
419
|
+
// Plugin-driven iMessage turns are non-interactive, so host tools stay
|
|
420
|
+
// off, but sandbox tools (bash, web_search, ...) have to remain on the
|
|
421
|
+
// request. Missing them is what makes the model dump `to=bash code:`
|
|
422
|
+
// into the SMS as prose. The create path initializes the registry
|
|
423
|
+
// before constructing the Conversation that snapshots it.
|
|
424
|
+
await runConversationTurn({
|
|
425
|
+
channel: CHANNEL,
|
|
426
|
+
content: [{ type: "text", text: "hello" }],
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const names = providerCalls[0]?.tools?.map((tool) => tool.name) ?? [];
|
|
430
|
+
expect(names.length).toBeGreaterThan(0);
|
|
431
|
+
expect(names).toContain("bash");
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test("a queued turn carries its own channel rather than inheriting one", async () => {
|
|
435
|
+
// The drain happens after this call returns and reads the channel off the
|
|
436
|
+
// queued message, falling back to whichever turn was in flight. Here that
|
|
437
|
+
// fallback would be `vellum` — someone else's channel on a conversation
|
|
438
|
+
// shared with another channel.
|
|
439
|
+
const conversationId = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d";
|
|
440
|
+
holdTheTurnOpen();
|
|
441
|
+
|
|
442
|
+
const inFlight = runConversationTurn({
|
|
443
|
+
conversationId,
|
|
444
|
+
content: [{ type: "text", text: "native turn" }],
|
|
445
|
+
});
|
|
446
|
+
await waitFor(() => userRows(conversationId).length === 1, {
|
|
447
|
+
timeoutMs: 5_000,
|
|
448
|
+
message: "the first turn never reached the provider",
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
const result = await runConversationTurn({
|
|
452
|
+
conversationId,
|
|
453
|
+
channel: CHANNEL,
|
|
454
|
+
content: [{ type: "text", text: "hello" }],
|
|
455
|
+
});
|
|
456
|
+
expect(result.queued).toBe(true);
|
|
457
|
+
|
|
458
|
+
releaseGate?.();
|
|
459
|
+
await inFlight;
|
|
460
|
+
await waitFor(() => userRows(conversationId).length === 2, {
|
|
461
|
+
timeoutMs: 5_000,
|
|
462
|
+
message: "the queued turn never drained",
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
expect(metadataOf(userRows(conversationId)[1])).toMatchObject({
|
|
466
|
+
userMessageChannel: "plugin",
|
|
467
|
+
assistantMessageChannel: "plugin",
|
|
468
|
+
userMessageInterface: "plugin",
|
|
469
|
+
assistantMessageInterface: "plugin",
|
|
470
|
+
});
|
|
471
|
+
}, 20_000);
|
|
472
|
+
|
|
473
|
+
test("leaves the channel unset when the turn names none", async () => {
|
|
474
|
+
// Clearing rather than leaving whatever the last turn set: a stale
|
|
475
|
+
// context would report this turn on a channel it has nothing to do with.
|
|
476
|
+
await runConversationTurn({
|
|
477
|
+
channel: CHANNEL,
|
|
478
|
+
content: [{ type: "text", text: "hello" }],
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
const result = await runConversationTurn({
|
|
482
|
+
content: [{ type: "text", text: "hello" }],
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
const rows = getMessages(result.conversationId);
|
|
486
|
+
const userMetadata = metadataOf(rows[0]);
|
|
487
|
+
expect(userMetadata).toMatchObject({ automated: true });
|
|
488
|
+
expect(userMetadata?.userMessageChannel).toBeUndefined();
|
|
489
|
+
// The conversation carries no origin either, so the turn falls back to the
|
|
490
|
+
// native channel rather than to the previous turn's.
|
|
491
|
+
expect(metadataOf(rows[1])).toMatchObject({
|
|
492
|
+
userMessageChannel: "vellum",
|
|
493
|
+
assistantMessageChannel: "vellum",
|
|
494
|
+
});
|
|
495
|
+
}, 20_000);
|
|
496
|
+
|
|
497
|
+
test("keeps a known sender when a later turn omits it", async () => {
|
|
498
|
+
// A plugin that knows only the chat coordinates this time is silent about
|
|
499
|
+
// the sender, not asserting it has none. Erasing the stored name on that
|
|
500
|
+
// silence drops it from the conversation and session APIs.
|
|
501
|
+
await runConversationTurn({
|
|
502
|
+
channel: { ...CHANNEL, externalUserId: "+12025550142" },
|
|
503
|
+
content: [{ type: "text", text: "hello" }],
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
const result = await runConversationTurn({
|
|
507
|
+
channel: {
|
|
508
|
+
sourceChannel: CHANNEL.sourceChannel,
|
|
509
|
+
externalChatId: CHANNEL.externalChatId,
|
|
510
|
+
},
|
|
511
|
+
content: [{ type: "text", text: "still me" }],
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
expect(getBindingByConversation(result.conversationId)).toMatchObject({
|
|
515
|
+
externalUserId: "+12025550142",
|
|
516
|
+
displayName: "Ada",
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test("clears a sender the caller explicitly says is gone", async () => {
|
|
521
|
+
await runConversationTurn({
|
|
522
|
+
channel: CHANNEL,
|
|
523
|
+
content: [{ type: "text", text: "hello" }],
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
const result = await runConversationTurn({
|
|
527
|
+
channel: { ...CHANNEL, displayName: null },
|
|
528
|
+
content: [{ type: "text", text: "anonymous now" }],
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
expect(
|
|
532
|
+
getBindingByConversation(result.conversationId)?.displayName,
|
|
533
|
+
).toBeNull();
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
test("an explicit conversation id wins over the address", async () => {
|
|
537
|
+
// Two different conversations were named. Re-resolving would overrule the
|
|
538
|
+
// caller, so the explicit one is honoured and nothing is bound behind it.
|
|
539
|
+
const existing = createConversation({ title: "chosen" });
|
|
540
|
+
|
|
541
|
+
const result = await runConversationTurn({
|
|
542
|
+
conversationId: existing.id,
|
|
543
|
+
channel: CHANNEL,
|
|
544
|
+
content: [{ type: "text", text: "hello" }],
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
expect(result.conversationId).toBe(existing.id);
|
|
548
|
+
expect(getBindingByConversation(existing.id)).toBeNull();
|
|
194
549
|
});
|
|
195
550
|
});
|
|
@@ -57,6 +57,7 @@ mock.module("../persistence/lifecycle-quiesce.js", () => ({
|
|
|
57
57
|
|
|
58
58
|
import { getDb } from "../persistence/db-connection.js";
|
|
59
59
|
import { initializeDb } from "../persistence/db-init.js";
|
|
60
|
+
import { isPluginDirActivated } from "../plugins/mtime-cache.js";
|
|
60
61
|
import {
|
|
61
62
|
createSchedule,
|
|
62
63
|
deferClaimedSchedule,
|
|
@@ -342,6 +343,26 @@ describe("fire-time source-availability gate", () => {
|
|
|
342
343
|
expect(skipRunsFor(job.id)).toHaveLength(1);
|
|
343
344
|
});
|
|
344
345
|
|
|
346
|
+
test("executes an armed sourced row even though no plugin is activated in this process", async () => {
|
|
347
|
+
const job = await seedDueSourcedScript();
|
|
348
|
+
// The tick runs in the schedule worker, which activates no plugins, so the
|
|
349
|
+
// activation ledger is empty here. The gate must therefore read the disk
|
|
350
|
+
// alone: reading activation would skip every plugin schedule. Arming is
|
|
351
|
+
// the daemon-side reconciler's call.
|
|
352
|
+
expect(isPluginDirActivated(pluginDir)).toBe(false);
|
|
353
|
+
|
|
354
|
+
const result = await runDueSchedulesOnce();
|
|
355
|
+
|
|
356
|
+
expect(result.completed).toBe(1);
|
|
357
|
+
expect(result.skipped).toBe(0);
|
|
358
|
+
expect(existsSync(marker)).toBe(true);
|
|
359
|
+
const runs = rawDb()
|
|
360
|
+
.query("SELECT status FROM cron_runs WHERE job_id = ?")
|
|
361
|
+
.all(job.id) as Array<{ status: string }>;
|
|
362
|
+
expect(runs).toHaveLength(1);
|
|
363
|
+
expect(runs[0].status).toBe("ok");
|
|
364
|
+
});
|
|
365
|
+
|
|
345
366
|
test("does not execute a due sourced row while the feature flag is off", async () => {
|
|
346
367
|
const job = await seedDueSourcedScript();
|
|
347
368
|
setOverridesForTesting({ "plugin-schedules": false });
|