@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
|
@@ -25,14 +25,14 @@ import { createConversation } from "../../../persistence/conversation-crud.js";
|
|
|
25
25
|
import { getDb } from "../../../persistence/db-connection.js";
|
|
26
26
|
import { initializeDb } from "../../../persistence/db-init.js";
|
|
27
27
|
import { createGroup } from "../../../persistence/group-crud.js";
|
|
28
|
-
import { rawRun } from "../../../persistence/raw-query.js";
|
|
28
|
+
import { rawExec, rawRun } from "../../../persistence/raw-query.js";
|
|
29
29
|
import {
|
|
30
30
|
conversationAssistantAttentionState,
|
|
31
31
|
conversationAttentionEvents,
|
|
32
32
|
conversations,
|
|
33
33
|
} from "../../../persistence/schema/index.js";
|
|
34
34
|
import { ROUTES as CONVERSATION_LIST_ROUTES } from "../conversation-list-routes.js";
|
|
35
|
-
import { BadRequestError } from "../errors.js";
|
|
35
|
+
import { BadRequestError, NotFoundError } from "../errors.js";
|
|
36
36
|
import type { RouteDefinition } from "../types.js";
|
|
37
37
|
|
|
38
38
|
// ---------------------------------------------------------------------------
|
|
@@ -246,6 +246,42 @@ describe("GET /v1/conversations — conversationType", () => {
|
|
|
246
246
|
});
|
|
247
247
|
});
|
|
248
248
|
|
|
249
|
+
describe("GET /v1/conversations/:id", () => {
|
|
250
|
+
const getHandler = findHandler(CONVERSATION_LIST_ROUTES, "getConversation");
|
|
251
|
+
|
|
252
|
+
beforeEach(() => {
|
|
253
|
+
clearConversations();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("serves a listed conversation", () => {
|
|
257
|
+
const conv = createConversation({ title: "listed" });
|
|
258
|
+
|
|
259
|
+
const detail = getHandler({ pathParams: { id: conv.id } }) as {
|
|
260
|
+
conversation: { id: string; conversationType: string };
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
expect(detail.conversation.id).toBe(conv.id);
|
|
264
|
+
expect(detail.conversation.conversationType).toBe("standard");
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("does not serve a legacy private row the listing hides", () => {
|
|
268
|
+
// "private" is not a creatable type (legacy rows only); every listing
|
|
269
|
+
// hides it by type and the wire type collapses it to "standard", so a
|
|
270
|
+
// client holding only a stale id must get the same answer the listing
|
|
271
|
+
// gives: not there.
|
|
272
|
+
const conv = createConversation({ title: "hidden" });
|
|
273
|
+
rawRun(
|
|
274
|
+
"test:makePrivate",
|
|
275
|
+
"UPDATE conversations SET conversation_type = 'private' WHERE id = ?",
|
|
276
|
+
conv.id,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
expect(() => getHandler({ pathParams: { id: conv.id } })).toThrow(
|
|
280
|
+
NotFoundError,
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
249
285
|
describe("GET /v1/conversations with groupId", () => {
|
|
250
286
|
function seedInGroup(title: string, groupId: string): string {
|
|
251
287
|
const conv = createConversation(title);
|
|
@@ -514,6 +550,272 @@ describe("GET /v1/conversations with groupId", () => {
|
|
|
514
550
|
});
|
|
515
551
|
});
|
|
516
552
|
|
|
553
|
+
describe("GET /v1/conversations with needsAttention", () => {
|
|
554
|
+
function seedUnseen(conversationId: string): void {
|
|
555
|
+
projectAssistantMessage({
|
|
556
|
+
conversationId,
|
|
557
|
+
messageId: `msg-${conversationId}`,
|
|
558
|
+
messageAt: Date.now(),
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function markSeen(conversationId: string): void {
|
|
563
|
+
recordConversationSeenSignal({
|
|
564
|
+
conversationId,
|
|
565
|
+
sourceChannel: "vellum",
|
|
566
|
+
signalType: "macos_conversation_opened",
|
|
567
|
+
confidence: "explicit",
|
|
568
|
+
source: "test",
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
beforeEach(() => {
|
|
573
|
+
getDb().delete(conversationAttentionEvents).run();
|
|
574
|
+
getDb().delete(conversationAssistantAttentionState).run();
|
|
575
|
+
clearConversations();
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
test("returns only conversations with an unseen latest assistant message", async () => {
|
|
579
|
+
const unseen = createConversation("needs-attention");
|
|
580
|
+
seedUnseen(unseen.id);
|
|
581
|
+
const seen = createConversation("already-seen");
|
|
582
|
+
seedUnseen(seen.id);
|
|
583
|
+
markSeen(seen.id);
|
|
584
|
+
// No attention projection at all: not unseen, so not returned. The
|
|
585
|
+
// filter's inner join is what excludes it; a left join would leak it.
|
|
586
|
+
createConversation("never-projected");
|
|
587
|
+
|
|
588
|
+
const result = await invoke({ needsAttention: "true" });
|
|
589
|
+
|
|
590
|
+
expect(result.conversations.map((c) => c.title)).toEqual([
|
|
591
|
+
"needs-attention",
|
|
592
|
+
]);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
test("omitting the filter leaves every list unchanged, join and all", async () => {
|
|
596
|
+
/* The sensitivity check for the conditional join: with the filter off,
|
|
597
|
+
rows with no attention row must still be listed. If the join were
|
|
598
|
+
applied unconditionally, "never-projected" would vanish from the
|
|
599
|
+
plain list, which is every list the app has today. */
|
|
600
|
+
const unseen = createConversation("has-attention-row");
|
|
601
|
+
seedUnseen(unseen.id);
|
|
602
|
+
createConversation("never-projected");
|
|
603
|
+
|
|
604
|
+
const result = await invoke();
|
|
605
|
+
|
|
606
|
+
expect(result.conversations.map((c) => c.title).sort()).toEqual([
|
|
607
|
+
"has-attention-row",
|
|
608
|
+
"never-projected",
|
|
609
|
+
]);
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
test("hasMore and the total describe the filtered set, not the whole table", async () => {
|
|
613
|
+
/* countConversations reads through the same where AND the same join;
|
|
614
|
+
a page and its total have to agree or the client's hasMore lies. */
|
|
615
|
+
for (let i = 0; i < 3; i++) {
|
|
616
|
+
const c = createConversation(`unseen-${i}`);
|
|
617
|
+
seedUnseen(c.id);
|
|
618
|
+
}
|
|
619
|
+
for (let i = 0; i < 5; i++) {
|
|
620
|
+
createConversation(`quiet-${i}`);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const page = await invoke({ needsAttention: "true", limit: "2" });
|
|
624
|
+
|
|
625
|
+
expect(page.conversations).toHaveLength(2);
|
|
626
|
+
expect(page.hasMore).toBe(true);
|
|
627
|
+
const rest = await invoke({
|
|
628
|
+
needsAttention: "true",
|
|
629
|
+
limit: "2",
|
|
630
|
+
offset: "2",
|
|
631
|
+
});
|
|
632
|
+
expect(rest.conversations).toHaveLength(1);
|
|
633
|
+
expect(rest.hasMore).toBe(false);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test("an attention-scoped first page has no pinned rows appended to it", async () => {
|
|
637
|
+
/* Same rule as the group-scoped page: the pinned injection exists for
|
|
638
|
+
a client reading Pinned out of the unfiltered list, and a caller
|
|
639
|
+
that asked for the unseen subset is not that client. A seen pinned
|
|
640
|
+
row appended here would be a row outside the filter, on a page whose
|
|
641
|
+
hasMore was computed from the filtered count. */
|
|
642
|
+
const unseen = createConversation("unseen-only");
|
|
643
|
+
seedUnseen(unseen.id);
|
|
644
|
+
const pinnedSeen = createConversation("pinned-and-seen");
|
|
645
|
+
rawRun(
|
|
646
|
+
"test:pinConversation",
|
|
647
|
+
"UPDATE conversations SET is_pinned = 1, group_id = 'system:pinned' WHERE id = ?",
|
|
648
|
+
pinnedSeen.id,
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
const result = await invoke({ needsAttention: "true" });
|
|
652
|
+
|
|
653
|
+
expect(result.conversations.map((c) => c.title)).toEqual(["unseen-only"]);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
test("composes with the other filters", async () => {
|
|
657
|
+
const group = createGroup("Work");
|
|
658
|
+
const inGroupUnseen = createConversation("in-group-unseen");
|
|
659
|
+
seedUnseen(inGroupUnseen.id);
|
|
660
|
+
rawRun(
|
|
661
|
+
"test:fileInGroup",
|
|
662
|
+
"UPDATE conversations SET group_id = ? WHERE id = ?",
|
|
663
|
+
group.id,
|
|
664
|
+
inGroupUnseen.id,
|
|
665
|
+
);
|
|
666
|
+
const outOfGroupUnseen = createConversation("out-of-group-unseen");
|
|
667
|
+
seedUnseen(outOfGroupUnseen.id);
|
|
668
|
+
|
|
669
|
+
const result = await invoke({ needsAttention: "true", groupId: group.id });
|
|
670
|
+
|
|
671
|
+
expect(result.conversations.map((c) => c.title)).toEqual([
|
|
672
|
+
"in-group-unseen",
|
|
673
|
+
]);
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
test('any value other than "true" is rejected with a 400', () => {
|
|
677
|
+
/* Same posture as conversationType: silently reading a typo or a newer
|
|
678
|
+
client's value as "no filter" would hand back the full list where a
|
|
679
|
+
subset was asked for, and that skew is invisible to the client. */
|
|
680
|
+
for (const bad of ["false", "1", "yes", "TRUE"]) {
|
|
681
|
+
expect(() => invoke({ needsAttention: bad })).toThrow(BadRequestError);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
describe("GET /v1/conversations with foregroundOnly", () => {
|
|
687
|
+
function fileIntoGroup(conversationId: string, groupId: string): void {
|
|
688
|
+
rawRun(
|
|
689
|
+
"test:fileIntoGroup",
|
|
690
|
+
"UPDATE conversations SET group_id = ? WHERE id = ?",
|
|
691
|
+
groupId,
|
|
692
|
+
conversationId,
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function surface(conversationId: string): void {
|
|
697
|
+
rawRun(
|
|
698
|
+
"test:surfaceConversation",
|
|
699
|
+
"UPDATE conversations SET surfaced_at = ? WHERE id = ?",
|
|
700
|
+
Date.now(),
|
|
701
|
+
conversationId,
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
beforeEach(() => {
|
|
706
|
+
clearConversations();
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
test("drops the automated runs the standard listing admits through a custom group", async () => {
|
|
710
|
+
/* The standard listing shows a background run filed in a custom group,
|
|
711
|
+
so a caller reading page one to find "the newest chat" can lead with
|
|
712
|
+
one; the filter is what lets that caller ask for chats alone. A
|
|
713
|
+
surfaced run is a chat for this purpose, exactly as the unread count
|
|
714
|
+
treats it. */
|
|
715
|
+
const group = createGroup("Work");
|
|
716
|
+
createConversation("plain-chat");
|
|
717
|
+
const bgInGroup = createConversation({
|
|
718
|
+
title: "bg-in-group",
|
|
719
|
+
conversationType: "background",
|
|
720
|
+
});
|
|
721
|
+
fileIntoGroup(bgInGroup.id, group.id);
|
|
722
|
+
const surfacedBg = createConversation({
|
|
723
|
+
title: "surfaced-bg",
|
|
724
|
+
conversationType: "background",
|
|
725
|
+
});
|
|
726
|
+
surface(surfacedBg.id);
|
|
727
|
+
|
|
728
|
+
const unfiltered = await invoke();
|
|
729
|
+
expect(unfiltered.conversations.map((c) => c.title).sort()).toEqual([
|
|
730
|
+
"bg-in-group",
|
|
731
|
+
"plain-chat",
|
|
732
|
+
"surfaced-bg",
|
|
733
|
+
]);
|
|
734
|
+
|
|
735
|
+
const result = await invoke({ foregroundOnly: "true" });
|
|
736
|
+
expect(result.conversations.map((c) => c.title).sort()).toEqual([
|
|
737
|
+
"plain-chat",
|
|
738
|
+
"surfaced-bg",
|
|
739
|
+
]);
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
test("hasMore and the total describe the filtered set, not the whole table", async () => {
|
|
743
|
+
/* The reason for the filter is a limit=1 read whose first row is the
|
|
744
|
+
answer; that read is only right if the page and its total come from
|
|
745
|
+
the same predicate. */
|
|
746
|
+
const group = createGroup("Work");
|
|
747
|
+
for (let i = 0; i < 3; i++) {
|
|
748
|
+
const bg = createConversation({
|
|
749
|
+
title: `bg-${i}`,
|
|
750
|
+
conversationType: "background",
|
|
751
|
+
});
|
|
752
|
+
fileIntoGroup(bg.id, group.id);
|
|
753
|
+
}
|
|
754
|
+
createConversation("the-only-chat");
|
|
755
|
+
|
|
756
|
+
const page = await invoke({ foregroundOnly: "true", limit: "1" });
|
|
757
|
+
|
|
758
|
+
expect(page.conversations.map((c) => c.title)).toEqual(["the-only-chat"]);
|
|
759
|
+
expect(page.hasMore).toBe(false);
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
test("a foreground-only first page has no pinned rows appended to it", async () => {
|
|
763
|
+
/* Same rule as the attention- and group-scoped pages: the injection is
|
|
764
|
+
for a client reading Pinned out of the unfiltered list, and a caller
|
|
765
|
+
that asked for the newest chat in one row is not that client. The
|
|
766
|
+
older pinned chat is a member of the filter, so only the injection
|
|
767
|
+
could put it on a limit=1 page. */
|
|
768
|
+
const older = createConversation("older-pinned-chat");
|
|
769
|
+
rawRun(
|
|
770
|
+
"test:pinConversation",
|
|
771
|
+
"UPDATE conversations SET is_pinned = 1, group_id = 'system:pinned', last_message_at = ? WHERE id = ?",
|
|
772
|
+
Date.now() - 60_000,
|
|
773
|
+
older.id,
|
|
774
|
+
);
|
|
775
|
+
const newer = createConversation("newest-chat");
|
|
776
|
+
rawRun(
|
|
777
|
+
"test:bumpRecency",
|
|
778
|
+
"UPDATE conversations SET last_message_at = ? WHERE id = ?",
|
|
779
|
+
Date.now(),
|
|
780
|
+
newer.id,
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
const unfiltered = await invoke({ limit: "1" });
|
|
784
|
+
expect(unfiltered.conversations.map((c) => c.title)).toEqual([
|
|
785
|
+
"newest-chat",
|
|
786
|
+
"older-pinned-chat",
|
|
787
|
+
]);
|
|
788
|
+
|
|
789
|
+
const result = await invoke({ foregroundOnly: "true", limit: "1" });
|
|
790
|
+
expect(result.conversations.map((c) => c.title)).toEqual(["newest-chat"]);
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
test("composes with the other filters", async () => {
|
|
794
|
+
const group = createGroup("Work");
|
|
795
|
+
const chatInGroup = createConversation("chat-in-group");
|
|
796
|
+
fileIntoGroup(chatInGroup.id, group.id);
|
|
797
|
+
const bgInGroup = createConversation({
|
|
798
|
+
title: "bg-in-group",
|
|
799
|
+
conversationType: "background",
|
|
800
|
+
});
|
|
801
|
+
fileIntoGroup(bgInGroup.id, group.id);
|
|
802
|
+
createConversation("chat-outside");
|
|
803
|
+
|
|
804
|
+
const result = await invoke({
|
|
805
|
+
foregroundOnly: "true",
|
|
806
|
+
groupId: group.id,
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
expect(result.conversations.map((c) => c.title)).toEqual(["chat-in-group"]);
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
test('any value other than "true" is rejected with a 400', () => {
|
|
813
|
+
for (const bad of ["false", "1", "yes", "TRUE"]) {
|
|
814
|
+
expect(() => invoke({ foregroundOnly: bad })).toThrow(BadRequestError);
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
});
|
|
818
|
+
|
|
517
819
|
describe("GET /v1/conversations/unread-count", () => {
|
|
518
820
|
const unreadCountHandler = findHandler(
|
|
519
821
|
CONVERSATION_LIST_ROUTES,
|
|
@@ -782,3 +1084,221 @@ describe("GET /v1/conversations/unread-count", () => {
|
|
|
782
1084
|
expect(invokeUnreadCount()).toEqual({ count: 1 });
|
|
783
1085
|
});
|
|
784
1086
|
});
|
|
1087
|
+
|
|
1088
|
+
describe("GET /v1/conversations/sections", () => {
|
|
1089
|
+
const sectionsHandler = findHandler(
|
|
1090
|
+
CONVERSATION_LIST_ROUTES,
|
|
1091
|
+
"getConversationSections",
|
|
1092
|
+
);
|
|
1093
|
+
|
|
1094
|
+
interface SectionRow {
|
|
1095
|
+
kind: "pinned" | "group" | "channel" | "chats";
|
|
1096
|
+
groupId?: string;
|
|
1097
|
+
name?: string;
|
|
1098
|
+
icon?: string | null;
|
|
1099
|
+
sortPosition?: number;
|
|
1100
|
+
channelId?: string;
|
|
1101
|
+
total: number;
|
|
1102
|
+
unread: number;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
function invokeSections(): SectionRow[] {
|
|
1106
|
+
return (sectionsHandler({}) as { sections: SectionRow[] }).sections;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
function fileIntoGroup(conversationId: string, groupId: string): void {
|
|
1110
|
+
rawRun(
|
|
1111
|
+
"test:fileIntoGroup",
|
|
1112
|
+
"UPDATE conversations SET group_id = ? WHERE id = ?",
|
|
1113
|
+
groupId,
|
|
1114
|
+
conversationId,
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
function stampChannel(conversationId: string, channel: string): void {
|
|
1119
|
+
rawRun(
|
|
1120
|
+
"test:stampChannel",
|
|
1121
|
+
"UPDATE conversations SET origin_channel = ? WHERE id = ?",
|
|
1122
|
+
channel,
|
|
1123
|
+
conversationId,
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function pin(conversationId: string): void {
|
|
1128
|
+
rawRun(
|
|
1129
|
+
"test:pinConversation",
|
|
1130
|
+
"UPDATE conversations SET is_pinned = 1, group_id = 'system:pinned' WHERE id = ?",
|
|
1131
|
+
conversationId,
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function seedUnseen(conversationId: string): void {
|
|
1136
|
+
projectAssistantMessage({
|
|
1137
|
+
conversationId,
|
|
1138
|
+
messageId: `msg-${conversationId}`,
|
|
1139
|
+
messageAt: Date.now(),
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
beforeEach(() => {
|
|
1144
|
+
clearConversations();
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
test("an empty conversation table yields Chats alone, at zero", () => {
|
|
1148
|
+
// Chats is the leftover bucket and renders regardless, so its counts
|
|
1149
|
+
// are part of the contract even when nothing exists.
|
|
1150
|
+
expect(invokeSections()).toEqual([{ kind: "chats", total: 0, unread: 0 }]);
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
test("every section kind appears with its own totals", () => {
|
|
1154
|
+
const pinned = createConversation({ title: "pinned-1" });
|
|
1155
|
+
pin(pinned.id);
|
|
1156
|
+
const group = createGroup("Sections Spread Group");
|
|
1157
|
+
fileIntoGroup(createConversation({ title: "g-1" }).id, group.id);
|
|
1158
|
+
fileIntoGroup(createConversation({ title: "g-2" }).id, group.id);
|
|
1159
|
+
stampChannel(createConversation({ title: "slack-1" }).id, "slack");
|
|
1160
|
+
createConversation({ title: "native-unstamped" });
|
|
1161
|
+
stampChannel(createConversation({ title: "native-stamped" }).id, "vellum");
|
|
1162
|
+
|
|
1163
|
+
const sections = invokeSections();
|
|
1164
|
+
|
|
1165
|
+
expect(sections).toContainEqual({ kind: "pinned", total: 1, unread: 0 });
|
|
1166
|
+
expect(sections).toContainEqual({
|
|
1167
|
+
kind: "group",
|
|
1168
|
+
groupId: group.id,
|
|
1169
|
+
name: "Sections Spread Group",
|
|
1170
|
+
icon: null,
|
|
1171
|
+
sortPosition: group.sortPosition,
|
|
1172
|
+
total: 2,
|
|
1173
|
+
unread: 0,
|
|
1174
|
+
});
|
|
1175
|
+
expect(sections).toContainEqual({
|
|
1176
|
+
kind: "channel",
|
|
1177
|
+
channelId: "slack",
|
|
1178
|
+
total: 1,
|
|
1179
|
+
unread: 0,
|
|
1180
|
+
});
|
|
1181
|
+
// NULL origin_channel reads as native, exactly as the list filter reads
|
|
1182
|
+
// it, so the stamped and unstamped native rows share the Chats bucket.
|
|
1183
|
+
expect(sections).toContainEqual({ kind: "chats", total: 2, unread: 0 });
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
test("unread counts follow the seen state per section", () => {
|
|
1187
|
+
const pinned = createConversation({ title: "pinned-seen" });
|
|
1188
|
+
pin(pinned.id);
|
|
1189
|
+
seedUnseen(pinned.id);
|
|
1190
|
+
recordConversationSeenSignal({
|
|
1191
|
+
conversationId: pinned.id,
|
|
1192
|
+
sourceChannel: "vellum",
|
|
1193
|
+
signalType: "macos_conversation_opened",
|
|
1194
|
+
confidence: "explicit",
|
|
1195
|
+
source: "test",
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
const group = createGroup("Sections Unread Group");
|
|
1199
|
+
const unreadInGroup = createConversation({ title: "g-unread" });
|
|
1200
|
+
fileIntoGroup(unreadInGroup.id, group.id);
|
|
1201
|
+
seedUnseen(unreadInGroup.id);
|
|
1202
|
+
// A member with no attention projection at all reads as seen.
|
|
1203
|
+
fileIntoGroup(createConversation({ title: "g-quiet" }).id, group.id);
|
|
1204
|
+
|
|
1205
|
+
const chatsUnread = createConversation({ title: "chat-unread" });
|
|
1206
|
+
seedUnseen(chatsUnread.id);
|
|
1207
|
+
|
|
1208
|
+
const sections = invokeSections();
|
|
1209
|
+
|
|
1210
|
+
expect(sections).toContainEqual({ kind: "pinned", total: 1, unread: 0 });
|
|
1211
|
+
expect(sections).toContainEqual({
|
|
1212
|
+
kind: "group",
|
|
1213
|
+
groupId: group.id,
|
|
1214
|
+
name: "Sections Unread Group",
|
|
1215
|
+
icon: null,
|
|
1216
|
+
sortPosition: group.sortPosition,
|
|
1217
|
+
total: 2,
|
|
1218
|
+
unread: 1,
|
|
1219
|
+
});
|
|
1220
|
+
expect(sections).toContainEqual({ kind: "chats", total: 1, unread: 1 });
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
test("a background row filed into a group counts toward total but never unread", () => {
|
|
1224
|
+
// Mirrors the unread-count contract: the custom-group visibility arm
|
|
1225
|
+
// admits the row into the section, and the not-background unread rule
|
|
1226
|
+
// keeps it out of the badge.
|
|
1227
|
+
const group = createGroup("Sections Background Group");
|
|
1228
|
+
const bg = createConversation({
|
|
1229
|
+
title: "bg-in-group",
|
|
1230
|
+
conversationType: "background",
|
|
1231
|
+
});
|
|
1232
|
+
fileIntoGroup(bg.id, group.id);
|
|
1233
|
+
seedUnseen(bg.id);
|
|
1234
|
+
|
|
1235
|
+
const sections = invokeSections();
|
|
1236
|
+
|
|
1237
|
+
expect(sections).toContainEqual({
|
|
1238
|
+
kind: "group",
|
|
1239
|
+
groupId: group.id,
|
|
1240
|
+
name: "Sections Background Group",
|
|
1241
|
+
icon: null,
|
|
1242
|
+
sortPosition: group.sortPosition,
|
|
1243
|
+
total: 1,
|
|
1244
|
+
unread: 0,
|
|
1245
|
+
});
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
test("an empty custom group gets no section", () => {
|
|
1249
|
+
const group = createGroup("Sections Empty Group");
|
|
1250
|
+
|
|
1251
|
+
const sections = invokeSections();
|
|
1252
|
+
|
|
1253
|
+
expect(sections.some((s) => s.groupId === group.id)).toBe(false);
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
test("archived rows count toward no section", () => {
|
|
1257
|
+
const group = createGroup("Sections Archived Group");
|
|
1258
|
+
fileIntoGroup(seedArchived("archived-in-group"), group.id);
|
|
1259
|
+
|
|
1260
|
+
const sections = invokeSections();
|
|
1261
|
+
|
|
1262
|
+
expect(sections.some((s) => s.groupId === group.id)).toBe(false);
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
test("a dangling group id is skipped, not surfaced", () => {
|
|
1266
|
+
/* Live write paths cannot create this state: group_id carries a foreign
|
|
1267
|
+
key, and the placement write sanitizes unknown groups precisely to
|
|
1268
|
+
avoid violating it. A restored snapshot can, though: in-place restore
|
|
1269
|
+
swaps the SQLite file without running migrations in-process (the same
|
|
1270
|
+
window that lets legacy private rows exist transiently), so the
|
|
1271
|
+
fixture creates the state the way reality does, with enforcement off. */
|
|
1272
|
+
rawExec("PRAGMA foreign_keys = OFF");
|
|
1273
|
+
try {
|
|
1274
|
+
fileIntoGroup(
|
|
1275
|
+
createConversation({ title: "dangling" }).id,
|
|
1276
|
+
"00000000-0000-4000-8000-00000000dead",
|
|
1277
|
+
);
|
|
1278
|
+
} finally {
|
|
1279
|
+
rawExec("PRAGMA foreign_keys = ON");
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
const sections = invokeSections();
|
|
1283
|
+
|
|
1284
|
+
expect(sections.some((s) => s.kind === "group")).toBe(false);
|
|
1285
|
+
// Grouped, so it does not leak into the ungrouped Chats bucket either.
|
|
1286
|
+
expect(sections).toContainEqual({ kind: "chats", total: 0, unread: 0 });
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
test("an unsurfaced background row pinned by raw column writes stays invisible", () => {
|
|
1290
|
+
// Standard-listing visibility admits a pinned background row only when
|
|
1291
|
+
// it is surfaced (the pinned group fails the custom-group arm on its
|
|
1292
|
+
// system: prefix), so it appears in neither the Pinned list nor the
|
|
1293
|
+
// Pinned count.
|
|
1294
|
+
const bg = createConversation({
|
|
1295
|
+
title: "bg-pinned",
|
|
1296
|
+
conversationType: "background",
|
|
1297
|
+
});
|
|
1298
|
+
pin(bg.id);
|
|
1299
|
+
|
|
1300
|
+
const sections = invokeSections();
|
|
1301
|
+
|
|
1302
|
+
expect(sections.some((s) => s.kind === "pinned")).toBe(false);
|
|
1303
|
+
});
|
|
1304
|
+
});
|
|
@@ -1712,3 +1712,55 @@ describe("config invariant flag enrichment", () => {
|
|
|
1712
1712
|
}
|
|
1713
1713
|
});
|
|
1714
1714
|
});
|
|
1715
|
+
|
|
1716
|
+
describe("provider membership at the write choke point", () => {
|
|
1717
|
+
const configPatchRoute = ROUTES.find(
|
|
1718
|
+
(r) => r.operationId === "config_patch",
|
|
1719
|
+
)!;
|
|
1720
|
+
|
|
1721
|
+
test("a pre-existing unknown provider does not block unrelated writes", async () => {
|
|
1722
|
+
// Read tolerance means a stored entry-name provider is legal on disk;
|
|
1723
|
+
// re-validating it on every write would make all later settings saves
|
|
1724
|
+
// fail with no in-product repair path.
|
|
1725
|
+
rawConfigFixture = {
|
|
1726
|
+
llm: {
|
|
1727
|
+
profiles: {
|
|
1728
|
+
work: {
|
|
1729
|
+
source: "user",
|
|
1730
|
+
provider: "anthropic-work",
|
|
1731
|
+
model: "claude-opus-4-8",
|
|
1732
|
+
},
|
|
1733
|
+
},
|
|
1734
|
+
},
|
|
1735
|
+
};
|
|
1736
|
+
seedRawConfig();
|
|
1737
|
+
|
|
1738
|
+
await configPatchRoute.handler({
|
|
1739
|
+
body: { llm: { callSites: { mainAgent: { maxTokens: 2048 } } } },
|
|
1740
|
+
});
|
|
1741
|
+
|
|
1742
|
+
const llm = loadRawConfig().llm as {
|
|
1743
|
+
callSites?: Record<string, { maxTokens?: number }>;
|
|
1744
|
+
profiles?: Record<string, { provider?: string }>;
|
|
1745
|
+
};
|
|
1746
|
+
expect(llm.callSites?.mainAgent?.maxTokens).toBe(2048);
|
|
1747
|
+
expect(llm.profiles?.work?.provider).toBe("anthropic-work");
|
|
1748
|
+
});
|
|
1749
|
+
|
|
1750
|
+
test("introducing an unknown provider is rejected", async () => {
|
|
1751
|
+
rawConfigFixture = { llm: {} };
|
|
1752
|
+
seedRawConfig();
|
|
1753
|
+
|
|
1754
|
+
await expect(
|
|
1755
|
+
configPatchRoute.handler({
|
|
1756
|
+
body: {
|
|
1757
|
+
llm: {
|
|
1758
|
+
profiles: {
|
|
1759
|
+
rogue: { source: "user", provider: "not-a-provider", model: "m" },
|
|
1760
|
+
},
|
|
1761
|
+
},
|
|
1762
|
+
},
|
|
1763
|
+
}),
|
|
1764
|
+
).rejects.toThrow(/Invalid provider/);
|
|
1765
|
+
});
|
|
1766
|
+
});
|
|
@@ -432,6 +432,31 @@ describe("PUT config/llm/default-provider", () => {
|
|
|
432
432
|
expect(availability(result)).toEqual({ status: "ok" });
|
|
433
433
|
});
|
|
434
434
|
|
|
435
|
+
test("chatgpt persists and resolves the canonical subscription row", async () => {
|
|
436
|
+
seedConnection({
|
|
437
|
+
name: "chatgpt-subscription",
|
|
438
|
+
provider: "chatgpt",
|
|
439
|
+
auth: {
|
|
440
|
+
type: "oauth_subscription",
|
|
441
|
+
credential: "credential/chatgpt/access_token",
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
secureKeyResults["credential/chatgpt/access_token"] = {
|
|
445
|
+
value: "token",
|
|
446
|
+
unreachable: false,
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const result = (await put({ provider: "chatgpt" })) as Record<
|
|
450
|
+
string,
|
|
451
|
+
unknown
|
|
452
|
+
>;
|
|
453
|
+
|
|
454
|
+
expect(persistedDefaultProvider()).toEqual({ provider: "chatgpt" });
|
|
455
|
+
expect(result.provider).toBe("chatgpt");
|
|
456
|
+
expect(result.resolvedConnectionName).toBe("chatgpt-subscription");
|
|
457
|
+
expect(availability(result)).toEqual({ status: "ok" });
|
|
458
|
+
});
|
|
459
|
+
|
|
435
460
|
test("persists an explicit connectionName", async () => {
|
|
436
461
|
await put({ provider: "openai", connectionName: "work-openai" });
|
|
437
462
|
expect(persistedDefaultProvider()).toEqual({
|
|
@@ -449,6 +474,42 @@ describe("PUT config/llm/default-provider", () => {
|
|
|
449
474
|
expect(availability(result).status).toBe("missing_connection");
|
|
450
475
|
});
|
|
451
476
|
|
|
477
|
+
test("a noncanonical pin on a routing identity → 400 naming the canonical row", async () => {
|
|
478
|
+
for (const [provider, canonical] of [
|
|
479
|
+
["chatgpt", "chatgpt-subscription"],
|
|
480
|
+
["vellum", "vellum"],
|
|
481
|
+
] as const) {
|
|
482
|
+
const err = await put({
|
|
483
|
+
provider,
|
|
484
|
+
connectionName: "some-other-row",
|
|
485
|
+
}).catch((e: unknown) => e);
|
|
486
|
+
expect(String(err)).toContain(canonical);
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test("the canonical pin on a routing identity is accepted", async () => {
|
|
491
|
+
seedConnection({
|
|
492
|
+
name: "chatgpt-subscription",
|
|
493
|
+
provider: "chatgpt",
|
|
494
|
+
auth: {
|
|
495
|
+
type: "oauth_subscription",
|
|
496
|
+
credential: "credential/chatgpt/access_token",
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
secureKeyResults["credential/chatgpt/access_token"] = {
|
|
500
|
+
value: "token",
|
|
501
|
+
unreachable: false,
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
const result = (await put({
|
|
505
|
+
provider: "chatgpt",
|
|
506
|
+
connectionName: "chatgpt-subscription",
|
|
507
|
+
})) as Record<string, unknown>;
|
|
508
|
+
|
|
509
|
+
expect(result.resolvedConnectionName).toBe("chatgpt-subscription");
|
|
510
|
+
expect(availability(result)).toEqual({ status: "ok" });
|
|
511
|
+
});
|
|
512
|
+
|
|
452
513
|
test("invalid provider → 400 naming the allowed providers", async () => {
|
|
453
514
|
const err = await put({ provider: "not-a-provider" }).catch(
|
|
454
515
|
(e: unknown) => e,
|