@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
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join, relative, sep } from "node:path";
|
|
3
|
+
import { describe, expect, test } from "bun:test";
|
|
4
|
+
|
|
5
|
+
import { Glob } from "bun";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Ratchet over direct reads of the `messages` table outside `persistence/`.
|
|
9
|
+
*
|
|
10
|
+
* Every row in `messages` is conversation content in some state of
|
|
11
|
+
* completeness, and each read encodes a visibility decision (completed
|
|
12
|
+
* history vs. any row). `persistence/` owns that contract: named accessors in
|
|
13
|
+
* `message-reads.ts` and the history readers in `conversation-crud.ts` state
|
|
14
|
+
* their decision in their name; everything else should go through them. A
|
|
15
|
+
* direct reader that re-decides visibility on its own is how the contract
|
|
16
|
+
* fails silently, so a new direct read is a reviewed exception, not a
|
|
17
|
+
* default.
|
|
18
|
+
*
|
|
19
|
+
* The BASELINE lists every file outside `persistence/` that reads the table
|
|
20
|
+
* directly today. Each entry has been audited and carries its visibility
|
|
21
|
+
* decision as a comment at the read site. The guard fails when a file outside
|
|
22
|
+
* the baseline gains a direct read (route it through an accessor, or audit it
|
|
23
|
+
* and baseline it with a site comment explaining the decision in your PR),
|
|
24
|
+
* and when a baseline entry goes stale (remove it, so coverage cannot rot).
|
|
25
|
+
*
|
|
26
|
+
* Detection is deliberately shape-based (from/join of `messages` in drizzle
|
|
27
|
+
* builders, `FROM messages` / `JOIN messages` in raw SQL): this is a ratchet
|
|
28
|
+
* against sprawl, not a proof, and matching the read shapes keeps it free of
|
|
29
|
+
* false positives from unrelated identifiers.
|
|
30
|
+
*
|
|
31
|
+
* Regenerate after an intentional change with:
|
|
32
|
+
* UPDATE_MESSAGES_READ_BASELINE=1 bun test src/__tests__/messages-read-boundary-guard.test.ts
|
|
33
|
+
* and paste the printed list here, explaining the new read in your PR.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const SRC_REL = "src";
|
|
37
|
+
const SRC_ABS = join(process.cwd(), SRC_REL);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Audited direct readers outside persistence/, as file -> count of matching
|
|
41
|
+
* read sites. Counts, not just filenames: a new read added inside an
|
|
42
|
+
* already-baselined file must fail the ratchet too, since each site carries
|
|
43
|
+
* its own visibility decision. A same-file refactor that replaces one read
|
|
44
|
+
* with another at equal count passes; that is accepted ratchet granularity
|
|
45
|
+
* (the diff still shows the site to its reviewer).
|
|
46
|
+
*/
|
|
47
|
+
const BASELINE: Readonly<Record<string, number>> = {
|
|
48
|
+
"src/apps/app-store.ts": 1,
|
|
49
|
+
"src/daemon/credential-transcript-scrub.ts": 1,
|
|
50
|
+
"src/live-voice/live-voice-archive.ts": 1,
|
|
51
|
+
"src/monitoring/recovery/inflight-content.ts": 2,
|
|
52
|
+
"src/plugins/defaults/memory/context-search/sources/conversations.ts": 1,
|
|
53
|
+
"src/plugins/defaults/memory/graph/image-ref-utils.ts": 1,
|
|
54
|
+
"src/plugins/defaults/memory/indexer.ts": 2,
|
|
55
|
+
"src/plugins/defaults/memory/memory-retrospective-accounting.ts": 4,
|
|
56
|
+
"src/plugins/defaults/memory/substrate/sweep-job.ts": 1,
|
|
57
|
+
"src/plugins/defaults/memory/v1/graph/extraction.ts": 3,
|
|
58
|
+
"src/plugins/defaults/memory/v1/job-handlers/backfill.ts": 1,
|
|
59
|
+
"src/plugins/defaults/memory/v1/job-handlers/embedding.ts": 1,
|
|
60
|
+
"src/plugins/defaults/memory/v1/job-handlers/index-maintenance.ts": 1,
|
|
61
|
+
"src/plugins/defaults/memory/v2/harness/oracle.ts": 1,
|
|
62
|
+
"src/plugins/defaults/memory/v2/harness/replay-input.ts": 1,
|
|
63
|
+
"src/plugins/defaults/memory/v3-eval/eval-packets.ts": 2,
|
|
64
|
+
"src/plugins/defaults/memory/v3/prune.ts": 1,
|
|
65
|
+
"src/plugins/defaults/memory/v3/selection-log-store.ts": 1,
|
|
66
|
+
"src/runtime/pre-first-message-gate.ts": 1,
|
|
67
|
+
"src/runtime/routes/log-export-routes.ts": 1,
|
|
68
|
+
"src/runtime/routes/surface-conversation-resolver.ts": 2,
|
|
69
|
+
"src/telemetry/turn-events-store.ts": 2,
|
|
70
|
+
"src/telemetry/turn-trace-store.ts": 2,
|
|
71
|
+
"src/workspace/migrations/rebuild-conversation-disk-view.ts": 1,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const DRIZZLE_READ =
|
|
75
|
+
/\.(?:from|innerJoin|leftJoin|rightJoin|fullJoin)\(\s*messages\s*[,)]/g;
|
|
76
|
+
const RAW_SQL_READ = /(?:FROM|JOIN)\s+messages\b/g;
|
|
77
|
+
|
|
78
|
+
function isExempt(relPath: string): boolean {
|
|
79
|
+
const parts = relPath.split(sep);
|
|
80
|
+
return (
|
|
81
|
+
parts[1] === "persistence" ||
|
|
82
|
+
parts.includes("__tests__") ||
|
|
83
|
+
relPath.endsWith(".test.ts")
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function scanDirectReaders(): Promise<Map<string, number>> {
|
|
88
|
+
const glob = new Glob("**/*.ts");
|
|
89
|
+
const found = new Map<string, number>();
|
|
90
|
+
for await (const file of glob.scan({ cwd: SRC_ABS, absolute: true })) {
|
|
91
|
+
const relPath = join(SRC_REL, relative(SRC_ABS, file));
|
|
92
|
+
if (isExempt(relPath)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const source = readFileSync(file, "utf-8");
|
|
96
|
+
const count =
|
|
97
|
+
[...source.matchAll(DRIZZLE_READ)].length +
|
|
98
|
+
[...source.matchAll(RAW_SQL_READ)].length;
|
|
99
|
+
if (count > 0) {
|
|
100
|
+
found.set(relPath, count);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return found;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
describe("messages read boundary", () => {
|
|
107
|
+
test("no new direct messages reads beyond the audited baseline", async () => {
|
|
108
|
+
const found = await scanDirectReaders();
|
|
109
|
+
|
|
110
|
+
if (process.env.UPDATE_MESSAGES_READ_BASELINE === "1") {
|
|
111
|
+
console.log(
|
|
112
|
+
JSON.stringify(Object.fromEntries([...found].sort()), null, 2),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const grown = [...found]
|
|
117
|
+
.filter(([file, count]) => count > (BASELINE[file] ?? 0))
|
|
118
|
+
.map(([file, count]) => `${file}: ${BASELINE[file] ?? 0} -> ${count}`);
|
|
119
|
+
expect(
|
|
120
|
+
grown,
|
|
121
|
+
`New direct reads of the messages table outside persistence/ (route ` +
|
|
122
|
+
`through an accessor in persistence/message-reads.ts, or audit the ` +
|
|
123
|
+
`read, state its visibility decision at the site, and baseline it):`,
|
|
124
|
+
).toEqual([]);
|
|
125
|
+
|
|
126
|
+
const shrunk = Object.entries(BASELINE)
|
|
127
|
+
.filter(([file, count]) => (found.get(file) ?? 0) < count)
|
|
128
|
+
.map(([file, count]) => `${file}: ${count} -> ${found.get(file) ?? 0}`);
|
|
129
|
+
expect(
|
|
130
|
+
shrunk,
|
|
131
|
+
"Stale baseline entries (fewer direct reads than baselined; ratchet the count down so coverage cannot rot):",
|
|
132
|
+
).toEqual([]);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -30,14 +30,15 @@ import {
|
|
|
30
30
|
} from "bun:test";
|
|
31
31
|
|
|
32
32
|
import { _inspectHookCacheForTests } from "../hooks/hook-loader.js";
|
|
33
|
+
import { getUserHooksFor } from "../hooks/registry.js";
|
|
33
34
|
import {
|
|
34
35
|
_inspectToolCacheForTests,
|
|
35
36
|
getCachedUserTools,
|
|
36
|
-
getUserHooksFor,
|
|
37
37
|
populateCacheAtBoot,
|
|
38
38
|
reconcilePluginSourcesNow,
|
|
39
39
|
resetPluginCacheForTests,
|
|
40
40
|
} from "../plugins/mtime-cache.js";
|
|
41
|
+
import { resetPluginRegistryForTests } from "../plugins/registry.js";
|
|
41
42
|
import { getSourceVersionsPath } from "../plugins/source-versions.js";
|
|
42
43
|
import {
|
|
43
44
|
__resetRegistryForTesting,
|
|
@@ -157,6 +158,7 @@ beforeEach(() => {
|
|
|
157
158
|
// The registry pulls plugin tools via loadPluginTools(); clear its side
|
|
158
159
|
// (tools + pulled fingerprints) so registrations never leak across tests.
|
|
159
160
|
__resetRegistryForTesting();
|
|
161
|
+
resetPluginRegistryForTests();
|
|
160
162
|
});
|
|
161
163
|
|
|
162
164
|
afterAll(() => {
|
|
@@ -501,7 +501,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
501
501
|
|
|
502
502
|
test("notifyGuardianOfAccessRequest returns no_sender_id when actorExternalId is absent", async () => {
|
|
503
503
|
const result = await notifyGuardianOfAccessRequest({
|
|
504
|
-
canonicalAssistantId: "self",
|
|
505
504
|
sourceChannel: "telegram",
|
|
506
505
|
conversationExternalId: "chat-123",
|
|
507
506
|
actorExternalId: undefined,
|
|
@@ -522,7 +521,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
522
521
|
|
|
523
522
|
test("notifyGuardianOfAccessRequest creates request with self-healed principal when no binding exists", async () => {
|
|
524
523
|
const result = await notifyGuardianOfAccessRequest({
|
|
525
|
-
canonicalAssistantId: "self",
|
|
526
524
|
sourceChannel: "telegram",
|
|
527
525
|
conversationExternalId: "chat-123",
|
|
528
526
|
actorExternalId: "unknown-user",
|
|
@@ -565,7 +563,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
565
563
|
});
|
|
566
564
|
|
|
567
565
|
const result = await notifyGuardianOfAccessRequest({
|
|
568
|
-
canonicalAssistantId: "self",
|
|
569
566
|
sourceChannel: "telegram",
|
|
570
567
|
conversationExternalId: "tg-chat",
|
|
571
568
|
actorExternalId: "unknown-tg-user",
|
|
@@ -619,7 +616,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
619
616
|
});
|
|
620
617
|
|
|
621
618
|
const result = await notifyGuardianOfAccessRequest({
|
|
622
|
-
canonicalAssistantId: "self",
|
|
623
619
|
sourceChannel: "telegram",
|
|
624
620
|
conversationExternalId: "chat-123",
|
|
625
621
|
actorExternalId: "unknown-user",
|
|
@@ -660,7 +656,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
660
656
|
});
|
|
661
657
|
|
|
662
658
|
const result = await notifyGuardianOfAccessRequest({
|
|
663
|
-
canonicalAssistantId: "self",
|
|
664
659
|
sourceChannel: "telegram",
|
|
665
660
|
conversationExternalId: "chat-123",
|
|
666
661
|
actorExternalId: "unknown-user",
|
|
@@ -688,7 +683,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
688
683
|
test("notifyGuardianOfAccessRequest resolves the vellum anchor from the gateway delivery", async () => {
|
|
689
684
|
// Only the vellum anchor (seeded in resetState) is served by the gateway.
|
|
690
685
|
const result = await notifyGuardianOfAccessRequest({
|
|
691
|
-
canonicalAssistantId: "self",
|
|
692
686
|
sourceChannel: "telegram",
|
|
693
687
|
conversationExternalId: "chat-123",
|
|
694
688
|
actorExternalId: "unknown-user",
|
|
@@ -722,7 +716,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
722
716
|
|
|
723
717
|
await expect(
|
|
724
718
|
notifyGuardianOfAccessRequest({
|
|
725
|
-
canonicalAssistantId: "self",
|
|
726
719
|
sourceChannel: "telegram",
|
|
727
720
|
conversationExternalId: "chat-123",
|
|
728
721
|
actorExternalId: "unknown-user",
|
|
@@ -739,7 +732,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
739
732
|
|
|
740
733
|
test("notifyGuardianOfAccessRequest for voice channel includes actorDisplayName in contextPayload", async () => {
|
|
741
734
|
const result = await notifyGuardianOfAccessRequest({
|
|
742
|
-
canonicalAssistantId: "self",
|
|
743
735
|
sourceChannel: "phone",
|
|
744
736
|
conversationExternalId: "+15559998888",
|
|
745
737
|
actorExternalId: "+15559998888",
|
|
@@ -770,7 +762,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
770
762
|
|
|
771
763
|
test("notifyGuardianOfAccessRequest includes requestCode in contextPayload", async () => {
|
|
772
764
|
const result = await notifyGuardianOfAccessRequest({
|
|
773
|
-
canonicalAssistantId: "self",
|
|
774
765
|
sourceChannel: "telegram",
|
|
775
766
|
conversationExternalId: "chat-123",
|
|
776
767
|
actorExternalId: "unknown-user",
|
|
@@ -794,7 +785,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
794
785
|
// is created and the status is forwarded to the card payload. (A `revoked`
|
|
795
786
|
// status would instead suppress — covered by the keep-out tests.)
|
|
796
787
|
const result = await notifyGuardianOfAccessRequest({
|
|
797
|
-
canonicalAssistantId: "self",
|
|
798
788
|
sourceChannel: "telegram",
|
|
799
789
|
conversationExternalId: "chat-123",
|
|
800
790
|
actorExternalId: "pending-user",
|
|
@@ -834,7 +824,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
834
824
|
};
|
|
835
825
|
|
|
836
826
|
const result = await notifyGuardianOfAccessRequest({
|
|
837
|
-
canonicalAssistantId: "self",
|
|
838
827
|
sourceChannel: "phone",
|
|
839
828
|
conversationExternalId: "+15556667777",
|
|
840
829
|
actorExternalId: "+15556667777",
|
|
@@ -897,7 +886,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
897
886
|
};
|
|
898
887
|
|
|
899
888
|
const result = await notifyGuardianOfAccessRequest({
|
|
900
|
-
canonicalAssistantId: "self",
|
|
901
889
|
sourceChannel: "telegram",
|
|
902
890
|
conversationExternalId: "chat-123",
|
|
903
891
|
actorExternalId: "unknown-user",
|
|
@@ -929,7 +917,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
929
917
|
// request row. A revoked/blocked contact is deliberately kept out — no new
|
|
930
918
|
// prompt, signal, or pending request.
|
|
931
919
|
const result = await notifyGuardianOfAccessRequest({
|
|
932
|
-
canonicalAssistantId: "self",
|
|
933
920
|
sourceChannel: "telegram",
|
|
934
921
|
conversationExternalId: "chat-kept-out",
|
|
935
922
|
actorExternalId: "revoked-user",
|
|
@@ -969,7 +956,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
969
956
|
});
|
|
970
957
|
|
|
971
958
|
const result = await notifyGuardianOfAccessRequest({
|
|
972
|
-
canonicalAssistantId: "self",
|
|
973
959
|
sourceChannel: "telegram",
|
|
974
960
|
conversationExternalId: "chat-parked",
|
|
975
961
|
actorExternalId: "parked-user",
|
|
@@ -1006,7 +992,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
1006
992
|
});
|
|
1007
993
|
|
|
1008
994
|
const result = await notifyGuardianOfAccessRequest({
|
|
1009
|
-
canonicalAssistantId: "self",
|
|
1010
995
|
sourceChannel: "telegram",
|
|
1011
996
|
conversationExternalId: "chat-approved",
|
|
1012
997
|
actorExternalId: "approved-user",
|
|
@@ -1047,7 +1032,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
1047
1032
|
bridgeState.requests.get(requestId)!.updatedAt = staleUpdatedAt;
|
|
1048
1033
|
|
|
1049
1034
|
const result = await notifyGuardianOfAccessRequest({
|
|
1050
|
-
canonicalAssistantId: "self",
|
|
1051
1035
|
sourceChannel: "telegram",
|
|
1052
1036
|
conversationExternalId: "chat-stale",
|
|
1053
1037
|
actorExternalId: "stale-user",
|
|
@@ -1082,7 +1066,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
1082
1066
|
});
|
|
1083
1067
|
|
|
1084
1068
|
const result = await notifyGuardianOfAccessRequest({
|
|
1085
|
-
canonicalAssistantId: "self",
|
|
1086
1069
|
sourceChannel: "telegram",
|
|
1087
1070
|
conversationExternalId: "chat-flip",
|
|
1088
1071
|
actorExternalId: "flip-user",
|
|
@@ -1101,7 +1084,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
1101
1084
|
// Suppression is scoped to the sender via their own contact status, so a
|
|
1102
1085
|
// kept-out contact never bleeds into a different sender's prompt.
|
|
1103
1086
|
const keptOut = await notifyGuardianOfAccessRequest({
|
|
1104
|
-
canonicalAssistantId: "self",
|
|
1105
1087
|
sourceChannel: "telegram",
|
|
1106
1088
|
conversationExternalId: "chat-kept",
|
|
1107
1089
|
actorExternalId: "revoked-user",
|
|
@@ -1112,7 +1094,6 @@ describe("access-request-helper unit tests", () => {
|
|
|
1112
1094
|
|
|
1113
1095
|
// A different sender with no keep-out still gets a fresh prompt.
|
|
1114
1096
|
const result = await notifyGuardianOfAccessRequest({
|
|
1115
|
-
canonicalAssistantId: "self",
|
|
1116
1097
|
sourceChannel: "telegram",
|
|
1117
1098
|
conversationExternalId: "chat-fresh",
|
|
1118
1099
|
actorExternalId: "fresh-user",
|
|
@@ -1141,7 +1122,6 @@ describe("maybeNotifyGuardianOfAdmittedContact", () => {
|
|
|
1141
1122
|
});
|
|
1142
1123
|
|
|
1143
1124
|
const baseParams = {
|
|
1144
|
-
canonicalAssistantId: "self",
|
|
1145
1125
|
sourceChannel: "telegram" as const,
|
|
1146
1126
|
conversationExternalId: "chat-123",
|
|
1147
1127
|
actorExternalId: "user-unknown-456",
|
|
@@ -103,7 +103,10 @@ mock.module("../persistence/conversation-crud.js", () => ({
|
|
|
103
103
|
},
|
|
104
104
|
// The handler treats provenance as a flat spread; returning {} keeps the
|
|
105
105
|
// metadata snapshot focused on the fields under test.
|
|
106
|
-
|
|
106
|
+
// Mirrors the real mapping so provenance assertions observe what production
|
|
107
|
+
// stamps rather than a placeholder.
|
|
108
|
+
provenanceFromTrustContext: (ctx?: { trustClass?: string }) =>
|
|
109
|
+
ctx ? { provenanceTrustClass: ctx.trustClass } : {},
|
|
107
110
|
reserveMessage: mock(
|
|
108
111
|
async (
|
|
109
112
|
conversationId: string,
|
|
@@ -187,6 +190,8 @@ function makeDeps(
|
|
|
187
190
|
requesterTimezoneLabel?: string;
|
|
188
191
|
clientTimezone?: string;
|
|
189
192
|
sourceThreadId?: string;
|
|
193
|
+
/** Turn-local trust; when set, the live slot below diverges from it. */
|
|
194
|
+
currentTurnTrustContext?: Record<string, unknown>;
|
|
190
195
|
} = {},
|
|
191
196
|
): EventHandlerDeps {
|
|
192
197
|
const assistantMessageChannel = overrides.assistantMessageChannel ?? "slack";
|
|
@@ -195,6 +200,7 @@ function makeDeps(
|
|
|
195
200
|
conversationId,
|
|
196
201
|
provider: { name: "anthropic" },
|
|
197
202
|
currentTurnSurfaces: [],
|
|
203
|
+
currentTurnTrustContext: overrides.currentTurnTrustContext,
|
|
198
204
|
trustContext: {
|
|
199
205
|
sourceChannel: assistantMessageChannel,
|
|
200
206
|
trustClass: "guardian",
|
|
@@ -439,6 +445,39 @@ describe("outbound assistant Slack metadata persistence", () => {
|
|
|
439
445
|
expect(slackMeta.channelTs).toBeUndefined();
|
|
440
446
|
});
|
|
441
447
|
|
|
448
|
+
test("envelope resolves from the turn's actor when the slot has moved", async () => {
|
|
449
|
+
// Provenance class and Slack routing must come from ONE actor: the turn's.
|
|
450
|
+
// A queued Slack turn runs while the guardian has since written the live
|
|
451
|
+
// slot; the assistant row must carry the sender's class AND the sender's
|
|
452
|
+
// channel/thread, not a mixed envelope.
|
|
453
|
+
const conversationId = "conv-slack-moved-slot";
|
|
454
|
+
const deps = makeDeps(conversationId, {
|
|
455
|
+
assistantMessageChannel: "slack",
|
|
456
|
+
// Live slot: the guardian, on a different chat, no thread.
|
|
457
|
+
requesterChatId: "C-GUARDIAN",
|
|
458
|
+
currentTurnTrustContext: {
|
|
459
|
+
sourceChannel: "slack",
|
|
460
|
+
trustClass: "trusted_contact",
|
|
461
|
+
requesterChatId: "C-CONTACT",
|
|
462
|
+
sourceThreadId: "1723300000.000100",
|
|
463
|
+
},
|
|
464
|
+
});
|
|
465
|
+
await handleLlmCallStarted(state, deps);
|
|
466
|
+
await handleMessageComplete(
|
|
467
|
+
state,
|
|
468
|
+
deps,
|
|
469
|
+
makeMessageCompleteEvent("reply to the contact"),
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
const persisted = lastAssistantPersisted();
|
|
473
|
+
expect(persisted.metadata?.provenanceTrustClass).toBe("trusted_contact");
|
|
474
|
+
const slackMeta = JSON.parse(
|
|
475
|
+
persisted.metadata?.slackMeta as string,
|
|
476
|
+
) as Record<string, unknown>;
|
|
477
|
+
expect(slackMeta.channelId).toBe("C-CONTACT");
|
|
478
|
+
expect(slackMeta.threadTs).toBe("1723300000.000100");
|
|
479
|
+
});
|
|
480
|
+
|
|
442
481
|
test("ignores a non-ts sourceThreadId", async () => {
|
|
443
482
|
const conversationId = "conv-slack-bad-thread";
|
|
444
483
|
const channelId = "C789BAD";
|
|
@@ -44,12 +44,8 @@ mock.module("../permissions/checker.js", () => ({
|
|
|
44
44
|
}
|
|
45
45
|
return { decision: "allow", reason: "allowed" };
|
|
46
46
|
},
|
|
47
|
-
generateAllowlistOptions: () => [
|
|
48
|
-
{ label: "exact", description: "exact", pattern: "exact" },
|
|
49
|
-
],
|
|
50
47
|
generateScopeOptions: () =>
|
|
51
48
|
scopeOptionsOverride ?? [{ label: "/tmp", scope: "/tmp" }],
|
|
52
|
-
getCachedAssessment: () => undefined,
|
|
53
49
|
}));
|
|
54
50
|
|
|
55
51
|
// Mock every export so downstream test files that dynamically import modules
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
afterAll,
|
|
7
|
+
afterEach,
|
|
8
|
+
beforeEach,
|
|
9
|
+
describe,
|
|
10
|
+
expect,
|
|
11
|
+
spyOn,
|
|
12
|
+
test,
|
|
13
|
+
} from "bun:test";
|
|
14
|
+
|
|
15
|
+
import { credentialKey } from "@vellumai/credential-storage";
|
|
16
|
+
|
|
17
|
+
import * as scrub from "../daemon/credential-transcript-scrub.js";
|
|
18
|
+
import * as manualToken from "../oauth/manual-token-connection.js";
|
|
19
|
+
import { resolveCredential } from "../plugin-api/resolve-credential.js";
|
|
20
|
+
import {
|
|
21
|
+
CredentialStoreError,
|
|
22
|
+
storeCredential,
|
|
23
|
+
} from "../plugin-api/store-credential.js";
|
|
24
|
+
import { runInPluginContext } from "../plugins/plugin-execution-context.js";
|
|
25
|
+
import * as secureKeys from "../security/secure-keys.js";
|
|
26
|
+
import {
|
|
27
|
+
_setMetadataPath,
|
|
28
|
+
getCredentialMetadata,
|
|
29
|
+
} from "../tools/credentials/metadata-store.js";
|
|
30
|
+
import { storeCredentialValue } from "../tools/credentials/store.js";
|
|
31
|
+
|
|
32
|
+
// Real metadata store backed by a temp file. A mock.module on metadata-store
|
|
33
|
+
// or secure-keys would replace the whole module namespace, so only the secure
|
|
34
|
+
// backend access, the transcript scrub, and the manual-token connection sync
|
|
35
|
+
// are intercepted, via restorable spies.
|
|
36
|
+
|
|
37
|
+
const TEST_DIR = join(
|
|
38
|
+
tmpdir(),
|
|
39
|
+
`vellum-plugin-storecred-${randomBytes(4).toString("hex")}`,
|
|
40
|
+
);
|
|
41
|
+
const META_PATH = join(TEST_DIR, "metadata.json");
|
|
42
|
+
|
|
43
|
+
/** The plugin every scoped case runs as: it owns the `acme` field. */
|
|
44
|
+
const PLUGIN = "acme";
|
|
45
|
+
|
|
46
|
+
/** Run `fn` as the plugin would: inside its execution context. */
|
|
47
|
+
function asPlugin<T>(fn: () => T): T {
|
|
48
|
+
return runInPluginContext(PLUGIN, fn);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let secureStore: Map<string, string>;
|
|
52
|
+
let writeSucceeds: boolean;
|
|
53
|
+
let setSpy: ReturnType<typeof spyOn>;
|
|
54
|
+
let getSpy: ReturnType<typeof spyOn>;
|
|
55
|
+
let scrubSpy: ReturnType<typeof spyOn>;
|
|
56
|
+
let syncSpy: ReturnType<typeof spyOn>;
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
if (existsSync(TEST_DIR)) {
|
|
60
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
63
|
+
_setMetadataPath(META_PATH);
|
|
64
|
+
|
|
65
|
+
secureStore = new Map();
|
|
66
|
+
writeSucceeds = true;
|
|
67
|
+
setSpy = spyOn(secureKeys, "setSecureKeyAsync").mockImplementation(
|
|
68
|
+
async (key: string, value: string) => {
|
|
69
|
+
if (!writeSucceeds) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
secureStore.set(key, value);
|
|
73
|
+
return true;
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
getSpy = spyOn(secureKeys, "getSecureKeyResultAsync").mockImplementation(
|
|
77
|
+
async (key: string) => ({
|
|
78
|
+
value: secureStore.get(key),
|
|
79
|
+
unreachable: false,
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
scrubSpy = spyOn(
|
|
83
|
+
scrub,
|
|
84
|
+
"scrubStoredCredentialFromTranscripts",
|
|
85
|
+
).mockImplementation(async () => ({
|
|
86
|
+
dbMessagesScrubbed: 0,
|
|
87
|
+
residentMessagesScrubbed: 0,
|
|
88
|
+
}));
|
|
89
|
+
syncSpy = spyOn(manualToken, "syncManualTokenConnection").mockImplementation(
|
|
90
|
+
async () => {},
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
afterEach(() => {
|
|
95
|
+
setSpy.mockRestore();
|
|
96
|
+
getSpy.mockRestore();
|
|
97
|
+
scrubSpy.mockRestore();
|
|
98
|
+
syncSpy.mockRestore();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
afterAll(() => {
|
|
102
|
+
_setMetadataPath(null);
|
|
103
|
+
if (existsSync(TEST_DIR)) {
|
|
104
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("storeCredential", () => {
|
|
109
|
+
test("creates a credential from a service/field ref", async () => {
|
|
110
|
+
const stored = await asPlugin(() =>
|
|
111
|
+
storeCredential("openai/acme", "sk-secret"),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
expect(stored.service).toBe("openai");
|
|
115
|
+
expect(stored.field).toBe(PLUGIN);
|
|
116
|
+
expect(stored.credentialId).toBeTruthy();
|
|
117
|
+
expect(secureStore.get(credentialKey("openai", "acme"))).toBe("sk-secret");
|
|
118
|
+
expect(getCredentialMetadata("openai", "acme")?.credentialId).toBe(
|
|
119
|
+
stored.credentialId,
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("stores a value that resolveCredential reads back", async () => {
|
|
124
|
+
await asPlugin(() => storeCredential("openai/acme", "sk-secret"));
|
|
125
|
+
await expect(
|
|
126
|
+
asPlugin(() => resolveCredential("openai/acme")),
|
|
127
|
+
).resolves.toBe("sk-secret");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("trims edge whitespace from the value", async () => {
|
|
131
|
+
await asPlugin(() => storeCredential("openai/acme", " sk-secret\n"));
|
|
132
|
+
expect(secureStore.get(credentialKey("openai", "acme"))).toBe("sk-secret");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("records label and description on the credential metadata", async () => {
|
|
136
|
+
await asPlugin(() =>
|
|
137
|
+
storeCredential("openai/acme", "sk-secret", {
|
|
138
|
+
label: "Acme key",
|
|
139
|
+
description: "Used by the acme plugin",
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const metadata = getCredentialMetadata("openai", "acme");
|
|
144
|
+
expect(metadata?.alias).toBe("Acme key");
|
|
145
|
+
expect(metadata?.usageDescription).toBe("Used by the acme plugin");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("replaces the value of an existing credential named by UUID", async () => {
|
|
149
|
+
const first = await asPlugin(() =>
|
|
150
|
+
storeCredential("openai/acme", "sk-old"),
|
|
151
|
+
);
|
|
152
|
+
const second = await asPlugin(() =>
|
|
153
|
+
storeCredential(first.credentialId, "sk-new"),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
expect(second).toEqual(first);
|
|
157
|
+
expect(secureStore.get(credentialKey("openai", "acme"))).toBe("sk-new");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("rejects a ref that names no credential and is not service/field", async () => {
|
|
161
|
+
await expect(
|
|
162
|
+
asPlugin(() => storeCredential("openai", "sk-secret")),
|
|
163
|
+
).rejects.toBeInstanceOf(CredentialStoreError);
|
|
164
|
+
expect(setSpy).not.toHaveBeenCalled();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("rejects an empty value", async () => {
|
|
168
|
+
await expect(
|
|
169
|
+
asPlugin(() => storeCredential("openai/acme", " ")),
|
|
170
|
+
).rejects.toThrow(/value is required/);
|
|
171
|
+
expect(setSpy).not.toHaveBeenCalled();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("surfaces a failed secure-backend write", async () => {
|
|
175
|
+
writeSucceeds = false;
|
|
176
|
+
await expect(
|
|
177
|
+
asPlugin(() => storeCredential("openai/acme", "sk-secret")),
|
|
178
|
+
).rejects.toThrow(CredentialStoreError);
|
|
179
|
+
expect(getCredentialMetadata("openai", "acme")).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("scrubs the stored value from recent transcripts", async () => {
|
|
183
|
+
await asPlugin(() => storeCredential("openai/acme", "sk-secret"));
|
|
184
|
+
expect(scrubSpy).toHaveBeenCalledWith("sk-secret");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("skips the transcript scrub when asked", async () => {
|
|
188
|
+
await asPlugin(() =>
|
|
189
|
+
storeCredential("openai/acme", "sk-secret", {
|
|
190
|
+
skipTranscriptScrub: true,
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
expect(scrubSpy).not.toHaveBeenCalled();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("reconciles the manual-token connection for the service", async () => {
|
|
197
|
+
await asPlugin(() => storeCredential("openai/acme", "sk-secret"));
|
|
198
|
+
expect(syncSpy).toHaveBeenCalledWith("openai");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("plugin scoping", () => {
|
|
202
|
+
test("fails closed with no plugin in context", async () => {
|
|
203
|
+
// A plugin's module body is evaluated outside any context. An unscoped
|
|
204
|
+
// branch there would let top-level code overwrite the user's own
|
|
205
|
+
// credentials, so the write is refused instead.
|
|
206
|
+
await expect(storeCredential("openai/acme", "sk-secret")).rejects.toThrow(
|
|
207
|
+
/requires an active plugin execution context/,
|
|
208
|
+
);
|
|
209
|
+
expect(setSpy).not.toHaveBeenCalled();
|
|
210
|
+
expect(getCredentialMetadata("openai", "acme")).toBeUndefined();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("blocks a plugin from storing a credential whose field differs from its name", async () => {
|
|
214
|
+
await expect(
|
|
215
|
+
asPlugin(() => storeCredential("openai/api_key", "sk-secret")),
|
|
216
|
+
).rejects.toThrow(/out of scope/);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("does not write the secure backend when out of scope", async () => {
|
|
220
|
+
await expect(
|
|
221
|
+
asPlugin(() => storeCredential("openai/api_key", "sk-secret")),
|
|
222
|
+
).rejects.toThrow(CredentialStoreError);
|
|
223
|
+
expect(setSpy).not.toHaveBeenCalled();
|
|
224
|
+
expect(getCredentialMetadata("openai", "api_key")).toBeUndefined();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("blocks a plugin from overwriting another credential by UUID", async () => {
|
|
228
|
+
// Seeded through the host write path, the way the user's own credential
|
|
229
|
+
// is created (`credentials set`), not through the plugin API.
|
|
230
|
+
const existing = await storeCredentialValue({
|
|
231
|
+
service: "openai",
|
|
232
|
+
field: "api_key",
|
|
233
|
+
value: "sk-user",
|
|
234
|
+
skipTranscriptScrub: true,
|
|
235
|
+
});
|
|
236
|
+
setSpy.mockClear();
|
|
237
|
+
|
|
238
|
+
await expect(
|
|
239
|
+
asPlugin(() => storeCredential(existing.credentialId, "sk-hijacked")),
|
|
240
|
+
).rejects.toThrow(/out of scope/);
|
|
241
|
+
expect(setSpy).not.toHaveBeenCalled();
|
|
242
|
+
expect(secureStore.get(credentialKey("openai", "api_key"))).toBe(
|
|
243
|
+
"sk-user",
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("scoping applies by field only, across any service", async () => {
|
|
248
|
+
await asPlugin(async () => {
|
|
249
|
+
await storeCredential("stripe/acme", "stripe-secret");
|
|
250
|
+
await storeCredential("openai/acme", "openai-secret");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
expect(secureStore.get(credentialKey("stripe", "acme"))).toBe(
|
|
254
|
+
"stripe-secret",
|
|
255
|
+
);
|
|
256
|
+
expect(secureStore.get(credentialKey("openai", "acme"))).toBe(
|
|
257
|
+
"openai-secret",
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
});
|