@vellumai/assistant 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +54 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +111 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
- package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
- package/src/__tests__/app-dir-path-guard.test.ts +78 -0
- package/src/__tests__/app-executors.test.ts +1 -291
- package/src/__tests__/app-git-history.test.ts +4 -4
- package/src/__tests__/app-routes-csp.test.ts +1 -0
- package/src/__tests__/app-store-dir-names.test.ts +426 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -9
- package/src/__tests__/attachments-store.test.ts +169 -21
- package/src/__tests__/attachments.test.ts +115 -1
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/canonical-guardian-store.test.ts +38 -0
- package/src/__tests__/channel-reply-delivery.test.ts +55 -0
- package/src/__tests__/checker.test.ts +54 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/config-schema-cmd.test.ts +68 -21
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +149 -5
- package/src/__tests__/conversation-agent-loop.test.ts +290 -2
- package/src/__tests__/conversation-attachments.test.ts +17 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
- package/src/__tests__/conversation-disk-view.test.ts +810 -0
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +551 -0
- package/src/__tests__/conversation-fork-route.test.ts +386 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
- package/src/__tests__/conversation-media-retry.test.ts +8 -2
- package/src/__tests__/conversation-queue.test.ts +36 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
- package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
- package/src/__tests__/conversation-skill-tools.test.ts +4 -9
- package/src/__tests__/conversation-slash-commands.test.ts +149 -0
- package/src/__tests__/conversation-store.test.ts +24 -21
- package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/conversation-title-service.test.ts +137 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-vault-unit.test.ts +5 -10
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
- package/src/__tests__/diagnostics-export.test.ts +70 -1
- package/src/__tests__/filesystem-tools.test.ts +4 -2
- package/src/__tests__/first-greeting.test.ts +80 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
- package/src/__tests__/history-repair.test.ts +103 -10
- package/src/__tests__/http-conversation-lineage.test.ts +251 -0
- package/src/__tests__/image-source-path-reinject.test.ts +136 -0
- package/src/__tests__/llm-context-normalization.test.ts +1116 -0
- package/src/__tests__/llm-context-route-provider.test.ts +217 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
- package/src/__tests__/media-generate-image.test.ts +47 -94
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-recall-quality.test.ts +5 -5
- package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
- package/src/__tests__/migration-export-http.test.ts +3 -1
- package/src/__tests__/migration-import-commit-http.test.ts +18 -4
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
- package/src/__tests__/mime-builder.test.ts +3 -2
- package/src/__tests__/non-member-access-request.test.ts +12 -1
- package/src/__tests__/notification-decision-identity.test.ts +52 -0
- package/src/__tests__/oauth-apps-routes.test.ts +103 -0
- package/src/__tests__/oauth-store.test.ts +115 -0
- package/src/__tests__/provider-error-scenarios.test.ts +1 -3
- package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
- package/src/__tests__/recording-handler.test.ts +17 -0
- package/src/__tests__/registry.test.ts +3 -8
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
- package/src/__tests__/schema-transforms.test.ts +165 -5
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/skill-feature-flags-integration.test.ts +18 -17
- package/src/__tests__/skill-feature-flags.test.ts +13 -13
- package/src/__tests__/skill-load-feature-flag.test.ts +4 -4
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -2
- package/src/__tests__/starter-task-flow.test.ts +1 -0
- package/src/__tests__/suggestion-routes.test.ts +443 -0
- package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +8 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
- package/src/__tests__/top-level-renderer.test.ts +22 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
- package/src/__tests__/web-fetch.test.ts +6 -2
- package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
- package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
- package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
- package/src/agent/attachments.ts +27 -1
- package/src/agent/loop.ts +29 -1
- package/src/avatar/traits-png-sync.ts +80 -25
- package/src/bundler/app-bundler.ts +4 -4
- package/src/calls/call-domain.ts +1 -0
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/auth.ts +92 -0
- package/src/cli/commands/avatar.ts +7 -6
- package/src/cli/commands/config.ts +2 -0
- package/src/cli/commands/oauth/providers.ts +29 -0
- package/src/cli/program.ts +12 -0
- package/src/cli.ts +15 -48
- package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
- package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
- package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
- package/src/config/bundled-tool-registry.ts +2 -14
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/loader.ts +64 -0
- package/src/config/raw-config-utils.ts +30 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/services.ts +8 -6
- package/src/contacts/contact-store.ts +13 -6
- package/src/contacts/contacts-write.ts +0 -1
- package/src/context/window-manager.ts +13 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +46 -42
- package/src/daemon/conversation-agent-loop.ts +56 -19
- package/src/daemon/conversation-attachments.ts +18 -36
- package/src/daemon/conversation-error.ts +2 -1
- package/src/daemon/conversation-history.ts +18 -4
- package/src/daemon/conversation-lifecycle.ts +39 -15
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +21 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +143 -20
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +51 -29
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +148 -0
- package/src/daemon/handlers/config-model.ts +71 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/history-repair.ts +28 -8
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +106 -64
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +19 -0
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/shared.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/upgrades.ts +23 -0
- package/src/daemon/server.ts +83 -12
- package/src/daemon/shutdown-handlers.ts +8 -5
- package/src/daemon/startup-error.ts +9 -0
- package/src/daemon/tool-side-effects.ts +11 -28
- package/src/events/tool-permission-telemetry-listener.ts +1 -3
- package/src/instrument.ts +0 -4
- package/src/media/app-icon-generator.ts +2 -2
- package/src/memory/app-git-service.ts +28 -16
- package/src/memory/app-store.ts +230 -41
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +442 -3
- package/src/memory/conversation-directories.ts +125 -0
- package/src/memory/conversation-disk-view.ts +390 -0
- package/src/memory/conversation-key-store.ts +17 -5
- package/src/memory/conversation-queries.ts +5 -1
- package/src/memory/conversation-title-service.ts +21 -49
- package/src/memory/db-init.ts +28 -0
- package/src/memory/embedding-backend.ts +42 -53
- package/src/memory/embedding-gemini.test.ts +4 -4
- package/src/memory/embedding-local.ts +1 -3
- package/src/memory/embedding-ollama.ts +1 -3
- package/src/memory/embedding-openai.ts +1 -3
- package/src/memory/indexer.ts +9 -7
- package/src/memory/items-extractor.ts +42 -13
- package/src/memory/job-handlers/conversation-starters.ts +6 -1
- package/src/memory/job-handlers/embedding.test.ts +1 -4
- package/src/memory/llm-request-log-store.ts +100 -1
- package/src/memory/migrations/102-alter-table-columns.ts +5 -0
- package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
- package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
- package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
- package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
- package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
- package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
- package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
- package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
- package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +6 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/oauth.ts +6 -0
- package/src/messaging/providers/gmail/mime-builder.ts +3 -1
- package/src/notifications/copy-composer.ts +26 -0
- package/src/notifications/decision-engine.ts +14 -1
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +36 -0
- package/src/oauth/byo-connection.test.ts +1 -45
- package/src/oauth/byo-connection.ts +2 -8
- package/src/oauth/connect-orchestrator.ts +15 -11
- package/src/oauth/connection-resolver.test.ts +191 -0
- package/src/oauth/connection-resolver.ts +66 -38
- package/src/oauth/connection.ts +0 -1
- package/src/oauth/oauth-store.ts +97 -47
- package/src/oauth/platform-connection.test.ts +0 -1
- package/src/oauth/platform-connection.ts +11 -3
- package/src/oauth/seed-providers.ts +78 -3
- package/src/oauth/token-persistence.ts +16 -10
- package/src/permissions/checker.ts +62 -19
- package/src/prompts/system-prompt.ts +2 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -0
- package/src/providers/anthropic/client.ts +8 -1
- package/src/providers/failover.ts +4 -1
- package/src/providers/gemini/client.ts +50 -0
- package/src/providers/model-catalog.ts +92 -0
- package/src/providers/model-intents.ts +29 -20
- package/src/providers/openai/client.ts +49 -0
- package/src/providers/types.ts +2 -0
- package/src/runtime/access-request-helper.ts +16 -7
- package/src/runtime/auth/credential-service.ts +3 -1
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/btw-sidechain.ts +101 -0
- package/src/runtime/channel-reply-delivery.ts +17 -1
- package/src/runtime/http-router.ts +3 -1
- package/src/runtime/http-server.ts +196 -141
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +5 -1
- package/src/runtime/routes/access-request-decision.ts +41 -0
- package/src/runtime/routes/app-management-routes.ts +6 -3
- package/src/runtime/routes/app-routes.ts +7 -3
- package/src/runtime/routes/approval-routes.ts +1 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
- package/src/runtime/routes/attachment-routes.ts +45 -15
- package/src/runtime/routes/btw-routes.ts +21 -61
- package/src/runtime/routes/conversation-management-routes.ts +68 -0
- package/src/runtime/routes/conversation-query-routes.ts +180 -10
- package/src/runtime/routes/conversation-routes.ts +222 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1199 -0
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/memory-item-routes.test.ts +34 -0
- package/src/runtime/routes/memory-item-routes.ts +4 -0
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/oauth-apps.ts +291 -0
- package/src/runtime/routes/secret-routes.ts +28 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +9 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +3 -1
- package/src/tools/AGENTS.md +6 -10
- package/src/tools/apps/executors.ts +17 -232
- package/src/tools/claude-code/claude-code.ts +2 -3
- package/src/tools/credentials/vault.ts +7 -12
- package/src/tools/host-filesystem/read.ts +13 -10
- package/src/tools/network/__tests__/web-search.test.ts +4 -2
- package/src/tools/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +4 -21
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/tool-manifest.ts +0 -6
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/util/device-id.ts +28 -5
- package/src/util/platform.ts +6 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
- package/src/workspace/migrations/003-seed-device-id.ts +3 -4
- package/src/workspace/migrations/006-services-config.ts +5 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/top-level-renderer.ts +12 -0
- package/src/__tests__/asset-materialize-tool.test.ts +0 -523
- package/src/__tests__/asset-search-tool.test.ts +0 -536
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
- package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
- package/src/__tests__/media-visibility-policy.test.ts +0 -190
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
- package/src/daemon/media-visibility-policy.ts +0 -59
- package/src/tools/assets/materialize.ts +0 -248
- package/src/tools/assets/search.ts +0 -400
|
@@ -53,10 +53,18 @@ mock.module("../memory/embedding-local.js", () => ({
|
|
|
53
53
|
}));
|
|
54
54
|
|
|
55
55
|
// Mock Qdrant client so semantic search returns empty results by default.
|
|
56
|
+
// Tests can push entries into `mockQdrantResults` to simulate Qdrant returning
|
|
57
|
+
// specific hits (e.g. item candidates).
|
|
58
|
+
const mockQdrantResults: Array<{
|
|
59
|
+
id: string;
|
|
60
|
+
score: number;
|
|
61
|
+
payload: Record<string, unknown>;
|
|
62
|
+
}> = [];
|
|
63
|
+
|
|
56
64
|
mock.module("../memory/qdrant-client.js", () => ({
|
|
57
65
|
getQdrantClient: () => ({
|
|
58
|
-
searchWithFilter: async () => [],
|
|
59
|
-
hybridSearch: async () => [],
|
|
66
|
+
searchWithFilter: async () => [...mockQdrantResults],
|
|
67
|
+
hybridSearch: async () => [...mockQdrantResults],
|
|
60
68
|
upsertPoints: async () => {},
|
|
61
69
|
deletePoints: async () => {},
|
|
62
70
|
}),
|
|
@@ -145,6 +153,7 @@ function insertMessage(
|
|
|
145
153
|
role: string,
|
|
146
154
|
text: string,
|
|
147
155
|
createdAt: number,
|
|
156
|
+
opts?: { metadata?: string | null },
|
|
148
157
|
) {
|
|
149
158
|
db.insert(messages)
|
|
150
159
|
.values({
|
|
@@ -153,6 +162,7 @@ function insertMessage(
|
|
|
153
162
|
role,
|
|
154
163
|
content: JSON.stringify([{ type: "text", text }]),
|
|
155
164
|
createdAt,
|
|
165
|
+
metadata: opts?.metadata ?? null,
|
|
156
166
|
})
|
|
157
167
|
.run();
|
|
158
168
|
}
|
|
@@ -297,6 +307,7 @@ describe("Memory Retriever Pipeline", () => {
|
|
|
297
307
|
db.run("DELETE FROM conversations");
|
|
298
308
|
_resetQdrantBreaker();
|
|
299
309
|
clearEmbeddingBackendCache();
|
|
310
|
+
mockQdrantResults.length = 0;
|
|
300
311
|
});
|
|
301
312
|
|
|
302
313
|
afterAll(() => {
|
|
@@ -858,4 +869,592 @@ describe("Memory Retriever Pipeline", () => {
|
|
|
858
869
|
// Current-conversation segments are filtered out of merged results
|
|
859
870
|
expect(result.mergedCount).toBe(0);
|
|
860
871
|
});
|
|
872
|
+
|
|
873
|
+
// -----------------------------------------------------------------------
|
|
874
|
+
// Step 5b: in-context item filtering
|
|
875
|
+
// -----------------------------------------------------------------------
|
|
876
|
+
|
|
877
|
+
describe("step 5b: in-context item filtering", () => {
|
|
878
|
+
test("filters items whose all sources are in-context messages", async () => {
|
|
879
|
+
const db = getDb();
|
|
880
|
+
const now = Date.now();
|
|
881
|
+
const convId = "conv-item-filter";
|
|
882
|
+
|
|
883
|
+
insertConversation(db, convId, now - 60_000);
|
|
884
|
+
insertMessage(db, "msg-if-1", convId, "user", "hello", now - 50_000);
|
|
885
|
+
insertMessage(db, "msg-if-2", convId, "assistant", "world", now - 40_000);
|
|
886
|
+
insertMessage(
|
|
887
|
+
db,
|
|
888
|
+
"msg-if-3",
|
|
889
|
+
convId,
|
|
890
|
+
"user",
|
|
891
|
+
"memory items test",
|
|
892
|
+
now - 30_000,
|
|
893
|
+
);
|
|
894
|
+
|
|
895
|
+
// Insert a memory item sourced from msg-if-2 (in-context)
|
|
896
|
+
insertItem(db, {
|
|
897
|
+
id: "item-in-ctx",
|
|
898
|
+
kind: "fact",
|
|
899
|
+
subject: "test",
|
|
900
|
+
statement: "A fact from in-context message",
|
|
901
|
+
firstSeenAt: now - 35_000,
|
|
902
|
+
});
|
|
903
|
+
insertItemSource(db, "item-in-ctx", "msg-if-2", now - 35_000);
|
|
904
|
+
|
|
905
|
+
// Simulate Qdrant returning this item as a semantic hit
|
|
906
|
+
mockQdrantResults.push({
|
|
907
|
+
id: "qdrant-pt-1",
|
|
908
|
+
score: 0.9,
|
|
909
|
+
payload: {
|
|
910
|
+
target_type: "item",
|
|
911
|
+
target_id: "item-in-ctx",
|
|
912
|
+
text: "test: A fact from in-context message",
|
|
913
|
+
created_at: now - 35_000,
|
|
914
|
+
},
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
const result = await buildMemoryRecall(
|
|
918
|
+
"memory items test",
|
|
919
|
+
convId,
|
|
920
|
+
TEST_CONFIG,
|
|
921
|
+
);
|
|
922
|
+
|
|
923
|
+
expect(result.enabled).toBe(true);
|
|
924
|
+
// The item should be filtered because its only source is in-context
|
|
925
|
+
expect(result.mergedCount).toBe(0);
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
test("keeps items from compacted messages", async () => {
|
|
929
|
+
const db = getDb();
|
|
930
|
+
const now = Date.now();
|
|
931
|
+
const convId = "conv-item-compacted";
|
|
932
|
+
|
|
933
|
+
// 2 messages compacted away
|
|
934
|
+
insertConversation(db, convId, now - 120_000, {
|
|
935
|
+
contextCompactedMessageCount: 2,
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
// Compacted messages (first 2 by createdAt order)
|
|
939
|
+
insertMessage(
|
|
940
|
+
db,
|
|
941
|
+
"msg-ic-1",
|
|
942
|
+
convId,
|
|
943
|
+
"user",
|
|
944
|
+
"compacted old topic",
|
|
945
|
+
now - 100_000,
|
|
946
|
+
);
|
|
947
|
+
insertMessage(
|
|
948
|
+
db,
|
|
949
|
+
"msg-ic-2",
|
|
950
|
+
convId,
|
|
951
|
+
"assistant",
|
|
952
|
+
"compacted old reply",
|
|
953
|
+
now - 90_000,
|
|
954
|
+
);
|
|
955
|
+
|
|
956
|
+
// Still in context
|
|
957
|
+
insertMessage(
|
|
958
|
+
db,
|
|
959
|
+
"msg-ic-3",
|
|
960
|
+
convId,
|
|
961
|
+
"user",
|
|
962
|
+
"item compaction test",
|
|
963
|
+
now - 50_000,
|
|
964
|
+
);
|
|
965
|
+
|
|
966
|
+
// Item sourced from a compacted message — should be kept
|
|
967
|
+
insertItem(db, {
|
|
968
|
+
id: "item-compacted",
|
|
969
|
+
kind: "fact",
|
|
970
|
+
subject: "compaction",
|
|
971
|
+
statement: "A fact from a compacted message",
|
|
972
|
+
firstSeenAt: now - 95_000,
|
|
973
|
+
});
|
|
974
|
+
insertItemSource(db, "item-compacted", "msg-ic-1", now - 95_000);
|
|
975
|
+
|
|
976
|
+
// Simulate Qdrant returning this item as a semantic hit
|
|
977
|
+
mockQdrantResults.push({
|
|
978
|
+
id: "qdrant-pt-2",
|
|
979
|
+
score: 0.9,
|
|
980
|
+
payload: {
|
|
981
|
+
target_type: "item",
|
|
982
|
+
target_id: "item-compacted",
|
|
983
|
+
text: "compaction: A fact from a compacted message",
|
|
984
|
+
created_at: now - 95_000,
|
|
985
|
+
},
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
const result = await buildMemoryRecall(
|
|
989
|
+
"item compaction test",
|
|
990
|
+
convId,
|
|
991
|
+
TEST_CONFIG,
|
|
992
|
+
);
|
|
993
|
+
|
|
994
|
+
expect(result.enabled).toBe(true);
|
|
995
|
+
// The item sourced from a compacted message should survive filtering
|
|
996
|
+
// because its source is no longer in the context window
|
|
997
|
+
expect(result.mergedCount).toBeGreaterThan(0);
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
test("keeps items with cross-conversation sources", async () => {
|
|
1001
|
+
const db = getDb();
|
|
1002
|
+
const now = Date.now();
|
|
1003
|
+
const convId = "conv-item-cross";
|
|
1004
|
+
const otherConvId = "conv-item-other";
|
|
1005
|
+
|
|
1006
|
+
insertConversation(db, convId, now - 60_000);
|
|
1007
|
+
insertConversation(db, otherConvId, now - 120_000);
|
|
1008
|
+
|
|
1009
|
+
// Messages in current conversation
|
|
1010
|
+
insertMessage(
|
|
1011
|
+
db,
|
|
1012
|
+
"msg-cr-1",
|
|
1013
|
+
convId,
|
|
1014
|
+
"user",
|
|
1015
|
+
"cross conv test",
|
|
1016
|
+
now - 50_000,
|
|
1017
|
+
);
|
|
1018
|
+
insertMessage(
|
|
1019
|
+
db,
|
|
1020
|
+
"msg-cr-2",
|
|
1021
|
+
convId,
|
|
1022
|
+
"assistant",
|
|
1023
|
+
"cross conv reply",
|
|
1024
|
+
now - 40_000,
|
|
1025
|
+
);
|
|
1026
|
+
|
|
1027
|
+
// Message in the other conversation
|
|
1028
|
+
insertMessage(
|
|
1029
|
+
db,
|
|
1030
|
+
"msg-cr-other",
|
|
1031
|
+
otherConvId,
|
|
1032
|
+
"user",
|
|
1033
|
+
"other conv msg",
|
|
1034
|
+
now - 100_000,
|
|
1035
|
+
);
|
|
1036
|
+
|
|
1037
|
+
// Item sourced from BOTH the current conversation AND a different one
|
|
1038
|
+
insertItem(db, {
|
|
1039
|
+
id: "item-cross",
|
|
1040
|
+
kind: "fact",
|
|
1041
|
+
subject: "cross",
|
|
1042
|
+
statement: "A cross-conversation fact",
|
|
1043
|
+
firstSeenAt: now - 95_000,
|
|
1044
|
+
});
|
|
1045
|
+
insertItemSource(db, "item-cross", "msg-cr-1", now - 45_000);
|
|
1046
|
+
insertItemSource(db, "item-cross", "msg-cr-other", now - 95_000);
|
|
1047
|
+
|
|
1048
|
+
// Simulate Qdrant returning this item as a semantic hit
|
|
1049
|
+
mockQdrantResults.push({
|
|
1050
|
+
id: "qdrant-pt-3",
|
|
1051
|
+
score: 0.9,
|
|
1052
|
+
payload: {
|
|
1053
|
+
target_type: "item",
|
|
1054
|
+
target_id: "item-cross",
|
|
1055
|
+
text: "cross: A cross-conversation fact",
|
|
1056
|
+
created_at: now - 95_000,
|
|
1057
|
+
},
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
const result = await buildMemoryRecall(
|
|
1061
|
+
"cross conv test",
|
|
1062
|
+
convId,
|
|
1063
|
+
TEST_CONFIG,
|
|
1064
|
+
);
|
|
1065
|
+
|
|
1066
|
+
expect(result.enabled).toBe(true);
|
|
1067
|
+
// The item has a source outside the in-context set (from other conv),
|
|
1068
|
+
// so it should NOT be filtered — it carries cross-conversation info
|
|
1069
|
+
expect(result.mergedCount).toBeGreaterThan(0);
|
|
1070
|
+
});
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
// -----------------------------------------------------------------------
|
|
1074
|
+
// Step 5b: fork-aware filtering
|
|
1075
|
+
// -----------------------------------------------------------------------
|
|
1076
|
+
|
|
1077
|
+
describe("step 5b: fork-aware filtering", () => {
|
|
1078
|
+
test("filters segments sourced from fork-parent messages", async () => {
|
|
1079
|
+
const db = getDb();
|
|
1080
|
+
const now = Date.now();
|
|
1081
|
+
|
|
1082
|
+
// Parent conversation with messages
|
|
1083
|
+
const parentConv = "conv-parent";
|
|
1084
|
+
insertConversation(db, parentConv, now - 120_000);
|
|
1085
|
+
insertMessage(
|
|
1086
|
+
db,
|
|
1087
|
+
"parent-msg-1",
|
|
1088
|
+
parentConv,
|
|
1089
|
+
"user",
|
|
1090
|
+
"discuss fork patterns",
|
|
1091
|
+
now - 110_000,
|
|
1092
|
+
);
|
|
1093
|
+
insertMessage(
|
|
1094
|
+
db,
|
|
1095
|
+
"parent-msg-2",
|
|
1096
|
+
parentConv,
|
|
1097
|
+
"assistant",
|
|
1098
|
+
"fork patterns are useful",
|
|
1099
|
+
now - 100_000,
|
|
1100
|
+
);
|
|
1101
|
+
|
|
1102
|
+
// Fork conversation — messages are copies with forkSourceMessageId metadata
|
|
1103
|
+
const forkConv = "conv-fork";
|
|
1104
|
+
insertConversation(db, forkConv, now - 50_000);
|
|
1105
|
+
insertMessage(
|
|
1106
|
+
db,
|
|
1107
|
+
"fork-msg-1",
|
|
1108
|
+
forkConv,
|
|
1109
|
+
"user",
|
|
1110
|
+
"discuss fork patterns",
|
|
1111
|
+
now - 50_000,
|
|
1112
|
+
{
|
|
1113
|
+
metadata: JSON.stringify({
|
|
1114
|
+
forkSourceMessageId: "parent-msg-1",
|
|
1115
|
+
}),
|
|
1116
|
+
},
|
|
1117
|
+
);
|
|
1118
|
+
insertMessage(
|
|
1119
|
+
db,
|
|
1120
|
+
"fork-msg-2",
|
|
1121
|
+
forkConv,
|
|
1122
|
+
"assistant",
|
|
1123
|
+
"fork patterns are useful",
|
|
1124
|
+
now - 49_000,
|
|
1125
|
+
{
|
|
1126
|
+
metadata: JSON.stringify({
|
|
1127
|
+
forkSourceMessageId: "parent-msg-2",
|
|
1128
|
+
}),
|
|
1129
|
+
},
|
|
1130
|
+
);
|
|
1131
|
+
|
|
1132
|
+
// Segment sourced from a parent message — should be filtered when
|
|
1133
|
+
// recalling for the fork conversation since the fork copy is in context.
|
|
1134
|
+
insertSegment(
|
|
1135
|
+
db,
|
|
1136
|
+
"seg-parent-1",
|
|
1137
|
+
"parent-msg-1",
|
|
1138
|
+
parentConv,
|
|
1139
|
+
"user",
|
|
1140
|
+
"discuss fork patterns detail",
|
|
1141
|
+
now - 110_000,
|
|
1142
|
+
);
|
|
1143
|
+
|
|
1144
|
+
// Simulate Qdrant returning the parent-conversation segment as a
|
|
1145
|
+
// semantic hit so it enters the candidate map (recency search is scoped
|
|
1146
|
+
// to forkConv and would never find it).
|
|
1147
|
+
mockQdrantResults.push({
|
|
1148
|
+
id: "qdrant-fork-1",
|
|
1149
|
+
score: 0.9,
|
|
1150
|
+
payload: {
|
|
1151
|
+
target_type: "segment",
|
|
1152
|
+
target_id: "seg-parent-1",
|
|
1153
|
+
text: "discuss fork patterns detail",
|
|
1154
|
+
created_at: now - 110_000,
|
|
1155
|
+
message_id: "parent-msg-1",
|
|
1156
|
+
conversation_id: parentConv,
|
|
1157
|
+
},
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
const result = await buildMemoryRecall(
|
|
1161
|
+
"fork patterns",
|
|
1162
|
+
forkConv,
|
|
1163
|
+
TEST_CONFIG,
|
|
1164
|
+
);
|
|
1165
|
+
|
|
1166
|
+
expect(result.enabled).toBe(true);
|
|
1167
|
+
// The segment entered the candidate map via semantic search…
|
|
1168
|
+
expect(result.semanticHits).toBeGreaterThanOrEqual(1);
|
|
1169
|
+
// …but the fork-source filtering removed it because parent-msg-1 is
|
|
1170
|
+
// in the in-context set (via forkSourceMessageId on fork-msg-1).
|
|
1171
|
+
expect(result.mergedCount).toBe(0);
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
test("keeps segments from compacted fork messages' parents", async () => {
|
|
1175
|
+
const db = getDb();
|
|
1176
|
+
const now = Date.now();
|
|
1177
|
+
|
|
1178
|
+
// Parent conversation
|
|
1179
|
+
const parentConv = "conv-parent-compact";
|
|
1180
|
+
insertConversation(db, parentConv, now - 200_000);
|
|
1181
|
+
insertMessage(
|
|
1182
|
+
db,
|
|
1183
|
+
"parent-compact-msg-1",
|
|
1184
|
+
parentConv,
|
|
1185
|
+
"user",
|
|
1186
|
+
"compacted parent topic",
|
|
1187
|
+
now - 190_000,
|
|
1188
|
+
);
|
|
1189
|
+
insertMessage(
|
|
1190
|
+
db,
|
|
1191
|
+
"parent-compact-msg-2",
|
|
1192
|
+
parentConv,
|
|
1193
|
+
"assistant",
|
|
1194
|
+
"compacted parent response",
|
|
1195
|
+
now - 180_000,
|
|
1196
|
+
);
|
|
1197
|
+
|
|
1198
|
+
// Fork conversation with compaction — first 2 messages are compacted
|
|
1199
|
+
const forkConv = "conv-fork-compact";
|
|
1200
|
+
insertConversation(db, forkConv, now - 100_000, {
|
|
1201
|
+
contextCompactedMessageCount: 2,
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
// These two messages are compacted (offset=2 means first 2 are compacted)
|
|
1205
|
+
insertMessage(
|
|
1206
|
+
db,
|
|
1207
|
+
"fork-compact-msg-1",
|
|
1208
|
+
forkConv,
|
|
1209
|
+
"user",
|
|
1210
|
+
"compacted parent topic",
|
|
1211
|
+
now - 100_000,
|
|
1212
|
+
{
|
|
1213
|
+
metadata: JSON.stringify({
|
|
1214
|
+
forkSourceMessageId: "parent-compact-msg-1",
|
|
1215
|
+
}),
|
|
1216
|
+
},
|
|
1217
|
+
);
|
|
1218
|
+
insertMessage(
|
|
1219
|
+
db,
|
|
1220
|
+
"fork-compact-msg-2",
|
|
1221
|
+
forkConv,
|
|
1222
|
+
"assistant",
|
|
1223
|
+
"compacted parent response",
|
|
1224
|
+
now - 99_000,
|
|
1225
|
+
{
|
|
1226
|
+
metadata: JSON.stringify({
|
|
1227
|
+
forkSourceMessageId: "parent-compact-msg-2",
|
|
1228
|
+
}),
|
|
1229
|
+
},
|
|
1230
|
+
);
|
|
1231
|
+
|
|
1232
|
+
// A newer message still in context
|
|
1233
|
+
insertMessage(
|
|
1234
|
+
db,
|
|
1235
|
+
"fork-compact-msg-3",
|
|
1236
|
+
forkConv,
|
|
1237
|
+
"user",
|
|
1238
|
+
"recent fork topic",
|
|
1239
|
+
now - 50_000,
|
|
1240
|
+
);
|
|
1241
|
+
|
|
1242
|
+
// Segment in the fork conversation sourced from a compacted fork
|
|
1243
|
+
// message. Since the fork message is compacted, its forkSourceMessageId
|
|
1244
|
+
// is NOT added to the in-context set, so the segment should survive.
|
|
1245
|
+
insertSegment(
|
|
1246
|
+
db,
|
|
1247
|
+
"seg-compact-fork",
|
|
1248
|
+
"fork-compact-msg-1",
|
|
1249
|
+
forkConv,
|
|
1250
|
+
"user",
|
|
1251
|
+
"compacted parent topic detail",
|
|
1252
|
+
now - 100_000,
|
|
1253
|
+
);
|
|
1254
|
+
|
|
1255
|
+
// Also insert a segment from an in-context message for contrast —
|
|
1256
|
+
// this one SHOULD be filtered.
|
|
1257
|
+
insertSegment(
|
|
1258
|
+
db,
|
|
1259
|
+
"seg-in-context-fork",
|
|
1260
|
+
"fork-compact-msg-3",
|
|
1261
|
+
forkConv,
|
|
1262
|
+
"user",
|
|
1263
|
+
"recent fork topic detail",
|
|
1264
|
+
now - 50_000,
|
|
1265
|
+
);
|
|
1266
|
+
|
|
1267
|
+
const result = await buildMemoryRecall(
|
|
1268
|
+
"compacted parent topic",
|
|
1269
|
+
forkConv,
|
|
1270
|
+
TEST_CONFIG,
|
|
1271
|
+
);
|
|
1272
|
+
|
|
1273
|
+
expect(result.enabled).toBe(true);
|
|
1274
|
+
// The segment from the compacted fork message survives filtering
|
|
1275
|
+
// (its source message is no longer in context). The in-context segment
|
|
1276
|
+
// is filtered out. Recency search returns both, but only the compacted
|
|
1277
|
+
// one survives step 5b.
|
|
1278
|
+
expect(result.mergedCount).toBeGreaterThan(0);
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
test("handles multi-level forks", async () => {
|
|
1282
|
+
const db = getDb();
|
|
1283
|
+
const now = Date.now();
|
|
1284
|
+
|
|
1285
|
+
// Grandparent conversation
|
|
1286
|
+
const grandparentConv = "conv-grandparent";
|
|
1287
|
+
insertConversation(db, grandparentConv, now - 300_000);
|
|
1288
|
+
insertMessage(
|
|
1289
|
+
db,
|
|
1290
|
+
"gp-msg-1",
|
|
1291
|
+
grandparentConv,
|
|
1292
|
+
"user",
|
|
1293
|
+
"grandparent topic",
|
|
1294
|
+
now - 290_000,
|
|
1295
|
+
);
|
|
1296
|
+
|
|
1297
|
+
// Parent conversation (fork of grandparent)
|
|
1298
|
+
// The fork metadata preserves the original grandparent message ID
|
|
1299
|
+
const parentConv = "conv-parent-multi";
|
|
1300
|
+
insertConversation(db, parentConv, now - 200_000);
|
|
1301
|
+
insertMessage(
|
|
1302
|
+
db,
|
|
1303
|
+
"parent-multi-msg-1",
|
|
1304
|
+
parentConv,
|
|
1305
|
+
"user",
|
|
1306
|
+
"grandparent topic",
|
|
1307
|
+
now - 200_000,
|
|
1308
|
+
{
|
|
1309
|
+
metadata: JSON.stringify({
|
|
1310
|
+
forkSourceMessageId: "gp-msg-1",
|
|
1311
|
+
}),
|
|
1312
|
+
},
|
|
1313
|
+
);
|
|
1314
|
+
|
|
1315
|
+
// Child conversation (fork of parent)
|
|
1316
|
+
// forkSourceMessageId still points to the original grandparent message
|
|
1317
|
+
const childConv = "conv-child-multi";
|
|
1318
|
+
insertConversation(db, childConv, now - 100_000);
|
|
1319
|
+
insertMessage(
|
|
1320
|
+
db,
|
|
1321
|
+
"child-multi-msg-1",
|
|
1322
|
+
childConv,
|
|
1323
|
+
"user",
|
|
1324
|
+
"grandparent topic",
|
|
1325
|
+
now - 100_000,
|
|
1326
|
+
{
|
|
1327
|
+
metadata: JSON.stringify({
|
|
1328
|
+
forkSourceMessageId: "gp-msg-1",
|
|
1329
|
+
}),
|
|
1330
|
+
},
|
|
1331
|
+
);
|
|
1332
|
+
|
|
1333
|
+
// Segment sourced from the grandparent message
|
|
1334
|
+
insertSegment(
|
|
1335
|
+
db,
|
|
1336
|
+
"seg-gp",
|
|
1337
|
+
"gp-msg-1",
|
|
1338
|
+
grandparentConv,
|
|
1339
|
+
"user",
|
|
1340
|
+
"grandparent topic detail",
|
|
1341
|
+
now - 290_000,
|
|
1342
|
+
);
|
|
1343
|
+
|
|
1344
|
+
// Simulate Qdrant returning the grandparent segment as a semantic hit
|
|
1345
|
+
// so it enters the candidate map (recency search is scoped to childConv
|
|
1346
|
+
// and would never find it).
|
|
1347
|
+
mockQdrantResults.push({
|
|
1348
|
+
id: "qdrant-gp-1",
|
|
1349
|
+
score: 0.9,
|
|
1350
|
+
payload: {
|
|
1351
|
+
target_type: "segment",
|
|
1352
|
+
target_id: "seg-gp",
|
|
1353
|
+
text: "grandparent topic detail",
|
|
1354
|
+
created_at: now - 290_000,
|
|
1355
|
+
message_id: "gp-msg-1",
|
|
1356
|
+
conversation_id: grandparentConv,
|
|
1357
|
+
},
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
const result = await buildMemoryRecall(
|
|
1361
|
+
"grandparent topic",
|
|
1362
|
+
childConv,
|
|
1363
|
+
TEST_CONFIG,
|
|
1364
|
+
);
|
|
1365
|
+
|
|
1366
|
+
expect(result.enabled).toBe(true);
|
|
1367
|
+
// The segment entered the candidate map via semantic search…
|
|
1368
|
+
expect(result.semanticHits).toBeGreaterThanOrEqual(1);
|
|
1369
|
+
// …but the fork-source filtering removed it because gp-msg-1 is in the
|
|
1370
|
+
// in-context set (via forkSourceMessageId on child-multi-msg-1).
|
|
1371
|
+
expect(result.mergedCount).toBe(0);
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
test("handles missing or invalid metadata gracefully", async () => {
|
|
1375
|
+
const db = getDb();
|
|
1376
|
+
const now = Date.now();
|
|
1377
|
+
|
|
1378
|
+
const forkConv = "conv-fork-bad-meta";
|
|
1379
|
+
insertConversation(db, forkConv, now - 50_000);
|
|
1380
|
+
|
|
1381
|
+
// Message with null metadata (no forkSourceMessageId)
|
|
1382
|
+
insertMessage(
|
|
1383
|
+
db,
|
|
1384
|
+
"fork-null-meta",
|
|
1385
|
+
forkConv,
|
|
1386
|
+
"user",
|
|
1387
|
+
"null metadata topic",
|
|
1388
|
+
now - 50_000,
|
|
1389
|
+
);
|
|
1390
|
+
|
|
1391
|
+
// Message with malformed JSON metadata
|
|
1392
|
+
insertMessage(
|
|
1393
|
+
db,
|
|
1394
|
+
"fork-bad-json",
|
|
1395
|
+
forkConv,
|
|
1396
|
+
"assistant",
|
|
1397
|
+
"bad json topic",
|
|
1398
|
+
now - 49_000,
|
|
1399
|
+
{ metadata: "not valid json {{{" },
|
|
1400
|
+
);
|
|
1401
|
+
|
|
1402
|
+
// Message with metadata that is a JSON array (not an object)
|
|
1403
|
+
insertMessage(
|
|
1404
|
+
db,
|
|
1405
|
+
"fork-array-meta",
|
|
1406
|
+
forkConv,
|
|
1407
|
+
"user",
|
|
1408
|
+
"array metadata topic",
|
|
1409
|
+
now - 48_000,
|
|
1410
|
+
{ metadata: JSON.stringify([1, 2, 3]) },
|
|
1411
|
+
);
|
|
1412
|
+
|
|
1413
|
+
// Message with metadata object but no forkSourceMessageId field
|
|
1414
|
+
insertMessage(
|
|
1415
|
+
db,
|
|
1416
|
+
"fork-no-field",
|
|
1417
|
+
forkConv,
|
|
1418
|
+
"assistant",
|
|
1419
|
+
"no field topic",
|
|
1420
|
+
now - 47_000,
|
|
1421
|
+
{ metadata: JSON.stringify({ someOtherField: "value" }) },
|
|
1422
|
+
);
|
|
1423
|
+
|
|
1424
|
+
// Message with forkSourceMessageId that is not a string
|
|
1425
|
+
insertMessage(
|
|
1426
|
+
db,
|
|
1427
|
+
"fork-non-string",
|
|
1428
|
+
forkConv,
|
|
1429
|
+
"user",
|
|
1430
|
+
"non-string fork id",
|
|
1431
|
+
now - 46_000,
|
|
1432
|
+
{ metadata: JSON.stringify({ forkSourceMessageId: 12345 }) },
|
|
1433
|
+
);
|
|
1434
|
+
|
|
1435
|
+
// Insert a segment from this conversation — should be filtered normally
|
|
1436
|
+
// (it's an in-context segment from the active conversation)
|
|
1437
|
+
insertSegment(
|
|
1438
|
+
db,
|
|
1439
|
+
"seg-bad-meta",
|
|
1440
|
+
"fork-null-meta",
|
|
1441
|
+
forkConv,
|
|
1442
|
+
"user",
|
|
1443
|
+
"null metadata topic detail",
|
|
1444
|
+
now - 50_000,
|
|
1445
|
+
);
|
|
1446
|
+
|
|
1447
|
+
// This should not crash despite various malformed metadata
|
|
1448
|
+
const result = await buildMemoryRecall(
|
|
1449
|
+
"metadata topic",
|
|
1450
|
+
forkConv,
|
|
1451
|
+
TEST_CONFIG,
|
|
1452
|
+
);
|
|
1453
|
+
|
|
1454
|
+
expect(result.enabled).toBe(true);
|
|
1455
|
+
// No crash — the pipeline completes successfully
|
|
1456
|
+
// The in-context segment is still filtered normally
|
|
1457
|
+
expect(result.mergedCount).toBe(0);
|
|
1458
|
+
});
|
|
1459
|
+
});
|
|
861
1460
|
});
|