@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
|
@@ -209,8 +209,8 @@ describe("credentials/prompt route", () => {
|
|
|
209
209
|
test("stores a non-slack credential to secure storage and reports the value as received", async () => {
|
|
210
210
|
/**
|
|
211
211
|
* The default delivery persists the value to secure storage and runs the
|
|
212
|
-
* manual-token connection sync. The message must name the outcome
|
|
213
|
-
* value is in hand
|
|
212
|
+
* manual-token connection sync. The message must name the outcome (the
|
|
213
|
+
* value is in hand) so a bare `ok: true` cannot be misread as "the prompt
|
|
214
214
|
* opened".
|
|
215
215
|
*/
|
|
216
216
|
// GIVEN a standard store-delivery prompt for a non-slack credential
|
|
@@ -236,15 +236,12 @@ describe("credentials/prompt route", () => {
|
|
|
236
236
|
|
|
237
237
|
test("every successful outcome states that the prompt has closed", async () => {
|
|
238
238
|
/**
|
|
239
|
-
* The prompt blocks until answered, so a caller reads the result only
|
|
240
|
-
* the secure input is gone.
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* the Aug 2026 Claude Code auth feedback. Every success path must say so,
|
|
244
|
-
* not just the plain vault write.
|
|
239
|
+
* The prompt blocks until answered, so a caller reads the result only once
|
|
240
|
+
* the secure input is gone. Every success path states the close, not just
|
|
241
|
+
* the plain vault write, so the result cannot be read as "the prompt is
|
|
242
|
+
* open, paste it there".
|
|
245
243
|
*/
|
|
246
|
-
const closed =
|
|
247
|
-
"The secure prompt has closed — do not ask the user to enter this value again.";
|
|
244
|
+
const closed = "The secure prompt has closed.";
|
|
248
245
|
|
|
249
246
|
// GIVEN a plain vault write
|
|
250
247
|
const stored = (await promptRoute!.handler({
|
|
@@ -238,6 +238,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
238
238
|
"workspace/default-provider-ensure.ts", // legacy anthropic echo disambiguation (vault key presence check)
|
|
239
239
|
"providers/inference/connection-availability.ts", // shared (provider, connection) availability status (credential presence check only; value never leaves the helper)
|
|
240
240
|
"plugin-api/resolve-credential.ts", // plugin-facing resolveCredential — reveal-equivalent plaintext read, scoped to the in-context plugin's own field
|
|
241
|
+
"tools/credentials/store.ts", // shared credential write path (setSecureKeyAsync only; no reads) behind credentials/set and plugin-facing storeCredential
|
|
241
242
|
]);
|
|
242
243
|
|
|
243
244
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -53,6 +53,10 @@ describe("ensureCompleteCustomProfiles", () => {
|
|
|
53
53
|
ensureCompleteCustomProfiles(workspaceDir);
|
|
54
54
|
const saved = readProfiles().partial;
|
|
55
55
|
expect(saved.model).toBe("claude-haiku-4-5-20251001");
|
|
56
|
+
// This workspace has no connection rows, so the binding is unverifiable
|
|
57
|
+
// and passes through in the legacy field for the collapse migration to
|
|
58
|
+
// judge; with a verified row it would fold into the provider value (see
|
|
59
|
+
// profile-materialization.test.ts).
|
|
56
60
|
expect(saved.provider).toBe("anthropic");
|
|
57
61
|
expect(saved.provider_connection).toBe("anthropic-personal");
|
|
58
62
|
expect(saved.maxTokens).toBe(12345);
|
|
@@ -91,7 +95,7 @@ describe("ensureCompleteCustomProfiles", () => {
|
|
|
91
95
|
},
|
|
92
96
|
});
|
|
93
97
|
ensureCompleteCustomProfiles(workspaceDir);
|
|
94
|
-
expect(readProfiles().gpt.provider_connection).
|
|
98
|
+
expect(readProfiles().gpt.provider_connection).toBeUndefined();
|
|
95
99
|
expect(readProfiles().router.provider_connection).toBeUndefined();
|
|
96
100
|
});
|
|
97
101
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import { getAllDefaultPlugins } from "../plugins/defaults/index.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getAllDefaultPluginNames,
|
|
6
|
+
isFirstPartyDefaultPlugin,
|
|
7
|
+
} from "../plugins/defaults/main.js";
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Guard: the filesystem-backed default-plugin registry (`defaults/main.ts`)
|
|
@@ -25,4 +28,12 @@ describe("default plugin names", () => {
|
|
|
25
28
|
expect(name).toMatch(/^default-[a-z0-9-]+$/);
|
|
26
29
|
}
|
|
27
30
|
});
|
|
31
|
+
|
|
32
|
+
test("isFirstPartyDefaultPlugin matches the filesystem registry and rejects other names", () => {
|
|
33
|
+
for (const name of getAllDefaultPluginNames()) {
|
|
34
|
+
expect(isFirstPartyDefaultPlugin(name)).toBe(true);
|
|
35
|
+
}
|
|
36
|
+
expect(isFirstPartyDefaultPlugin("default-a")).toBe(false);
|
|
37
|
+
expect(isFirstPartyDefaultPlugin("uplug-a")).toBe(false);
|
|
38
|
+
});
|
|
28
39
|
});
|
|
@@ -342,7 +342,11 @@ describe("Discord access-request decisions stay out of the guild channel", () =>
|
|
|
342
342
|
test("no Discord delivery is ever addressed to the originating conversation", async () => {
|
|
343
343
|
// The umbrella invariant, swept across every decision this resolver takes,
|
|
344
344
|
// so a newly added action cannot quietly reintroduce the leak.
|
|
345
|
-
for (const action of [
|
|
345
|
+
for (const action of [
|
|
346
|
+
"verify_code",
|
|
347
|
+
"block",
|
|
348
|
+
"leave_unverified",
|
|
349
|
+
] as const) {
|
|
346
350
|
resetState();
|
|
347
351
|
const req = makeDiscordAccessRequest();
|
|
348
352
|
|
|
@@ -27,9 +27,9 @@ describe("the dm-marked Discord route", () => {
|
|
|
27
27
|
// regressed, the notice would fall through to the HTTP proxy, which cannot
|
|
28
28
|
// fetch a base-less URL: the delivery would vanish rather than error, and
|
|
29
29
|
// every behavioural assertion about where it landed would still pass.
|
|
30
|
-
expect(
|
|
31
|
-
"discord",
|
|
32
|
-
);
|
|
30
|
+
expect(
|
|
31
|
+
channelForCallback(resolveDeliverCallbackUrlForChannel("discord")!),
|
|
32
|
+
).toBe("discord");
|
|
33
33
|
});
|
|
34
34
|
});
|
|
35
35
|
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { getDocumentById } from "../documents/document-store.js";
|
|
4
|
+
import { getSqlite } from "../persistence/db-connection.js";
|
|
5
|
+
import {
|
|
6
|
+
executeDocumentCreate,
|
|
7
|
+
executeDocumentUpdate,
|
|
8
|
+
} from "../tools/document/document-tool.js";
|
|
9
|
+
import type { ToolContext, ToolExecutionResult } from "../tools/types.js";
|
|
10
|
+
import { resetDbForTesting } from "./db-test-helpers.js";
|
|
11
|
+
|
|
12
|
+
const CONVERSATION_ID = "conv-current";
|
|
13
|
+
|
|
14
|
+
/** Long enough to clear the store's minimum-duplicate-length floor. */
|
|
15
|
+
const OPENING =
|
|
16
|
+
"Remote work reshaped the office long before anyone had a name for it.";
|
|
17
|
+
|
|
18
|
+
function makeContext(overrides: Partial<ToolContext> = {}): ToolContext {
|
|
19
|
+
return {
|
|
20
|
+
workingDir: "/tmp/project",
|
|
21
|
+
conversationId: CONVERSATION_ID,
|
|
22
|
+
trustClass: "trusted_contact",
|
|
23
|
+
executionChannel: "slack",
|
|
24
|
+
sendToClient: () => {},
|
|
25
|
+
...overrides,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseResult<T>(result: ToolExecutionResult): T {
|
|
30
|
+
return JSON.parse(result.content) as T;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function bootstrapDocumentTables(): void {
|
|
34
|
+
resetDbForTesting();
|
|
35
|
+
const raw = getSqlite();
|
|
36
|
+
raw.exec(/*sql*/ `
|
|
37
|
+
DROP TABLE IF EXISTS document_conversations;
|
|
38
|
+
DROP TABLE IF EXISTS documents;
|
|
39
|
+
DROP TABLE IF EXISTS conversations;
|
|
40
|
+
|
|
41
|
+
CREATE TABLE conversations (
|
|
42
|
+
id TEXT PRIMARY KEY,
|
|
43
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
CREATE TABLE documents (
|
|
47
|
+
surface_id TEXT PRIMARY KEY,
|
|
48
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
49
|
+
title TEXT NOT NULL,
|
|
50
|
+
content TEXT NOT NULL,
|
|
51
|
+
word_count INTEGER NOT NULL DEFAULT 0,
|
|
52
|
+
created_at INTEGER NOT NULL,
|
|
53
|
+
updated_at INTEGER NOT NULL,
|
|
54
|
+
workspace_path TEXT
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
CREATE TABLE document_conversations (
|
|
58
|
+
surface_id TEXT NOT NULL,
|
|
59
|
+
conversation_id TEXT NOT NULL,
|
|
60
|
+
created_at INTEGER NOT NULL,
|
|
61
|
+
PRIMARY KEY (surface_id, conversation_id),
|
|
62
|
+
FOREIGN KEY (surface_id) REFERENCES documents(surface_id) ON DELETE CASCADE
|
|
63
|
+
);
|
|
64
|
+
`);
|
|
65
|
+
raw
|
|
66
|
+
.query(`INSERT OR IGNORE INTO conversations (id, created_at) VALUES (?, ?)`)
|
|
67
|
+
.run(CONVERSATION_ID, Date.now());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function seedDocument(surfaceId: string, content: string): void {
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
const raw = getSqlite();
|
|
73
|
+
raw
|
|
74
|
+
.query(
|
|
75
|
+
`INSERT INTO documents (surface_id, conversation_id, title, content, word_count, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
76
|
+
)
|
|
77
|
+
.run(
|
|
78
|
+
surfaceId,
|
|
79
|
+
CONVERSATION_ID,
|
|
80
|
+
"Draft",
|
|
81
|
+
content,
|
|
82
|
+
content.split(/\s+/).filter(Boolean).length,
|
|
83
|
+
now,
|
|
84
|
+
now,
|
|
85
|
+
);
|
|
86
|
+
raw
|
|
87
|
+
.query(
|
|
88
|
+
`INSERT OR IGNORE INTO document_conversations (surface_id, conversation_id, created_at) VALUES (?, ?, ?)`,
|
|
89
|
+
)
|
|
90
|
+
.run(surfaceId, CONVERSATION_ID, now);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
describe("document append idempotency", () => {
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
bootstrapDocumentTables();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("an opening passed to both document_create and the first append lands once", () => {
|
|
99
|
+
const created = executeDocumentCreate(
|
|
100
|
+
{ title: "Draft", initial_content: `# Draft\n\n${OPENING}` },
|
|
101
|
+
makeContext(),
|
|
102
|
+
);
|
|
103
|
+
const surfaceId = parseResult<{ surface_id: string }>(created).surface_id;
|
|
104
|
+
|
|
105
|
+
const result = executeDocumentUpdate(
|
|
106
|
+
{
|
|
107
|
+
surface_id: surfaceId,
|
|
108
|
+
content: `# Draft\n\n${OPENING}\n\nThe second paragraph carries the argument forward.`,
|
|
109
|
+
mode: "append",
|
|
110
|
+
},
|
|
111
|
+
makeContext(),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
expect(result.isError).toBe(false);
|
|
115
|
+
expect(getDocumentById(surfaceId)?.content).toBe(
|
|
116
|
+
`# Draft\n\n${OPENING}\n\nThe second paragraph carries the argument forward.`,
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("an append that only restates the tail writes nothing", () => {
|
|
121
|
+
seedDocument("doc-restate", `# Draft\n\n${OPENING}`);
|
|
122
|
+
|
|
123
|
+
const result = executeDocumentUpdate(
|
|
124
|
+
{ surface_id: "doc-restate", content: OPENING, mode: "append" },
|
|
125
|
+
makeContext(),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
expect(result.isError).toBe(false);
|
|
129
|
+
const body = parseResult<{ success: boolean; message: string }>(result);
|
|
130
|
+
expect(body.success).toBe(true);
|
|
131
|
+
expect(body.message).toBe(
|
|
132
|
+
"Nothing appended: the content is already at the end of the document",
|
|
133
|
+
);
|
|
134
|
+
expect(getDocumentById("doc-restate")?.content).toBe(
|
|
135
|
+
`# Draft\n\n${OPENING}`,
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("the client is told what landed, not what was submitted", () => {
|
|
140
|
+
seedDocument("doc-client", OPENING);
|
|
141
|
+
const sent: { markdown: unknown; mode: unknown }[] = [];
|
|
142
|
+
|
|
143
|
+
executeDocumentUpdate(
|
|
144
|
+
{
|
|
145
|
+
surface_id: "doc-client",
|
|
146
|
+
content: `${OPENING}\n\nAnd then the rest.`,
|
|
147
|
+
mode: "append",
|
|
148
|
+
},
|
|
149
|
+
makeContext({
|
|
150
|
+
sendToClient: (message) => {
|
|
151
|
+
if (message.type === "document_editor_update") {
|
|
152
|
+
sent.push({ markdown: message.markdown, mode: message.mode });
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
expect(sent).toEqual([{ markdown: "And then the rest.", mode: "append" }]);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// ── Legitimate repetition the guard must not eat ──────────────────
|
|
162
|
+
|
|
163
|
+
test("keeps a short repeated line, even back to back", () => {
|
|
164
|
+
seedDocument("doc-refrain", "## Checklist\n\nTODO");
|
|
165
|
+
|
|
166
|
+
executeDocumentUpdate(
|
|
167
|
+
{ surface_id: "doc-refrain", content: "TODO\n\nTODO", mode: "append" },
|
|
168
|
+
makeContext(),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
expect(getDocumentById("doc-refrain")?.content).toBe(
|
|
172
|
+
"## Checklist\n\nTODO\n\nTODO\n\nTODO",
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("keeps a repeated heading followed by new prose", () => {
|
|
177
|
+
seedDocument("doc-heading", "# Weekly Notes\n\n## Monday");
|
|
178
|
+
|
|
179
|
+
executeDocumentUpdate(
|
|
180
|
+
{
|
|
181
|
+
surface_id: "doc-heading",
|
|
182
|
+
content: "## Monday\n\nShipped the importer.",
|
|
183
|
+
mode: "append",
|
|
184
|
+
},
|
|
185
|
+
makeContext(),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
expect(getDocumentById("doc-heading")?.content).toBe(
|
|
189
|
+
"# Weekly Notes\n\n## Monday\n\n## Monday\n\nShipped the importer.",
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("keeps a long paragraph that repeats something other than the tail", () => {
|
|
194
|
+
seedDocument("doc-midway", `${OPENING}\n\nA later paragraph ends the doc.`);
|
|
195
|
+
|
|
196
|
+
executeDocumentUpdate(
|
|
197
|
+
{ surface_id: "doc-midway", content: OPENING, mode: "append" },
|
|
198
|
+
makeContext(),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(getDocumentById("doc-midway")?.content).toBe(
|
|
202
|
+
`${OPENING}\n\nA later paragraph ends the doc.\n\n${OPENING}`,
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("keeps content whose repetition starts partway into the append", () => {
|
|
207
|
+
seedDocument("doc-partway", `# Draft\n\n${OPENING}`);
|
|
208
|
+
|
|
209
|
+
executeDocumentUpdate(
|
|
210
|
+
{
|
|
211
|
+
surface_id: "doc-partway",
|
|
212
|
+
content: `A fresh lead-in.\n\n${OPENING}`,
|
|
213
|
+
mode: "append",
|
|
214
|
+
},
|
|
215
|
+
makeContext(),
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
expect(getDocumentById("doc-partway")?.content).toBe(
|
|
219
|
+
`# Draft\n\n${OPENING}\n\nA fresh lead-in.\n\n${OPENING}`,
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("replace mode is never deduplicated", () => {
|
|
224
|
+
seedDocument("doc-replace", OPENING);
|
|
225
|
+
|
|
226
|
+
executeDocumentUpdate(
|
|
227
|
+
{ surface_id: "doc-replace", content: OPENING, mode: "replace" },
|
|
228
|
+
makeContext(),
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
expect(getDocumentById("doc-replace")?.content).toBe(OPENING);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
* the Library and the per-conversation assets list converge on every surface.
|
|
4
4
|
* Read routes and rejected writes must stay silent.
|
|
5
5
|
*/
|
|
6
|
-
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { join } from "node:path";
|
|
8
6
|
import { afterAll, beforeEach, describe, expect, test } from "bun:test";
|
|
9
7
|
|
|
10
8
|
import type { AssistantEventEnvelope } from "../api/index.js";
|
|
@@ -24,9 +22,6 @@ import { resetDbForTesting } from "./db-test-helpers.js";
|
|
|
24
22
|
|
|
25
23
|
await initializeDb();
|
|
26
24
|
|
|
27
|
-
const workspaceDir = process.env.VELLUM_WORKSPACE_DIR!;
|
|
28
|
-
const notesDir = join(workspaceDir, "sync-notes");
|
|
29
|
-
|
|
30
25
|
const CONVERSATION_ID = "conv-doc-sync";
|
|
31
26
|
const OTHER_CONVERSATION_ID = "conv-doc-sync-other";
|
|
32
27
|
const MISSING_CONVERSATION_ID = "conv-doc-sync-missing";
|
|
@@ -110,15 +105,6 @@ async function saveViaRoute(
|
|
|
110
105
|
});
|
|
111
106
|
}
|
|
112
107
|
|
|
113
|
-
async function openWorkspaceFile(
|
|
114
|
-
path: string,
|
|
115
|
-
conversationId = CONVERSATION_ID,
|
|
116
|
-
): Promise<unknown> {
|
|
117
|
-
return await invoke("documentForWorkspaceFile", {
|
|
118
|
-
body: { path, conversationId },
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
108
|
async function linkViaRoute(
|
|
123
109
|
surfaceId: string,
|
|
124
110
|
conversationId: string,
|
|
@@ -136,12 +122,9 @@ beforeEach(() => {
|
|
|
136
122
|
db.run("DELETE FROM conversations");
|
|
137
123
|
createConversation({ id: CONVERSATION_ID });
|
|
138
124
|
createConversation({ id: OTHER_CONVERSATION_ID });
|
|
139
|
-
rmSync(notesDir, { recursive: true, force: true });
|
|
140
|
-
mkdirSync(notesDir, { recursive: true });
|
|
141
125
|
});
|
|
142
126
|
|
|
143
127
|
afterAll(() => {
|
|
144
|
-
rmSync(notesDir, { recursive: true, force: true });
|
|
145
128
|
resetDbForTesting();
|
|
146
129
|
});
|
|
147
130
|
|
|
@@ -220,67 +203,9 @@ describe("document write routes publish documents:list", () => {
|
|
|
220
203
|
expect(events).toHaveLength(1);
|
|
221
204
|
expect("originClientId" in events[0].message).toBe(false);
|
|
222
205
|
});
|
|
223
|
-
|
|
224
|
-
test("binding a workspace file publishes without an origin so the caller is not suppressed", async () => {
|
|
225
|
-
// `viewer-store.loadWorkspaceFileDocument` creates the row and invalidates
|
|
226
|
-
// nothing, so the client that opened the file is the one that most needs
|
|
227
|
-
// the invalidation.
|
|
228
|
-
writeFileSync(join(notesDir, "plan.md"), "# Plan");
|
|
229
|
-
|
|
230
|
-
const events = await captureSyncEvents(() =>
|
|
231
|
-
invoke("documentForWorkspaceFile", {
|
|
232
|
-
body: { path: "sync-notes/plan.md", conversationId: CONVERSATION_ID },
|
|
233
|
-
headers: { "x-vellum-client-id": "client-abc" },
|
|
234
|
-
}),
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
expect(events).toHaveLength(1);
|
|
238
|
-
expect("originClientId" in events[0].message).toBe(false);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
test("binding a workspace file to a new document publishes", async () => {
|
|
242
|
-
writeFileSync(join(notesDir, "plan.md"), "# Plan");
|
|
243
|
-
|
|
244
|
-
const events = await captureSyncEvents(() =>
|
|
245
|
-
openWorkspaceFile("sync-notes/plan.md"),
|
|
246
|
-
);
|
|
247
|
-
expectDocumentsChanged(events);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
test("reopening the file from another conversation publishes the new link", async () => {
|
|
251
|
-
writeFileSync(join(notesDir, "plan.md"), "# Plan");
|
|
252
|
-
await openWorkspaceFile("sync-notes/plan.md");
|
|
253
|
-
|
|
254
|
-
const events = await captureSyncEvents(() =>
|
|
255
|
-
openWorkspaceFile("sync-notes/plan.md", OTHER_CONVERSATION_ID),
|
|
256
|
-
);
|
|
257
|
-
expectDocumentsChanged(events);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test("refreshing a document whose file changed on disk publishes", async () => {
|
|
261
|
-
const filePath = join(notesDir, "plan.md");
|
|
262
|
-
writeFileSync(filePath, "# Plan");
|
|
263
|
-
await openWorkspaceFile("sync-notes/plan.md");
|
|
264
|
-
writeFileSync(filePath, "# Plan\n\nedited outside the editor");
|
|
265
|
-
|
|
266
|
-
const events = await captureSyncEvents(() =>
|
|
267
|
-
openWorkspaceFile("sync-notes/plan.md"),
|
|
268
|
-
);
|
|
269
|
-
expectDocumentsChanged(events);
|
|
270
|
-
});
|
|
271
206
|
});
|
|
272
207
|
|
|
273
208
|
describe("document routes that change nothing the list shows stay silent", () => {
|
|
274
|
-
test("reopening an unchanged, already-linked file publishes nothing", async () => {
|
|
275
|
-
writeFileSync(join(notesDir, "plan.md"), "# Plan");
|
|
276
|
-
await openWorkspaceFile("sync-notes/plan.md");
|
|
277
|
-
|
|
278
|
-
const events = await captureSyncEvents(() =>
|
|
279
|
-
openWorkspaceFile("sync-notes/plan.md"),
|
|
280
|
-
);
|
|
281
|
-
expect(events).toEqual([]);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
209
|
test("a save rejected by validation publishes nothing", async () => {
|
|
285
210
|
const events = await captureSyncEvents(async () => {
|
|
286
211
|
await expect(saveViaRoute({ title: undefined })).rejects.toThrow(
|
|
@@ -65,9 +65,7 @@ mock.module("../permissions/checker.js", () => ({
|
|
|
65
65
|
isDynamicSkillLoadInvocation: () => dynamicSkillLoad,
|
|
66
66
|
classifyRisk: async () => ({ level: "medium" }),
|
|
67
67
|
check: async () => ({ decision: checkDecision, reason: "medium risk" }),
|
|
68
|
-
generateAllowlistOptions: () => [],
|
|
69
68
|
generateScopeOptions: () => [],
|
|
70
|
-
getCachedAssessment: () => undefined,
|
|
71
69
|
}));
|
|
72
70
|
|
|
73
71
|
mock.module("../telemetry/tool-usage-store.js", () => ({
|
|
@@ -145,7 +145,6 @@ describe("Slack edit propagation", () => {
|
|
|
145
145
|
conversationExternalId: seeded.conversationExternalId,
|
|
146
146
|
externalMessageId: nextEditEventId(),
|
|
147
147
|
sourceMessageId: seeded.channelTs,
|
|
148
|
-
canonicalAssistantId: "self",
|
|
149
148
|
assistantId: "self",
|
|
150
149
|
content: "new text",
|
|
151
150
|
});
|
|
@@ -172,7 +171,100 @@ describe("Slack edit propagation", () => {
|
|
|
172
171
|
expect(slackMeta!.editedAt!).toBeGreaterThanOrEqual(t0);
|
|
173
172
|
});
|
|
174
173
|
|
|
175
|
-
|
|
174
|
+
// Times out on the default budget: an unresolvable target pays the full
|
|
175
|
+
// `EDIT_LOOKUP_RETRIES` window before it is dropped.
|
|
176
|
+
test("an edit of a message the assistant never stored creates no conversation", async () => {
|
|
177
|
+
// Keying a conversation off the edit's own address made every edit of an
|
|
178
|
+
// unseen message mint one, permanently titled with the loading placeholder.
|
|
179
|
+
const db = getDb();
|
|
180
|
+
const countRows = (table: string): number =>
|
|
181
|
+
(
|
|
182
|
+
db.$client.prepare(`SELECT COUNT(*) AS n FROM ${table}`).get() as {
|
|
183
|
+
n: number;
|
|
184
|
+
}
|
|
185
|
+
).n;
|
|
186
|
+
|
|
187
|
+
const resp = (await handleEditIntercept({
|
|
188
|
+
sourceChannel: "slack",
|
|
189
|
+
conversationExternalId: "C0NEVERSEEN",
|
|
190
|
+
externalMessageId: nextEditEventId(),
|
|
191
|
+
sourceMessageId: "1999.0001",
|
|
192
|
+
assistantId: "self",
|
|
193
|
+
content: "edited text",
|
|
194
|
+
})) as Record<string, unknown>;
|
|
195
|
+
|
|
196
|
+
expect(resp.accepted).toBe(true);
|
|
197
|
+
expect(resp.edited).toBe(false);
|
|
198
|
+
expect(countRows("conversations")).toBe(0);
|
|
199
|
+
expect(countRows("channel_inbound_events")).toBe(0);
|
|
200
|
+
expect(countRows("messages")).toBe(0);
|
|
201
|
+
}, 30_000);
|
|
202
|
+
|
|
203
|
+
test("an edit records against the edited message's conversation, not a new one", async () => {
|
|
204
|
+
const seeded = await seedSlackMessage({
|
|
205
|
+
conversationExternalId: "C0123CHANNEL",
|
|
206
|
+
channelTs: "1234.5678",
|
|
207
|
+
initialContent: "original text",
|
|
208
|
+
});
|
|
209
|
+
const db = getDb();
|
|
210
|
+
const conversationsBefore = (
|
|
211
|
+
db.$client.prepare("SELECT COUNT(*) AS n FROM conversations").get() as {
|
|
212
|
+
n: number;
|
|
213
|
+
}
|
|
214
|
+
).n;
|
|
215
|
+
|
|
216
|
+
await handleEditIntercept({
|
|
217
|
+
sourceChannel: "slack",
|
|
218
|
+
conversationExternalId: seeded.conversationExternalId,
|
|
219
|
+
externalMessageId: nextEditEventId(),
|
|
220
|
+
sourceMessageId: seeded.channelTs,
|
|
221
|
+
assistantId: "self",
|
|
222
|
+
content: "new text",
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const conversationsAfter = (
|
|
226
|
+
db.$client.prepare("SELECT COUNT(*) AS n FROM conversations").get() as {
|
|
227
|
+
n: number;
|
|
228
|
+
}
|
|
229
|
+
).n;
|
|
230
|
+
expect(conversationsAfter).toBe(conversationsBefore);
|
|
231
|
+
|
|
232
|
+
const editEvents = db.$client
|
|
233
|
+
.prepare(
|
|
234
|
+
`SELECT conversation_id AS conversationId FROM channel_inbound_events
|
|
235
|
+
WHERE external_message_id LIKE 'edit-event-%'`,
|
|
236
|
+
)
|
|
237
|
+
.all() as Array<{ conversationId: string }>;
|
|
238
|
+
expect(editEvents).toHaveLength(1);
|
|
239
|
+
expect(editEvents[0].conversationId).toBe(seeded.conversationId);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("a redelivered edit is answered without repeating the lookup window", async () => {
|
|
243
|
+
const seeded = await seedSlackMessage({
|
|
244
|
+
conversationExternalId: "C0123CHANNEL",
|
|
245
|
+
channelTs: "1234.5678",
|
|
246
|
+
initialContent: "original text",
|
|
247
|
+
});
|
|
248
|
+
const editEventId = nextEditEventId();
|
|
249
|
+
const send = () =>
|
|
250
|
+
handleEditIntercept({
|
|
251
|
+
sourceChannel: "slack",
|
|
252
|
+
conversationExternalId: seeded.conversationExternalId,
|
|
253
|
+
externalMessageId: editEventId,
|
|
254
|
+
sourceMessageId: seeded.channelTs,
|
|
255
|
+
assistantId: "self",
|
|
256
|
+
content: "new text",
|
|
257
|
+
}) as Promise<Record<string, unknown>>;
|
|
258
|
+
|
|
259
|
+
const first = await send();
|
|
260
|
+
const second = await send();
|
|
261
|
+
|
|
262
|
+
expect(first.duplicate).toBe(false);
|
|
263
|
+
expect(second.duplicate).toBe(true);
|
|
264
|
+
expect(second.eventId).toBe(first.eventId);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("a threaded edit stays in the edited message's conversation and preserves thread metadata", async () => {
|
|
176
268
|
const conversationExternalId = "C0123CHANNEL";
|
|
177
269
|
const threadTs = "1234.0000";
|
|
178
270
|
const seeded = await seedSlackMessage({
|
|
@@ -180,6 +272,14 @@ describe("Slack edit propagation", () => {
|
|
|
180
272
|
channelTs: "1234.5678",
|
|
181
273
|
initialContent: "original text",
|
|
182
274
|
});
|
|
275
|
+
const db = getDb();
|
|
276
|
+
const conversationCount = (): number =>
|
|
277
|
+
(
|
|
278
|
+
db.$client.prepare("SELECT COUNT(*) AS n FROM conversations").get() as {
|
|
279
|
+
n: number;
|
|
280
|
+
}
|
|
281
|
+
).n;
|
|
282
|
+
const before = conversationCount();
|
|
183
283
|
|
|
184
284
|
const resp = await handleEditIntercept({
|
|
185
285
|
sourceChannel: "slack",
|
|
@@ -187,16 +287,17 @@ describe("Slack edit propagation", () => {
|
|
|
187
287
|
externalMessageId: nextEditEventId(),
|
|
188
288
|
sourceMessageId: seeded.channelTs,
|
|
189
289
|
sourceThreadId: threadTs,
|
|
190
|
-
canonicalAssistantId: "self",
|
|
191
290
|
assistantId: "self",
|
|
192
291
|
content: "new text",
|
|
193
292
|
});
|
|
194
293
|
|
|
195
294
|
expect((resp as Record<string, unknown>).accepted).toBe(true);
|
|
295
|
+
|
|
296
|
+
// The thread id names where the reply goes, not a conversation of the
|
|
297
|
+
// edit's own. Keying one off it is what minted a conversation per edit.
|
|
196
298
|
const threadedKey = `asst:self:slack:${conversationExternalId}:thread:${threadTs}`;
|
|
197
|
-
|
|
198
|
-
expect(
|
|
199
|
-
expect(editConversation!.conversationId).not.toBe(seeded.conversationId);
|
|
299
|
+
expect(getConversationByKey(threadedKey)).toBeNull();
|
|
300
|
+
expect(conversationCount()).toBe(before);
|
|
200
301
|
|
|
201
302
|
const after = readMessageRow(seeded.messageId);
|
|
202
303
|
const outer = JSON.parse(after.metadata!) as Record<string, unknown>;
|
|
@@ -216,7 +317,6 @@ describe("Slack edit propagation", () => {
|
|
|
216
317
|
conversationExternalId: seeded.conversationExternalId,
|
|
217
318
|
externalMessageId: nextEditEventId(),
|
|
218
319
|
sourceMessageId: seeded.channelTs,
|
|
219
|
-
canonicalAssistantId: "self",
|
|
220
320
|
assistantId: "self",
|
|
221
321
|
content: "first edit",
|
|
222
322
|
});
|
|
@@ -239,7 +339,6 @@ describe("Slack edit propagation", () => {
|
|
|
239
339
|
conversationExternalId: seeded.conversationExternalId,
|
|
240
340
|
externalMessageId: nextEditEventId(),
|
|
241
341
|
sourceMessageId: seeded.channelTs,
|
|
242
|
-
canonicalAssistantId: "self",
|
|
243
342
|
assistantId: "self",
|
|
244
343
|
content: "second edit",
|
|
245
344
|
});
|
|
@@ -272,7 +371,6 @@ describe("Slack edit propagation", () => {
|
|
|
272
371
|
conversationExternalId: seeded.conversationExternalId,
|
|
273
372
|
externalMessageId: nextEditEventId(),
|
|
274
373
|
sourceMessageId: seeded.channelTs,
|
|
275
|
-
canonicalAssistantId: "self",
|
|
276
374
|
assistantId: "self",
|
|
277
375
|
// Same text as stored -- simulates a Slack unfurl `message_changed`
|
|
278
376
|
// where only attachments changed.
|
|
@@ -313,7 +411,6 @@ describe("Slack edit propagation", () => {
|
|
|
313
411
|
conversationExternalId: seeded.conversationExternalId,
|
|
314
412
|
externalMessageId: nextEditEventId(),
|
|
315
413
|
sourceMessageId: seeded.channelTs,
|
|
316
|
-
canonicalAssistantId: "self",
|
|
317
414
|
assistantId: "self",
|
|
318
415
|
content: "edited searchable text",
|
|
319
416
|
});
|
|
@@ -341,7 +438,6 @@ describe("Slack edit propagation", () => {
|
|
|
341
438
|
// sourceMessageId points at a ts that was never stored.
|
|
342
439
|
externalMessageId: nextEditEventId(),
|
|
343
440
|
sourceMessageId: "9999.0000",
|
|
344
|
-
canonicalAssistantId: "self",
|
|
345
441
|
assistantId: "self",
|
|
346
442
|
content: "new text",
|
|
347
443
|
});
|