@vellumai/assistant 0.4.49 → 0.4.50
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 +24 -33
- package/README.md +3 -3
- package/docs/architecture/memory.md +180 -119
- package/package.json +2 -2
- package/src/__tests__/agent-loop.test.ts +3 -1
- package/src/__tests__/anthropic-provider.test.ts +114 -23
- package/src/__tests__/approval-cascade.test.ts +1 -15
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
- package/src/__tests__/canonical-guardian-store.test.ts +95 -0
- package/src/__tests__/checker.test.ts +13 -0
- package/src/__tests__/config-schema.test.ts +1 -68
- package/src/__tests__/context-memory-e2e.test.ts +11 -100
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -0
- package/src/__tests__/credential-vault.test.ts +13 -1
- package/src/__tests__/cu-unified-flow.test.ts +532 -0
- package/src/__tests__/date-context.test.ts +93 -77
- package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
- package/src/__tests__/history-repair.test.ts +245 -0
- package/src/__tests__/host-cu-proxy.test.ts +165 -3
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/invite-redemption-service.test.ts +65 -1
- package/src/__tests__/keychain-broker-client.test.ts +4 -4
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
- package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
- package/src/__tests__/memory-recall-quality.test.ts +244 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
- package/src/__tests__/memory-regressions.test.ts +477 -2841
- package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
- package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
- package/src/__tests__/mime-builder.test.ts +28 -0
- package/src/__tests__/native-web-search.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +572 -5
- package/src/__tests__/oauth-store.test.ts +120 -6
- package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
- package/src/__tests__/registry.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +46 -1
- package/src/__tests__/schedule-tools.test.ts +32 -0
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -0
- package/src/__tests__/secure-keys.test.ts +7 -2
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/session-abort-tool-results.test.ts +1 -14
- package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
- package/src/__tests__/session-agent-loop.test.ts +19 -15
- package/src/__tests__/session-confirmation-signals.test.ts +1 -15
- package/src/__tests__/session-error.test.ts +124 -2
- package/src/__tests__/session-history-web-search.test.ts +918 -0
- package/src/__tests__/session-pre-run-repair.test.ts +1 -14
- package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
- package/src/__tests__/session-queue.test.ts +37 -27
- package/src/__tests__/session-runtime-assembly.test.ts +54 -0
- package/src/__tests__/session-slash-known.test.ts +1 -15
- package/src/__tests__/session-slash-queue.test.ts +1 -15
- package/src/__tests__/session-slash-unknown.test.ts +1 -15
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
- package/src/__tests__/session-workspace-injection.test.ts +3 -37
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
- package/src/__tests__/skills-install-extract.test.ts +93 -0
- package/src/__tests__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/trust-store.test.ts +15 -0
- package/src/__tests__/voice-invite-redemption.test.ts +32 -1
- package/src/agent/ax-tree-compaction.test.ts +51 -0
- package/src/agent/loop.ts +39 -12
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-request-resolvers.ts +14 -2
- package/src/bundler/compiler-tools.ts +66 -2
- package/src/calls/call-domain.ts +132 -0
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +43 -5
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +1 -1
- package/src/calls/types.ts +3 -1
- package/src/cli/commands/doctor.ts +4 -3
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +31 -2
- package/src/cli/commands/oauth/connections.ts +431 -97
- package/src/cli/commands/oauth/providers.ts +15 -1
- package/src/cli/commands/sessions.ts +5 -2
- package/src/cli/commands/skills.ts +173 -1
- package/src/cli/http-client.ts +0 -20
- package/src/cli/main-screen.tsx +2 -2
- package/src/cli/program.ts +5 -6
- package/src/cli.ts +4 -10
- package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
- package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/schema.ts +1 -12
- package/src/config/schemas/memory-lifecycle.ts +0 -9
- package/src/config/schemas/memory-processing.ts +0 -180
- package/src/config/schemas/memory-retrieval.ts +32 -104
- package/src/config/schemas/memory.ts +0 -10
- package/src/config/types.ts +0 -4
- package/src/context/window-manager.ts +4 -1
- package/src/daemon/config-watcher.ts +61 -3
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/sessions.ts +18 -13
- package/src/daemon/handlers/skills.ts +20 -1
- package/src/daemon/history-repair.ts +72 -8
- package/src/daemon/host-cu-proxy.ts +55 -26
- package/src/daemon/lifecycle.ts +31 -3
- package/src/daemon/mcp-reload-service.ts +2 -2
- package/src/daemon/message-types/computer-use.ts +1 -12
- package/src/daemon/message-types/memory.ts +4 -16
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/sessions.ts +4 -0
- package/src/daemon/server.ts +12 -1
- package/src/daemon/session-agent-loop-handlers.ts +38 -0
- package/src/daemon/session-agent-loop.ts +334 -48
- package/src/daemon/session-error.ts +89 -6
- package/src/daemon/session-history.ts +17 -7
- package/src/daemon/session-media-retry.ts +6 -2
- package/src/daemon/session-memory.ts +69 -149
- package/src/daemon/session-process.ts +10 -1
- package/src/daemon/session-runtime-assembly.ts +49 -19
- package/src/daemon/session-surfaces.ts +4 -1
- package/src/daemon/session-tool-setup.ts +7 -1
- package/src/daemon/session.ts +12 -2
- package/src/instrument.ts +61 -1
- package/src/memory/admin.ts +2 -191
- package/src/memory/canonical-guardian-store.ts +38 -2
- package/src/memory/conversation-crud.ts +0 -33
- package/src/memory/conversation-queries.ts +22 -3
- package/src/memory/db-init.ts +28 -0
- package/src/memory/embedding-backend.ts +84 -8
- package/src/memory/embedding-types.ts +9 -1
- package/src/memory/indexer.ts +7 -46
- package/src/memory/items-extractor.ts +274 -76
- package/src/memory/job-handlers/backfill.ts +2 -127
- package/src/memory/job-handlers/cleanup.ts +2 -16
- package/src/memory/job-handlers/extraction.ts +2 -138
- package/src/memory/job-handlers/index-maintenance.ts +1 -6
- package/src/memory/job-handlers/summarization.ts +3 -148
- package/src/memory/job-utils.ts +21 -59
- package/src/memory/jobs-store.ts +1 -159
- package/src/memory/jobs-worker.ts +9 -52
- package/src/memory/migrations/104-core-indexes.ts +3 -3
- package/src/memory/migrations/149-oauth-tables.ts +2 -0
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
- package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
- package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
- package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
- package/src/memory/migrations/154-drop-fts.ts +20 -0
- package/src/memory/migrations/155-drop-conflicts.ts +7 -0
- package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/qdrant-client.ts +148 -51
- package/src/memory/raw-query.ts +1 -1
- package/src/memory/retriever.test.ts +294 -273
- package/src/memory/retriever.ts +421 -645
- package/src/memory/schema/calls.ts +2 -0
- package/src/memory/schema/memory-core.ts +3 -48
- package/src/memory/schema/oauth.ts +2 -0
- package/src/memory/search/formatting.ts +263 -176
- package/src/memory/search/lexical.ts +1 -254
- package/src/memory/search/ranking.ts +0 -455
- package/src/memory/search/semantic.ts +100 -14
- package/src/memory/search/staleness.ts +47 -0
- package/src/memory/search/tier-classifier.ts +21 -0
- package/src/memory/search/types.ts +15 -77
- package/src/memory/task-memory-cleanup.ts +4 -6
- package/src/messaging/providers/gmail/mime-builder.ts +17 -7
- package/src/oauth/byo-connection.test.ts +8 -1
- package/src/oauth/oauth-store.ts +113 -27
- package/src/oauth/seed-providers.ts +6 -0
- package/src/oauth/token-persistence.ts +11 -3
- package/src/permissions/defaults.ts +1 -0
- package/src/permissions/trust-store.ts +23 -1
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -2
- package/src/providers/anthropic/client.ts +56 -126
- package/src/providers/types.ts +7 -1
- package/src/runtime/AGENTS.md +9 -0
- package/src/runtime/auth/route-policy.ts +6 -3
- package/src/runtime/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/invite-redemption-service.ts +19 -1
- package/src/runtime/invite-service.ts +25 -0
- package/src/runtime/pending-interactions.ts +2 -2
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/conversation-routes.ts +9 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- package/src/runtime/routes/memory-item-routes.test.ts +754 -0
- package/src/runtime/routes/memory-item-routes.ts +503 -0
- package/src/runtime/routes/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +2 -2
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/workspace-routes.ts +2 -1
- package/src/security/keychain-broker-client.ts +17 -4
- package/src/security/secure-keys.ts +25 -3
- package/src/security/token-manager.ts +36 -36
- package/src/skills/catalog-install.ts +74 -18
- package/src/skills/skillssh-registry.ts +503 -0
- package/src/tools/assets/search.ts +5 -1
- package/src/tools/computer-use/definitions.ts +0 -10
- package/src/tools/computer-use/registry.ts +1 -1
- package/src/tools/credentials/vault.ts +1 -3
- package/src/tools/memory/definitions.ts +4 -13
- package/src/tools/memory/handlers.test.ts +83 -103
- package/src/tools/memory/handlers.ts +50 -85
- package/src/tools/schedule/create.ts +8 -1
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +25 -2
- package/src/__tests__/clarification-resolver.test.ts +0 -193
- package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
- package/src/__tests__/conflict-policy.test.ts +0 -269
- package/src/__tests__/conflict-store.test.ts +0 -372
- package/src/__tests__/contradiction-checker.test.ts +0 -361
- package/src/__tests__/entity-extractor.test.ts +0 -211
- package/src/__tests__/entity-search.test.ts +0 -1117
- package/src/__tests__/profile-compiler.test.ts +0 -392
- package/src/__tests__/session-conflict-gate.test.ts +0 -1228
- package/src/__tests__/session-profile-injection.test.ts +0 -557
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
- package/src/daemon/session-conflict-gate.ts +0 -167
- package/src/daemon/session-dynamic-profile.ts +0 -77
- package/src/memory/clarification-resolver.ts +0 -417
- package/src/memory/conflict-intent.ts +0 -205
- package/src/memory/conflict-policy.ts +0 -127
- package/src/memory/conflict-store.ts +0 -410
- package/src/memory/contradiction-checker.ts +0 -508
- package/src/memory/entity-extractor.ts +0 -535
- package/src/memory/format-recall.ts +0 -47
- package/src/memory/fts-reconciler.ts +0 -165
- package/src/memory/job-handlers/conflict.ts +0 -200
- package/src/memory/profile-compiler.ts +0 -195
- package/src/memory/recall-cache.ts +0 -117
- package/src/memory/search/entity.ts +0 -535
- package/src/memory/search/query-expansion.test.ts +0 -70
- package/src/memory/search/query-expansion.ts +0 -118
- package/src/runtime/routes/mcp-routes.ts +0 -20
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory lifecycle E2E regression test.
|
|
3
|
+
*
|
|
4
|
+
* Verifies the new memory pipeline end-to-end:
|
|
5
|
+
* - 6-kind enum items (identity, preference, project, decision, constraint, event)
|
|
6
|
+
* - Supersession chains (supersedes/supersededBy fields)
|
|
7
|
+
* - Hybrid search retrieval
|
|
8
|
+
* - Two-layer XML injection format (<memory_context> with sections)
|
|
9
|
+
* - Stripping removes <memory_context> tags
|
|
10
|
+
* - No conflict gate references
|
|
11
|
+
*/
|
|
1
12
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
13
|
import { tmpdir } from "node:os";
|
|
3
14
|
import { join } from "node:path";
|
|
@@ -11,8 +22,6 @@ import {
|
|
|
11
22
|
test,
|
|
12
23
|
} from "bun:test";
|
|
13
24
|
|
|
14
|
-
import { eq } from "drizzle-orm";
|
|
15
|
-
|
|
16
25
|
import { DEFAULT_CONFIG } from "../config/defaults.js";
|
|
17
26
|
|
|
18
27
|
const testDir = mkdtempSync(join(tmpdir(), "memory-lifecycle-e2e-"));
|
|
@@ -38,56 +47,13 @@ mock.module("../util/logger.js", () => ({
|
|
|
38
47
|
mock.module("../memory/qdrant-client.js", () => ({
|
|
39
48
|
getQdrantClient: () => ({
|
|
40
49
|
searchWithFilter: async () => [],
|
|
50
|
+
hybridSearch: async () => [],
|
|
41
51
|
upsertPoints: async () => {},
|
|
42
52
|
deletePoints: async () => {},
|
|
43
53
|
}),
|
|
44
54
|
initQdrantClient: () => {},
|
|
45
55
|
}));
|
|
46
56
|
|
|
47
|
-
// Mock clarification resolver to prevent real Anthropic API calls when
|
|
48
|
-
// ANTHROPIC_API_KEY is set. Without this, resolveConflictClarification can
|
|
49
|
-
// resolve conflicts via LLM before the test asserts on pending state.
|
|
50
|
-
mock.module("../memory/clarification-resolver.js", () => ({
|
|
51
|
-
resolveConflictClarification: async (input: { userMessage: string }) => {
|
|
52
|
-
const msg = input.userMessage.toLowerCase();
|
|
53
|
-
// "Use the new renderer going forward" → keep candidate
|
|
54
|
-
if (
|
|
55
|
-
msg.includes("new") ||
|
|
56
|
-
msg.includes("replace") ||
|
|
57
|
-
msg.includes("instead")
|
|
58
|
-
) {
|
|
59
|
-
return {
|
|
60
|
-
resolution: "keep_candidate" as const,
|
|
61
|
-
strategy: "heuristic" as const,
|
|
62
|
-
resolvedStatement: null,
|
|
63
|
-
explanation:
|
|
64
|
-
"User response explicitly points to candidate/new statement.",
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
// "Keep the old runtime one" → keep existing
|
|
68
|
-
if (
|
|
69
|
-
msg.includes("old") ||
|
|
70
|
-
msg.includes("existing") ||
|
|
71
|
-
msg.includes("still")
|
|
72
|
-
) {
|
|
73
|
-
return {
|
|
74
|
-
resolution: "keep_existing" as const,
|
|
75
|
-
strategy: "heuristic" as const,
|
|
76
|
-
resolvedStatement: null,
|
|
77
|
-
explanation:
|
|
78
|
-
"User response explicitly points to existing/old statement.",
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
// Default: still_unclear (e.g. "Need react roadmap update today")
|
|
82
|
-
return {
|
|
83
|
-
resolution: "still_unclear" as const,
|
|
84
|
-
strategy: "heuristic" as const,
|
|
85
|
-
resolvedStatement: null,
|
|
86
|
-
explanation: "No clear directional cue found in user message.",
|
|
87
|
-
};
|
|
88
|
-
},
|
|
89
|
-
}));
|
|
90
|
-
|
|
91
57
|
const TEST_CONFIG = {
|
|
92
58
|
...DEFAULT_CONFIG,
|
|
93
59
|
memory: {
|
|
@@ -103,43 +69,7 @@ const TEST_CONFIG = {
|
|
|
103
69
|
},
|
|
104
70
|
retrieval: {
|
|
105
71
|
...DEFAULT_CONFIG.memory.retrieval,
|
|
106
|
-
lexicalTopK: 40,
|
|
107
|
-
semanticTopK: 0,
|
|
108
72
|
maxInjectTokens: 900,
|
|
109
|
-
dynamicBudget: {
|
|
110
|
-
...DEFAULT_CONFIG.memory.retrieval.dynamicBudget,
|
|
111
|
-
enabled: true,
|
|
112
|
-
minInjectTokens: 180,
|
|
113
|
-
maxInjectTokens: 360,
|
|
114
|
-
targetHeadroomTokens: 700,
|
|
115
|
-
},
|
|
116
|
-
reranking: {
|
|
117
|
-
...DEFAULT_CONFIG.memory.retrieval.reranking,
|
|
118
|
-
enabled: false,
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
entity: {
|
|
122
|
-
...DEFAULT_CONFIG.memory.entity,
|
|
123
|
-
relationRetrieval: {
|
|
124
|
-
...DEFAULT_CONFIG.memory.entity.relationRetrieval,
|
|
125
|
-
enabled: true,
|
|
126
|
-
maxSeedEntities: 4,
|
|
127
|
-
maxNeighborEntities: 6,
|
|
128
|
-
maxEdges: 8,
|
|
129
|
-
neighborScoreMultiplier: 0.65,
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
conflicts: {
|
|
133
|
-
...DEFAULT_CONFIG.memory.conflicts,
|
|
134
|
-
enabled: true,
|
|
135
|
-
gateMode: "soft" as const,
|
|
136
|
-
relevanceThreshold: 0.2,
|
|
137
|
-
resolverLlmTimeoutMs: 250,
|
|
138
|
-
},
|
|
139
|
-
profile: {
|
|
140
|
-
...DEFAULT_CONFIG.memory.profile,
|
|
141
|
-
enabled: true,
|
|
142
|
-
maxInjectTokens: 300,
|
|
143
73
|
},
|
|
144
74
|
},
|
|
145
75
|
};
|
|
@@ -152,41 +82,22 @@ mock.module("../config/loader.js", () => ({
|
|
|
152
82
|
invalidateConfigCache: () => {},
|
|
153
83
|
}));
|
|
154
84
|
|
|
155
|
-
import { ConflictGate } from "../daemon/session-conflict-gate.js";
|
|
156
|
-
import {
|
|
157
|
-
injectDynamicProfileIntoUserMessage,
|
|
158
|
-
stripDynamicProfileMessages,
|
|
159
|
-
} from "../daemon/session-dynamic-profile.js";
|
|
160
|
-
import {
|
|
161
|
-
createOrUpdatePendingConflict,
|
|
162
|
-
getConflictById,
|
|
163
|
-
} from "../memory/conflict-store.js";
|
|
164
85
|
import { getDb, initializeDb, resetDb } from "../memory/db.js";
|
|
165
|
-
import { enqueueResolvePendingConflictsForMessageJob } from "../memory/jobs-store.js";
|
|
166
86
|
import {
|
|
167
87
|
resetCleanupScheduleThrottle,
|
|
168
88
|
resetStaleSweepThrottle,
|
|
169
|
-
runMemoryJobsOnce,
|
|
170
89
|
} from "../memory/jobs-worker.js";
|
|
171
|
-
import {
|
|
90
|
+
import {
|
|
91
|
+
buildMemoryRecall,
|
|
92
|
+
injectMemoryRecallAsSeparateMessage,
|
|
93
|
+
stripMemoryRecallMessages,
|
|
94
|
+
} from "../memory/retriever.js";
|
|
172
95
|
import {
|
|
173
96
|
conversations,
|
|
174
|
-
memoryEntities,
|
|
175
|
-
memoryEntityRelations,
|
|
176
|
-
memoryItemConflicts,
|
|
177
|
-
memoryItemEntities,
|
|
178
97
|
memoryItems,
|
|
179
98
|
memoryItemSources,
|
|
180
99
|
messages,
|
|
181
100
|
} from "../memory/schema.js";
|
|
182
|
-
import type { Message } from "../providers/types.js";
|
|
183
|
-
|
|
184
|
-
function messageText(message: Message): string {
|
|
185
|
-
return message.content
|
|
186
|
-
.filter((block) => block.type === "text")
|
|
187
|
-
.map((block) => (block as { type: "text"; text: string }).text)
|
|
188
|
-
.join("\n");
|
|
189
|
-
}
|
|
190
101
|
|
|
191
102
|
describe("Memory lifecycle E2E regression", () => {
|
|
192
103
|
beforeAll(() => {
|
|
@@ -195,15 +106,9 @@ describe("Memory lifecycle E2E regression", () => {
|
|
|
195
106
|
|
|
196
107
|
beforeEach(() => {
|
|
197
108
|
const db = getDb();
|
|
198
|
-
db.run("DELETE FROM memory_item_conflicts");
|
|
199
|
-
db.run("DELETE FROM memory_item_entities");
|
|
200
|
-
db.run("DELETE FROM memory_entity_relations");
|
|
201
|
-
db.run("DELETE FROM memory_entities");
|
|
202
109
|
db.run("DELETE FROM memory_item_sources");
|
|
203
110
|
db.run("DELETE FROM memory_embeddings");
|
|
204
|
-
db.run("DELETE FROM memory_summaries");
|
|
205
111
|
db.run("DELETE FROM memory_items");
|
|
206
|
-
db.run("DELETE FROM memory_segment_fts");
|
|
207
112
|
db.run("DELETE FROM memory_segments");
|
|
208
113
|
db.run("DELETE FROM messages");
|
|
209
114
|
db.run("DELETE FROM conversations");
|
|
@@ -222,7 +127,7 @@ describe("Memory lifecycle E2E regression", () => {
|
|
|
222
127
|
}
|
|
223
128
|
});
|
|
224
129
|
|
|
225
|
-
test("
|
|
130
|
+
test("extraction produces items with 6-kind enum and supersession chains form correctly", async () => {
|
|
226
131
|
const db = getDb();
|
|
227
132
|
const now = 1_701_100_000_000;
|
|
228
133
|
const conversationId = "conv-memory-lifecycle";
|
|
@@ -256,315 +161,267 @@ describe("Memory lifecycle E2E regression", () => {
|
|
|
256
161
|
]),
|
|
257
162
|
createdAt: now + 10,
|
|
258
163
|
},
|
|
259
|
-
{
|
|
260
|
-
id: "msg-lifecycle-background",
|
|
261
|
-
conversationId,
|
|
262
|
-
role: "user",
|
|
263
|
-
content: JSON.stringify([
|
|
264
|
-
{ type: "text", text: "Keep the old runtime one." },
|
|
265
|
-
]),
|
|
266
|
-
createdAt: now + 500,
|
|
267
|
-
},
|
|
268
164
|
])
|
|
269
165
|
.run();
|
|
270
166
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
167
|
+
// Seed items using the 6-kind enum
|
|
168
|
+
const kinds = [
|
|
169
|
+
"identity",
|
|
170
|
+
"preference",
|
|
171
|
+
"project",
|
|
172
|
+
"decision",
|
|
173
|
+
"constraint",
|
|
174
|
+
"event",
|
|
175
|
+
] as const;
|
|
176
|
+
for (let i = 0; i < kinds.length; i++) {
|
|
177
|
+
db.insert(memoryItems)
|
|
178
|
+
.values({
|
|
179
|
+
id: `item-kind-${kinds[i]}`,
|
|
180
|
+
kind: kinds[i],
|
|
181
|
+
subject: `${kinds[i]} test`,
|
|
182
|
+
statement: `This is a ${kinds[i]} item for testing.`,
|
|
278
183
|
status: "active",
|
|
279
|
-
confidence: 0.
|
|
280
|
-
importance: 0.
|
|
281
|
-
fingerprint:
|
|
282
|
-
verificationState: "assistant_inferred",
|
|
283
|
-
scopeId: "default",
|
|
284
|
-
firstSeenAt: now + 10,
|
|
285
|
-
lastSeenAt: now + 10,
|
|
286
|
-
validFrom: now + 10,
|
|
287
|
-
invalidAt: null,
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
id: "item-k8s-relation",
|
|
291
|
-
kind: "fact",
|
|
292
|
-
subject: "autoscaling",
|
|
293
|
-
statement: "Scale API pods at 70% CPU with Kubernetes HPA.",
|
|
294
|
-
status: "active",
|
|
295
|
-
confidence: 0.82,
|
|
296
|
-
importance: 0.7,
|
|
297
|
-
fingerprint: "fp-item-k8s-relation",
|
|
184
|
+
confidence: 0.9,
|
|
185
|
+
importance: 0.8,
|
|
186
|
+
fingerprint: `fp-item-kind-${kinds[i]}`,
|
|
298
187
|
verificationState: "assistant_inferred",
|
|
299
188
|
scopeId: "default",
|
|
300
|
-
firstSeenAt: now +
|
|
301
|
-
lastSeenAt: now +
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
db.insert(memoryItemSources)
|
|
309
|
-
.values([
|
|
310
|
-
{
|
|
311
|
-
memoryItemId: "item-atlas-direct",
|
|
189
|
+
firstSeenAt: now + i,
|
|
190
|
+
lastSeenAt: now + i,
|
|
191
|
+
})
|
|
192
|
+
.run();
|
|
193
|
+
|
|
194
|
+
db.insert(memoryItemSources)
|
|
195
|
+
.values({
|
|
196
|
+
memoryItemId: `item-kind-${kinds[i]}`,
|
|
312
197
|
messageId: "msg-lifecycle-seed",
|
|
313
|
-
evidence:
|
|
314
|
-
createdAt: now +
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
messageId: "msg-lifecycle-seed",
|
|
319
|
-
evidence: "Kubernetes autoscaling note",
|
|
320
|
-
createdAt: now + 12,
|
|
321
|
-
},
|
|
322
|
-
])
|
|
323
|
-
.run();
|
|
198
|
+
evidence: `${kinds[i]} evidence`,
|
|
199
|
+
createdAt: now + i,
|
|
200
|
+
})
|
|
201
|
+
.run();
|
|
202
|
+
}
|
|
324
203
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
description: null,
|
|
343
|
-
firstSeenAt: now,
|
|
344
|
-
lastSeenAt: now + 500,
|
|
345
|
-
mentionCount: 3,
|
|
346
|
-
},
|
|
347
|
-
])
|
|
204
|
+
// Create a supersession chain: old decision superseded by new decision
|
|
205
|
+
db.insert(memoryItems)
|
|
206
|
+
.values({
|
|
207
|
+
id: "item-old-decision",
|
|
208
|
+
kind: "decision",
|
|
209
|
+
subject: "deploy strategy",
|
|
210
|
+
statement: "Deploy manually every Friday.",
|
|
211
|
+
status: "superseded",
|
|
212
|
+
confidence: 0.7,
|
|
213
|
+
importance: 0.6,
|
|
214
|
+
fingerprint: "fp-old-decision",
|
|
215
|
+
verificationState: "assistant_inferred",
|
|
216
|
+
scopeId: "default",
|
|
217
|
+
firstSeenAt: now - 10_000,
|
|
218
|
+
lastSeenAt: now - 10_000,
|
|
219
|
+
supersededBy: "item-kind-decision",
|
|
220
|
+
})
|
|
348
221
|
.run();
|
|
349
222
|
|
|
350
|
-
|
|
223
|
+
// Update the new decision to reference the old one
|
|
224
|
+
db.run(
|
|
225
|
+
`UPDATE memory_items SET supersedes = 'item-old-decision' WHERE id = 'item-kind-decision'`,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// Verify supersession chain is stored correctly
|
|
229
|
+
const oldDecision = db
|
|
230
|
+
.select()
|
|
231
|
+
.from(memoryItems)
|
|
232
|
+
.all()
|
|
233
|
+
.find((i) => i.id === "item-old-decision");
|
|
234
|
+
const newDecision = db
|
|
235
|
+
.select()
|
|
236
|
+
.from(memoryItems)
|
|
237
|
+
.all()
|
|
238
|
+
.find((i) => i.id === "item-kind-decision");
|
|
239
|
+
|
|
240
|
+
expect(oldDecision).toBeDefined();
|
|
241
|
+
expect(oldDecision!.status).toBe("superseded");
|
|
242
|
+
expect(oldDecision!.supersededBy).toBe("item-kind-decision");
|
|
243
|
+
|
|
244
|
+
expect(newDecision).toBeDefined();
|
|
245
|
+
expect(newDecision!.status).toBe("active");
|
|
246
|
+
expect(newDecision!.supersedes).toBe("item-old-decision");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("recall completes and recency search retrieves relevant items", async () => {
|
|
250
|
+
const db = getDb();
|
|
251
|
+
const now = 1_701_100_000_000;
|
|
252
|
+
const conversationId = "conv-recall-lifecycle";
|
|
253
|
+
|
|
254
|
+
db.insert(conversations)
|
|
351
255
|
.values({
|
|
352
|
-
id:
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
256
|
+
id: conversationId,
|
|
257
|
+
title: null,
|
|
258
|
+
createdAt: now,
|
|
259
|
+
updatedAt: now,
|
|
260
|
+
totalInputTokens: 0,
|
|
261
|
+
totalOutputTokens: 0,
|
|
262
|
+
totalEstimatedCost: 0,
|
|
263
|
+
contextSummary: null,
|
|
264
|
+
contextCompactedMessageCount: 0,
|
|
265
|
+
contextCompactedAt: null,
|
|
359
266
|
})
|
|
360
267
|
.run();
|
|
361
268
|
|
|
362
|
-
db.insert(
|
|
363
|
-
.values(
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
269
|
+
db.insert(messages)
|
|
270
|
+
.values({
|
|
271
|
+
id: "msg-recall-seed",
|
|
272
|
+
conversationId,
|
|
273
|
+
role: "user",
|
|
274
|
+
content: JSON.stringify([
|
|
275
|
+
{
|
|
276
|
+
type: "text",
|
|
277
|
+
text: "Atlas deployment notes mention Kubernetes infrastructure.",
|
|
278
|
+
},
|
|
279
|
+
]),
|
|
280
|
+
createdAt: now + 10,
|
|
281
|
+
})
|
|
373
282
|
.run();
|
|
374
283
|
|
|
284
|
+
// Insert a segment that recency search can find
|
|
285
|
+
db.run(`
|
|
286
|
+
INSERT INTO memory_segments (
|
|
287
|
+
id, message_id, conversation_id, role, segment_index, text, token_estimate, scope_id, created_at, updated_at
|
|
288
|
+
) VALUES (
|
|
289
|
+
'seg-recall-seed', 'msg-recall-seed', '${conversationId}', 'user', 0,
|
|
290
|
+
'Atlas deployment notes mention Kubernetes infrastructure.', 10, 'default',
|
|
291
|
+
${now + 10}, ${now + 10}
|
|
292
|
+
)
|
|
293
|
+
`);
|
|
294
|
+
|
|
375
295
|
const recall = await buildMemoryRecall(
|
|
376
296
|
"atlas deployment guidance",
|
|
377
297
|
conversationId,
|
|
378
298
|
TEST_CONFIG,
|
|
379
299
|
);
|
|
380
|
-
expect(recall.injectedText).toContain("blue-green rollouts");
|
|
381
|
-
expect(recall.injectedText).toContain("70% CPU");
|
|
382
|
-
expect(recall.relationSeedEntityCount).toBeGreaterThan(0);
|
|
383
|
-
expect(recall.relationTraversedEdgeCount).toBeGreaterThan(0);
|
|
384
|
-
expect(recall.relationNeighborEntityCount).toBeGreaterThan(0);
|
|
385
|
-
expect(recall.relationExpandedItemCount).toBeGreaterThan(0);
|
|
386
300
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
verificationState: "user_reported",
|
|
415
|
-
scopeId: "default",
|
|
416
|
-
firstSeenAt: now + 31,
|
|
417
|
-
lastSeenAt: now + 31,
|
|
418
|
-
validFrom: now + 31,
|
|
419
|
-
invalidAt: null,
|
|
420
|
-
},
|
|
421
|
-
])
|
|
301
|
+
// Recency search finds segments but their finalScore (semantic*0.7 +
|
|
302
|
+
// recency*0.2 + confidence*0.1) is too low to pass tier classification
|
|
303
|
+
// (threshold > 0.6) because semantic=0 with Qdrant mocked empty.
|
|
304
|
+
// Verify recency search ran successfully.
|
|
305
|
+
expect(recall.recencyHits).toBeGreaterThan(0);
|
|
306
|
+
// Candidates exist but don't pass tier classification, so injectedText is empty
|
|
307
|
+
expect(recall.enabled).toBe(true);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("two-layer XML injection format uses <memory_context> tags", async () => {
|
|
311
|
+
const db = getDb();
|
|
312
|
+
const now = 1_701_100_000_000;
|
|
313
|
+
const conversationId = "conv-injection-format";
|
|
314
|
+
|
|
315
|
+
db.insert(conversations)
|
|
316
|
+
.values({
|
|
317
|
+
id: conversationId,
|
|
318
|
+
title: null,
|
|
319
|
+
createdAt: now,
|
|
320
|
+
updatedAt: now,
|
|
321
|
+
totalInputTokens: 0,
|
|
322
|
+
totalOutputTokens: 0,
|
|
323
|
+
totalEstimatedCost: 0,
|
|
324
|
+
contextSummary: null,
|
|
325
|
+
contextCompactedMessageCount: 0,
|
|
326
|
+
contextCompactedAt: null,
|
|
327
|
+
})
|
|
422
328
|
.run();
|
|
423
329
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
TEST_CONFIG.memory.conflicts,
|
|
439
|
-
);
|
|
440
|
-
expect(firstResult).toBeUndefined();
|
|
330
|
+
db.insert(messages)
|
|
331
|
+
.values({
|
|
332
|
+
id: "msg-injection-seed",
|
|
333
|
+
conversationId,
|
|
334
|
+
role: "user",
|
|
335
|
+
content: JSON.stringify([
|
|
336
|
+
{
|
|
337
|
+
type: "text",
|
|
338
|
+
text: "My preferred timezone is America/Los_Angeles.",
|
|
339
|
+
},
|
|
340
|
+
]),
|
|
341
|
+
createdAt: now + 10,
|
|
342
|
+
})
|
|
343
|
+
.run();
|
|
441
344
|
|
|
442
|
-
|
|
443
|
-
|
|
345
|
+
db.run(`
|
|
346
|
+
INSERT INTO memory_segments (
|
|
347
|
+
id, message_id, conversation_id, role, segment_index, text, token_estimate, scope_id, created_at, updated_at
|
|
348
|
+
) VALUES (
|
|
349
|
+
'seg-injection-seed', 'msg-injection-seed', '${conversationId}', 'user', 0,
|
|
350
|
+
'My preferred timezone is America/Los_Angeles.', 10, 'default',
|
|
351
|
+
${now + 10}, ${now + 10}
|
|
352
|
+
)
|
|
353
|
+
`);
|
|
444
354
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
TEST_CONFIG.memory.conflicts,
|
|
355
|
+
const recall = await buildMemoryRecall(
|
|
356
|
+
"timezone",
|
|
357
|
+
conversationId,
|
|
358
|
+
TEST_CONFIG,
|
|
450
359
|
);
|
|
451
|
-
expect(secondResult).toBeUndefined();
|
|
452
360
|
|
|
453
|
-
|
|
454
|
-
|
|
361
|
+
// The recency-only promotion path (Step 6 in retriever) ensures the
|
|
362
|
+
// seeded segment reaches tier 2 and is injected even without semantic
|
|
363
|
+
// search. Verify structure of the two-layer XML format.
|
|
364
|
+
expect(recall.recencyHits).toBeGreaterThan(0);
|
|
365
|
+
expect(recall.enabled).toBe(true);
|
|
366
|
+
expect(recall.injectedText.length).toBeGreaterThan(0);
|
|
367
|
+
expect(recall.injectedTokens).toBeGreaterThan(0);
|
|
368
|
+
expect(recall.injectedText).toContain("<memory_context>");
|
|
369
|
+
expect(recall.injectedText).toContain("</memory_context>");
|
|
370
|
+
});
|
|
455
371
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
372
|
+
test("stripping removes <memory_context> tags from injected recall", () => {
|
|
373
|
+
const memoryRecallText =
|
|
374
|
+
"<memory_context>\n\n<relevant_context>\nuser prefers concise answers\n</relevant_context>\n\n</memory_context>";
|
|
375
|
+
const originalMessages = [
|
|
376
|
+
{
|
|
377
|
+
role: "user" as const,
|
|
378
|
+
content: [{ type: "text", text: "Actual user request" }],
|
|
379
|
+
},
|
|
380
|
+
];
|
|
381
|
+
const injected = injectMemoryRecallAsSeparateMessage(
|
|
382
|
+
originalMessages,
|
|
383
|
+
memoryRecallText,
|
|
384
|
+
);
|
|
468
385
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
statement: "Node.js 20 remains the default runtime.",
|
|
476
|
-
status: "active",
|
|
477
|
-
confidence: 0.83,
|
|
478
|
-
importance: 0.7,
|
|
479
|
-
fingerprint: "fp-item-runtime-existing",
|
|
480
|
-
verificationState: "user_reported",
|
|
481
|
-
scopeId: "default",
|
|
482
|
-
firstSeenAt: now + 200,
|
|
483
|
-
lastSeenAt: now + 200,
|
|
484
|
-
validFrom: now + 200,
|
|
485
|
-
invalidAt: null,
|
|
486
|
-
},
|
|
487
|
-
{
|
|
488
|
-
id: "item-runtime-candidate",
|
|
489
|
-
kind: "preference",
|
|
490
|
-
subject: "runtime",
|
|
491
|
-
statement: "Bun becomes the default runtime.",
|
|
492
|
-
status: "pending_clarification",
|
|
493
|
-
confidence: 0.81,
|
|
494
|
-
importance: 0.7,
|
|
495
|
-
fingerprint: "fp-item-runtime-candidate",
|
|
496
|
-
verificationState: "user_reported",
|
|
497
|
-
scopeId: "default",
|
|
498
|
-
firstSeenAt: now + 201,
|
|
499
|
-
lastSeenAt: now + 201,
|
|
500
|
-
validFrom: now + 201,
|
|
501
|
-
invalidAt: null,
|
|
502
|
-
},
|
|
503
|
-
])
|
|
504
|
-
.run();
|
|
386
|
+
expect(injected).toHaveLength(3);
|
|
387
|
+
expect(injected[0].role).toBe("user");
|
|
388
|
+
expect(injected[0].content[0].text).toBe(memoryRecallText);
|
|
389
|
+
expect(injected[1].role as string).toBe("assistant");
|
|
390
|
+
expect(injected[2].role).toBe("user");
|
|
391
|
+
expect(injected[2].content[0].text).toBe("Actual user request");
|
|
505
392
|
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
relationship: "ambiguous_contradiction",
|
|
511
|
-
});
|
|
393
|
+
const cleaned = stripMemoryRecallMessages(injected, memoryRecallText);
|
|
394
|
+
expect(cleaned).toHaveLength(1);
|
|
395
|
+
expect(cleaned[0].content[0].text).toBe("Actual user request");
|
|
396
|
+
});
|
|
512
397
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
398
|
+
test("empty retrieval returns no injection", async () => {
|
|
399
|
+
const db = getDb();
|
|
400
|
+
const now = 1_701_100_000_000;
|
|
401
|
+
const conversationId = "conv-empty-lifecycle";
|
|
517
402
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
403
|
+
db.insert(conversations)
|
|
404
|
+
.values({
|
|
405
|
+
id: conversationId,
|
|
406
|
+
title: null,
|
|
407
|
+
createdAt: now,
|
|
408
|
+
updatedAt: now,
|
|
409
|
+
totalInputTokens: 0,
|
|
410
|
+
totalOutputTokens: 0,
|
|
411
|
+
totalEstimatedCost: 0,
|
|
412
|
+
contextSummary: null,
|
|
413
|
+
contextCompactedMessageCount: 0,
|
|
414
|
+
contextCompactedAt: null,
|
|
415
|
+
})
|
|
416
|
+
.run();
|
|
524
417
|
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
418
|
+
const recall = await buildMemoryRecall(
|
|
419
|
+
"completely unrelated xyzzy topic",
|
|
420
|
+
conversationId,
|
|
421
|
+
TEST_CONFIG,
|
|
529
422
|
);
|
|
530
423
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
.from(memoryItems)
|
|
534
|
-
.where(eq(memoryItems.id, "item-runtime-existing"))
|
|
535
|
-
.get();
|
|
536
|
-
const runtimeCandidate = db
|
|
537
|
-
.select()
|
|
538
|
-
.from(memoryItems)
|
|
539
|
-
.where(eq(memoryItems.id, "item-runtime-candidate"))
|
|
540
|
-
.get();
|
|
541
|
-
expect(runtimeExisting?.status).toBe("active");
|
|
542
|
-
expect(runtimeCandidate?.status).toBe("superseded");
|
|
543
|
-
|
|
544
|
-
const profileText =
|
|
545
|
-
"<dynamic-user-profile>\n- timezone: America/Los_Angeles\n- prefers concise answers\n</dynamic-user-profile>";
|
|
546
|
-
const baseUserMessage: Message = {
|
|
547
|
-
role: "user",
|
|
548
|
-
content: [{ type: "text", text: "Plan next sprint milestones." }],
|
|
549
|
-
};
|
|
550
|
-
|
|
551
|
-
const injectedProfileMessage = injectDynamicProfileIntoUserMessage(
|
|
552
|
-
baseUserMessage,
|
|
553
|
-
profileText,
|
|
554
|
-
);
|
|
555
|
-
const runtimeUserText = messageText(injectedProfileMessage);
|
|
556
|
-
expect(runtimeUserText).toContain("<dynamic-profile-context>");
|
|
557
|
-
expect(runtimeUserText).toContain("<dynamic-user-profile>");
|
|
558
|
-
expect(runtimeUserText).toContain("</dynamic-profile-context>");
|
|
559
|
-
|
|
560
|
-
const strippedMessages = stripDynamicProfileMessages(
|
|
561
|
-
[injectedProfileMessage],
|
|
562
|
-
profileText,
|
|
563
|
-
);
|
|
564
|
-
expect(strippedMessages).toHaveLength(1);
|
|
565
|
-
expect(messageText(strippedMessages[0] as Message).trim()).toBe(
|
|
566
|
-
"Plan next sprint milestones.",
|
|
567
|
-
);
|
|
568
|
-
expect(messageText(baseUserMessage)).toBe("Plan next sprint milestones.");
|
|
424
|
+
expect(recall.injectedText).toBe("");
|
|
425
|
+
expect(recall.injectedTokens).toBe(0);
|
|
569
426
|
});
|
|
570
427
|
});
|