@vellumai/assistant 0.7.3 → 0.8.0
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 +29 -28
- package/Dockerfile +1 -0
- package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
- package/bun.lock +3 -0
- package/knip.json +1 -0
- package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
- package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
- package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
- package/openapi.yaml +22 -4
- package/package.json +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +291 -0
- package/src/__tests__/approval-cascade.test.ts +8 -16
- package/src/__tests__/approval-routes-http.test.ts +6 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +12 -25
- package/src/__tests__/call-constants.test.ts +10 -1
- package/src/__tests__/call-controller.test.ts +127 -0
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
- package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
- package/src/__tests__/context-search-memory-source.test.ts +3 -26
- package/src/__tests__/context-search-pkb-source.test.ts +12 -6
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -6
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +3 -3
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
- package/src/__tests__/conversation-process-callsite.test.ts +1 -6
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -6
- package/src/__tests__/conversation-runtime-assembly.test.ts +15 -6
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -6
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +73 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +59 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -7
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -7
- package/src/__tests__/filing-service.test.ts +2 -19
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
- package/src/__tests__/injector-chain.test.ts +24 -16
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
- package/src/__tests__/notification-decision-fallback.test.ts +91 -0
- package/src/__tests__/notification-decision-strategy.test.ts +22 -0
- package/src/__tests__/oauth-cli.test.ts +121 -0
- package/src/__tests__/relay-server.test.ts +46 -2
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
- package/src/__tests__/secret-response-routing.test.ts +7 -5
- package/src/__tests__/server-history-render.test.ts +82 -0
- package/src/__tests__/skill-include-graph.test.ts +31 -0
- package/src/__tests__/skill-load-tool.test.ts +44 -16
- package/src/__tests__/skills.test.ts +39 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
- package/src/__tests__/tool-executor.test.ts +155 -0
- package/src/__tests__/voice-session-bridge.test.ts +3 -0
- package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
- package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +15 -27
- package/src/agent/loop.ts +11 -0
- package/src/approvals/guardian-decision-primitive.ts +0 -13
- package/src/approvals/guardian-request-resolvers.ts +4 -32
- package/src/calls/call-constants.ts +5 -8
- package/src/calls/call-controller.ts +130 -67
- package/src/calls/relay-server.ts +7 -1
- package/src/calls/voice-session-bridge.ts +1 -1
- package/src/cli/commands/memory-v2.ts +7 -7
- package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -254
- package/src/cli/commands/oauth/connect.ts +10 -52
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
- package/src/config/feature-flag-registry.json +1 -17
- package/src/config/loader.ts +72 -19
- package/src/config/schemas/memory-v2.ts +1 -1
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +32 -0
- package/src/daemon/conversation-agent-loop.ts +13 -10
- package/src/daemon/conversation-lifecycle.ts +22 -8
- package/src/daemon/conversation-surfaces.ts +16 -14
- package/src/daemon/conversation-tool-setup.ts +9 -5
- package/src/daemon/conversation.ts +1 -1
- package/src/daemon/handlers/shared.ts +26 -0
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-browser-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-transfer-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +88 -73
- package/src/daemon/memory-v2-startup.ts +55 -14
- package/src/daemon/message-types/messages.ts +19 -1
- package/src/documents/document-store.ts +35 -1
- package/src/filing/filing-service.ts +2 -3
- package/src/heartbeat/heartbeat-service.ts +1 -1
- package/src/ipc/assistant-server.ts +93 -36
- package/src/ipc/skill-server.ts +99 -42
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +10 -57
- package/src/memory/context-search/sources/memory-v2.ts +1 -17
- package/src/memory/context-search/sources/memory.ts +2 -2
- package/src/memory/context-search/sources/pkb.ts +2 -3
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +104 -61
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
- package/src/memory/graph/conversation-graph-memory.ts +32 -9
- package/src/memory/graph/graph-search.test.ts +6 -5
- package/src/memory/graph/graph-search.ts +3 -4
- package/src/memory/graph/retriever.test.ts +12 -7
- package/src/memory/graph/retriever.ts +4 -5
- package/src/memory/graph/tool-handlers.ts +3 -4
- package/src/memory/graph/tools.ts +4 -4
- package/src/memory/indexer.ts +1 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
- package/src/memory/jobs/embed-concept-page.ts +223 -87
- package/src/memory/jobs-worker.ts +8 -4
- package/src/memory/pkb/pkb-search.test.ts +6 -5
- package/src/memory/pkb/pkb-search.ts +4 -5
- package/src/memory/qdrant-client.ts +3 -0
- package/src/memory/search/semantic.ts +4 -5
- package/src/memory/v2/__tests__/activation.test.ts +35 -5
- package/src/memory/v2/__tests__/consolidation-job.test.ts +21 -32
- package/src/memory/v2/__tests__/injection.test.ts +140 -23
- package/src/memory/v2/__tests__/qdrant.test.ts +310 -9
- package/src/memory/v2/__tests__/sim.test.ts +118 -7
- package/src/memory/v2/__tests__/static-context.test.ts +1 -13
- package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
- package/src/memory/v2/consolidation-job.ts +7 -8
- package/src/memory/v2/injection.ts +32 -12
- package/src/memory/v2/page-store.ts +39 -0
- package/src/memory/v2/prompts/consolidation.ts +5 -0
- package/src/memory/v2/qdrant.ts +209 -48
- package/src/memory/v2/sim.ts +67 -26
- package/src/memory/v2/static-context.ts +4 -8
- package/src/memory/v2/sweep-job.ts +5 -6
- package/src/memory/v2/types.ts +7 -0
- package/src/notifications/copy-composer.ts +46 -12
- package/src/notifications/decision-engine.ts +46 -0
- package/src/permissions/gateway-threshold-reader.ts +116 -8
- package/src/permissions/prompter.ts +86 -96
- package/src/permissions/secret-prompter.ts +31 -31
- package/src/plugins/defaults/injectors.ts +1 -2
- package/src/proactive-artifact/job.test.ts +51 -4
- package/src/proactive-artifact/job.ts +16 -2
- package/src/proactive-artifact/message-copy.ts +18 -1
- package/src/prompts/templates/SOUL.md +13 -28
- package/src/runtime/auth/route-policy.ts +1 -0
- package/src/runtime/channel-approvals.ts +3 -2
- package/src/runtime/guardian-reply-router.ts +0 -10
- package/src/runtime/pending-interactions.ts +19 -15
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
- package/src/runtime/routes/approval-routes.ts +7 -3
- package/src/runtime/routes/consolidation-routes.ts +8 -9
- package/src/runtime/routes/conversation-query-routes.ts +44 -1
- package/src/runtime/routes/debug-bash-routes.ts +2 -0
- package/src/runtime/routes/filing-routes.ts +2 -3
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +0 -3
- package/src/runtime/routes/memory-item-routes.test.ts +3 -9
- package/src/runtime/routes/memory-item-routes.ts +5 -6
- package/src/runtime/routes/memory-v2-routes.ts +103 -17
- package/src/skills/include-graph.ts +35 -13
- package/src/tools/document/document-tool.ts +20 -0
- package/src/tools/executor.ts +18 -2
- package/src/tools/memory/register.test.ts +7 -5
- package/src/tools/permission-checker.ts +15 -0
- package/src/tools/skills/load.ts +24 -20
- package/src/tools/tool-name-aliases.ts +19 -0
- package/src/tools/types.ts +19 -1
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +4 -62
- package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
- package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
- package/src/workspace/migrations/registry.ts +6 -0
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for the v2 routing wired into `ConversationGraphMemory.prepareMemory`.
|
|
3
3
|
*
|
|
4
|
-
* The wiring layer at `conversation-graph-memory.ts` reads
|
|
5
|
-
* decide whether to swap v1's injection step
|
|
4
|
+
* The wiring layer at `conversation-graph-memory.ts` reads
|
|
5
|
+
* `config.memory.v2.enabled` to decide whether to swap v1's injection step
|
|
6
|
+
* for the v2 activation pipeline.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
13
|
-
* mocking `injection.js` itself would clobber that sibling test when both
|
|
14
|
-
* files run in the same `bun test` invocation, since `mock.module` is
|
|
15
|
-
* process-global. Avoiding the mock keeps the suite hermetic in either order.
|
|
8
|
+
* This file uses the *real* `injectMemoryV2Block` and stubs only the
|
|
9
|
+
* lower-level deps (Qdrant client, embedding backend) the way
|
|
10
|
+
* `memory/v2/__tests__/injection.test.ts` does — mocking `injection.js`
|
|
11
|
+
* itself would clobber that sibling test when both files run in the same
|
|
12
|
+
* `bun test` invocation, since `mock.module` is process-global. Avoiding
|
|
13
|
+
* the mock keeps the suite hermetic in either order.
|
|
16
14
|
*/
|
|
17
15
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
18
16
|
import { tmpdir } from "node:os";
|
|
@@ -20,7 +18,6 @@ import { join } from "node:path";
|
|
|
20
18
|
import { Database } from "bun:sqlite";
|
|
21
19
|
import {
|
|
22
20
|
afterAll,
|
|
23
|
-
afterEach,
|
|
24
21
|
beforeAll,
|
|
25
22
|
beforeEach,
|
|
26
23
|
describe,
|
|
@@ -98,9 +95,11 @@ class MockQdrantClient {
|
|
|
98
95
|
_name: string,
|
|
99
96
|
params: { using: string; limit: number; filter?: unknown },
|
|
100
97
|
) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
// The four-channel hybrid query fires body-dense, body-sparse,
|
|
99
|
+
// summary-dense, summary-sparse in order; both dense channels share
|
|
100
|
+
// the dense queue and both sparse channels share the sparse queue.
|
|
101
|
+
const channel = params.using.endsWith("sparse") ? "sparse" : "dense";
|
|
102
|
+
return qdrantState.queryResponses[channel].shift() ?? { points: [] };
|
|
104
103
|
}
|
|
105
104
|
}
|
|
106
105
|
|
|
@@ -170,14 +169,14 @@ import type { DrizzleDb } from "../../db-connection.js";
|
|
|
170
169
|
|
|
171
170
|
const { ConversationGraphMemory } =
|
|
172
171
|
await import("../conversation-graph-memory.js");
|
|
173
|
-
const { _setOverridesForTesting } =
|
|
174
|
-
await import("../../../config/assistant-feature-flags.js");
|
|
175
172
|
const { applyNestedDefaults } = await import("../../../config/loader.js");
|
|
176
173
|
const { getSqliteFrom } = await import("../../db-connection.js");
|
|
177
174
|
const { migrateActivationState } =
|
|
178
175
|
await import("../../migrations/232-activation-state.js");
|
|
179
176
|
const schema = await import("../../schema.js");
|
|
180
177
|
const { _resetMemoryV2QdrantForTests } = await import("../../v2/qdrant.js");
|
|
178
|
+
const { hydrate: hydrateActivationState } =
|
|
179
|
+
await import("../../v2/activation-store.js");
|
|
181
180
|
|
|
182
181
|
// The wiring layer calls `getDb()` to fetch the SQLite handle. We mock
|
|
183
182
|
// only that one export and spread the real module so unrelated callers
|
|
@@ -240,10 +239,21 @@ function makeMemory(): InstanceType<typeof ConversationGraphMemory> {
|
|
|
240
239
|
return m;
|
|
241
240
|
}
|
|
242
241
|
|
|
243
|
-
/** Stage one set of dense/sparse hits for each channel of
|
|
244
|
-
* pipeline (1 candidate query + 3 simBatch channels).
|
|
242
|
+
/** Stage one set of body and summary dense/sparse hits for each channel of
|
|
243
|
+
* the activation pipeline (1 candidate query + 3 simBatch channels). Each
|
|
244
|
+
* `hybridQueryConceptPages` call now fires four sub-queries (body-dense,
|
|
245
|
+
* body-sparse, summary-dense, summary-sparse) so we push four entries per
|
|
246
|
+
* channel iteration. Hits without `summary*Score` set produce empty point
|
|
247
|
+
* lists for the summary channels — fine for tests that only care about body
|
|
248
|
+
* scoring. */
|
|
245
249
|
function stageTurn(
|
|
246
|
-
hits: Array<{
|
|
250
|
+
hits: Array<{
|
|
251
|
+
slug: string;
|
|
252
|
+
denseScore?: number;
|
|
253
|
+
sparseScore?: number;
|
|
254
|
+
summaryDenseScore?: number;
|
|
255
|
+
summarySparseScore?: number;
|
|
256
|
+
}>,
|
|
247
257
|
): void {
|
|
248
258
|
for (let i = 0; i < 4; i++) {
|
|
249
259
|
qdrantState.queryResponses.dense.push({
|
|
@@ -256,6 +266,22 @@ function stageTurn(
|
|
|
256
266
|
.filter((h) => h.sparseScore !== undefined)
|
|
257
267
|
.map((h) => ({ score: h.sparseScore, payload: { slug: h.slug } })),
|
|
258
268
|
});
|
|
269
|
+
qdrantState.queryResponses.dense.push({
|
|
270
|
+
points: hits
|
|
271
|
+
.filter((h) => h.summaryDenseScore !== undefined)
|
|
272
|
+
.map((h) => ({
|
|
273
|
+
score: h.summaryDenseScore,
|
|
274
|
+
payload: { slug: h.slug },
|
|
275
|
+
})),
|
|
276
|
+
});
|
|
277
|
+
qdrantState.queryResponses.sparse.push({
|
|
278
|
+
points: hits
|
|
279
|
+
.filter((h) => h.summarySparseScore !== undefined)
|
|
280
|
+
.map((h) => ({
|
|
281
|
+
score: h.summarySparseScore,
|
|
282
|
+
payload: { slug: h.slug },
|
|
283
|
+
})),
|
|
284
|
+
});
|
|
259
285
|
}
|
|
260
286
|
}
|
|
261
287
|
|
|
@@ -270,39 +296,12 @@ beforeEach(() => {
|
|
|
270
296
|
_resetMemoryV2QdrantForTests();
|
|
271
297
|
});
|
|
272
298
|
|
|
273
|
-
afterEach(() => {
|
|
274
|
-
_setOverridesForTesting({});
|
|
275
|
-
});
|
|
276
|
-
|
|
277
299
|
// ---------------------------------------------------------------------------
|
|
278
300
|
// Tests
|
|
279
301
|
// ---------------------------------------------------------------------------
|
|
280
302
|
|
|
281
303
|
describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)", () => {
|
|
282
|
-
test("
|
|
283
|
-
_setOverridesForTesting({ "memory-v2-enabled": false });
|
|
284
|
-
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
285
|
-
|
|
286
|
-
const memory = makeMemory();
|
|
287
|
-
const config = makeConfig(true);
|
|
288
|
-
const messages = makeMessages();
|
|
289
|
-
|
|
290
|
-
const result = await memory.prepareMemory(
|
|
291
|
-
messages,
|
|
292
|
-
config,
|
|
293
|
-
new AbortController().signal,
|
|
294
|
-
noopEvent,
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
expect(result.mode).toBe("per-turn");
|
|
298
|
-
expect(result.injectedBlockText).toBeNull();
|
|
299
|
-
// No v2 block prepended — the v1 retriever returned zero nodes so the
|
|
300
|
-
// user message is exactly the input.
|
|
301
|
-
expect(result.runMessages).toEqual(messages);
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
test("flag on + config off → v2 not run, messages unchanged", async () => {
|
|
305
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
304
|
+
test("config off → v2 not run, messages unchanged", async () => {
|
|
306
305
|
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
307
306
|
|
|
308
307
|
const memory = makeMemory();
|
|
@@ -321,8 +320,7 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
|
|
|
321
320
|
expect(result.runMessages).toEqual(messages);
|
|
322
321
|
});
|
|
323
322
|
|
|
324
|
-
test("
|
|
325
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
323
|
+
test("config on → v2 block prepended, mode is per-turn", async () => {
|
|
326
324
|
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
327
325
|
|
|
328
326
|
const memory = makeMemory();
|
|
@@ -339,7 +337,9 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
|
|
|
339
337
|
expect(result.mode).toBe("per-turn");
|
|
340
338
|
expect(result.injectedBlockText).not.toBeNull();
|
|
341
339
|
expect(result.injectedBlockText).not.toContain("<memory>");
|
|
342
|
-
expect(result.injectedBlockText).toContain(
|
|
340
|
+
expect(result.injectedBlockText).toContain(
|
|
341
|
+
"# memory/concepts/alice-vscode.md",
|
|
342
|
+
);
|
|
343
343
|
|
|
344
344
|
// The leading content block on the user message is the v2 block,
|
|
345
345
|
// wrapped exactly once.
|
|
@@ -361,7 +361,6 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
|
|
|
361
361
|
// Regression for the double-wrap bug: v2 cached `lastInjectedBlock`
|
|
362
362
|
// already wrapped, then `reinjectCachedMemory` re-wrapped via
|
|
363
363
|
// `injectTextBlock`, producing `<memory>\n<memory>\n...\n</memory>\n</memory>`.
|
|
364
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
365
364
|
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
366
365
|
|
|
367
366
|
const memory = makeMemory();
|
|
@@ -388,11 +387,10 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
|
|
|
388
387
|
expect(firstBlock.text.endsWith("\n</memory>")).toBe(true);
|
|
389
388
|
expect(firstBlock.text.match(/<memory>/g)?.length).toBe(1);
|
|
390
389
|
expect(firstBlock.text.match(/<\/memory>/g)?.length).toBe(1);
|
|
391
|
-
expect(firstBlock.text).toContain("
|
|
390
|
+
expect(firstBlock.text).toContain("# memory/concepts/alice-vscode.md");
|
|
392
391
|
});
|
|
393
392
|
|
|
394
|
-
test("
|
|
395
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
393
|
+
test("config on with empty Qdrant hits → no v2 block, v1 fallback skipped", async () => {
|
|
396
394
|
// No `stageTurn` call — every channel returns `{ points: [] }` so the
|
|
397
395
|
// candidate set is empty and `injectMemoryV2Block` returns block=null.
|
|
398
396
|
const memory = makeMemory();
|
|
@@ -412,8 +410,7 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
|
|
|
412
410
|
});
|
|
413
411
|
|
|
414
412
|
describe("ConversationGraphMemory.prepareMemory — v2 routing (context-load path)", () => {
|
|
415
|
-
test("
|
|
416
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
413
|
+
test("config on → v2 fires with mode=context-load", async () => {
|
|
417
414
|
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
418
415
|
|
|
419
416
|
// Fresh memory → initialized=false → runContextLoad branch.
|
|
@@ -430,7 +427,9 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (context-load pat
|
|
|
430
427
|
|
|
431
428
|
expect(result.mode).toBe("context-load");
|
|
432
429
|
expect(result.injectedBlockText).not.toBeNull();
|
|
433
|
-
expect(result.injectedBlockText).toContain(
|
|
430
|
+
expect(result.injectedBlockText).toContain(
|
|
431
|
+
"# memory/concepts/alice-vscode.md",
|
|
432
|
+
);
|
|
434
433
|
// injectedBlockText is the unwrapped inner content; the wrapper is
|
|
435
434
|
// applied at injection time on the run message.
|
|
436
435
|
expect(result.injectedBlockText).not.toContain("<memory>");
|
|
@@ -443,12 +442,11 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (context-load pat
|
|
|
443
442
|
expect(loadContextMemoryMock).not.toHaveBeenCalled();
|
|
444
443
|
});
|
|
445
444
|
|
|
446
|
-
test("
|
|
447
|
-
_setOverridesForTesting({ "memory-v2-enabled": false });
|
|
445
|
+
test("config off → v2 not run on first turn either", async () => {
|
|
448
446
|
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
449
447
|
|
|
450
448
|
const memory = new ConversationGraphMemory("conv-test-cl-off");
|
|
451
|
-
const config = makeConfig(
|
|
449
|
+
const config = makeConfig(false);
|
|
452
450
|
const messages = makeMessages("first message of the conversation here");
|
|
453
451
|
|
|
454
452
|
const result = await memory.prepareMemory(
|
|
@@ -462,3 +460,48 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (context-load pat
|
|
|
462
460
|
expect(result.injectedBlockText).toBeNull();
|
|
463
461
|
});
|
|
464
462
|
});
|
|
463
|
+
|
|
464
|
+
describe("ConversationGraphMemory.onCompacted — v2 activation eviction", () => {
|
|
465
|
+
test("clears everInjected so a previously-injected slug can re-attach", async () => {
|
|
466
|
+
// Without this wiring, `selectInjections` keeps subtracting the slug from
|
|
467
|
+
// every per-turn delta even though compaction discarded the cached
|
|
468
|
+
// `<memory>` attachment that previously made it visible.
|
|
469
|
+
const conversationId = "conv-test-evict";
|
|
470
|
+
const memory = new ConversationGraphMemory(conversationId);
|
|
471
|
+
const config = makeConfig(true);
|
|
472
|
+
|
|
473
|
+
// Turn 1 — context-load fires (initialized=false), injecting alice-vscode.
|
|
474
|
+
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
475
|
+
const initial = await memory.prepareMemory(
|
|
476
|
+
makeMessages("Tell me about Alice's editor preferences"),
|
|
477
|
+
config,
|
|
478
|
+
new AbortController().signal,
|
|
479
|
+
noopEvent,
|
|
480
|
+
);
|
|
481
|
+
expect(initial.injectedBlockText).toContain(
|
|
482
|
+
"# memory/concepts/alice-vscode.md",
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
const before = await hydrateActivationState(testDbHandle!, conversationId);
|
|
486
|
+
expect(before?.everInjected.map((e) => e.slug)).toContain("alice-vscode");
|
|
487
|
+
|
|
488
|
+
await memory.onCompacted(1);
|
|
489
|
+
|
|
490
|
+
const after = await hydrateActivationState(testDbHandle!, conversationId);
|
|
491
|
+
expect(after?.everInjected).toEqual([]);
|
|
492
|
+
|
|
493
|
+
// Turn 2 — same Qdrant relevance. With everInjected cleared the slug
|
|
494
|
+
// should appear again in the injection block (re-attached on the new
|
|
495
|
+
// user message after compaction).
|
|
496
|
+
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
497
|
+
const next = await memory.prepareMemory(
|
|
498
|
+
makeMessages("And what about Alice's editor again?"),
|
|
499
|
+
config,
|
|
500
|
+
new AbortController().signal,
|
|
501
|
+
noopEvent,
|
|
502
|
+
);
|
|
503
|
+
expect(next.injectedBlockText).toContain(
|
|
504
|
+
"# memory/concepts/alice-vscode.md",
|
|
505
|
+
);
|
|
506
|
+
});
|
|
507
|
+
});
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for `handleRemember`
|
|
2
|
+
* Tests for `handleRemember` routing between v1 (PKB) and v2 (memory/).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* and NO PKB re-index job is enqueued.
|
|
7
|
-
* - Flag off → existing PKB path is unchanged (writes to `pkb/buffer.md`
|
|
8
|
-
* + `pkb/archive/<today>.md`, both files are re-indexed).
|
|
9
|
-
* - Archive filename uses today's local date.
|
|
4
|
+
* Routing follows `config.memory.v2.enabled`: when true, writes go to
|
|
5
|
+
* memory/; otherwise they fall back to v1 PKB.
|
|
10
6
|
*/
|
|
11
7
|
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
12
8
|
import { tmpdir } from "node:os";
|
|
13
9
|
import { join } from "node:path";
|
|
14
10
|
import {
|
|
15
11
|
afterAll,
|
|
16
|
-
afterEach,
|
|
17
12
|
beforeAll,
|
|
18
13
|
beforeEach,
|
|
19
14
|
describe,
|
|
@@ -62,11 +57,13 @@ afterAll(() => {
|
|
|
62
57
|
// Imports are deferred to after the env var is set so any internal use of
|
|
63
58
|
// `getWorkspaceDir()` resolves to the tmpdir.
|
|
64
59
|
const { handleRemember } = await import("../tool-handlers.js");
|
|
65
|
-
const { _setOverridesForTesting } =
|
|
66
|
-
await import("../../../config/assistant-feature-flags.js");
|
|
67
60
|
const { applyNestedDefaults } = await import("../../../config/loader.js");
|
|
68
61
|
|
|
69
62
|
const CONFIG = applyNestedDefaults({});
|
|
63
|
+
const CONFIG_V2_OFF = {
|
|
64
|
+
...CONFIG,
|
|
65
|
+
memory: { ...CONFIG.memory, v2: { ...CONFIG.memory.v2, enabled: false } },
|
|
66
|
+
};
|
|
70
67
|
|
|
71
68
|
beforeEach(() => {
|
|
72
69
|
enqueueCalls.length = 0;
|
|
@@ -76,10 +73,6 @@ beforeEach(() => {
|
|
|
76
73
|
rmSync(join(tmpWorkspace, "memory"), { recursive: true, force: true });
|
|
77
74
|
});
|
|
78
75
|
|
|
79
|
-
afterEach(() => {
|
|
80
|
-
_setOverridesForTesting({});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
76
|
function todaysArchiveBasename(now: Date = new Date()): string {
|
|
84
77
|
const yyyy = now.getFullYear();
|
|
85
78
|
const mm = String(now.getMonth() + 1).padStart(2, "0");
|
|
@@ -87,11 +80,7 @@ function todaysArchiveBasename(now: Date = new Date()): string {
|
|
|
87
80
|
return `${yyyy}-${mm}-${dd}.md`;
|
|
88
81
|
}
|
|
89
82
|
|
|
90
|
-
describe("handleRemember — memory
|
|
91
|
-
beforeEach(() => {
|
|
92
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
93
|
-
});
|
|
94
|
-
|
|
83
|
+
describe("handleRemember — memory.v2.enabled on", () => {
|
|
95
84
|
test("writes to memory/buffer.md and memory/archive/<today>.md", () => {
|
|
96
85
|
const result = handleRemember(
|
|
97
86
|
{ content: "Alice prefers VS Code over Vim" },
|
|
@@ -175,17 +164,13 @@ describe("handleRemember — memory-v2 flag on", () => {
|
|
|
175
164
|
});
|
|
176
165
|
});
|
|
177
166
|
|
|
178
|
-
describe("handleRemember — memory
|
|
179
|
-
beforeEach(() => {
|
|
180
|
-
_setOverridesForTesting({ "memory-v2-enabled": false });
|
|
181
|
-
});
|
|
182
|
-
|
|
167
|
+
describe("handleRemember — memory.v2.enabled off (v1 PKB path)", () => {
|
|
183
168
|
test("writes to pkb/buffer.md and pkb/archive/<today>.md", () => {
|
|
184
169
|
const result = handleRemember(
|
|
185
170
|
{ content: "v1 path still works" },
|
|
186
171
|
"conv-v1-1",
|
|
187
172
|
"default",
|
|
188
|
-
|
|
173
|
+
CONFIG_V2_OFF,
|
|
189
174
|
);
|
|
190
175
|
|
|
191
176
|
expect(result.success).toBe(true);
|
|
@@ -206,7 +191,7 @@ describe("handleRemember — memory-v2 flag off (v1 PKB path)", () => {
|
|
|
206
191
|
{ content: "index me" },
|
|
207
192
|
"conv-v1-2",
|
|
208
193
|
"default",
|
|
209
|
-
|
|
194
|
+
CONFIG_V2_OFF,
|
|
210
195
|
);
|
|
211
196
|
|
|
212
197
|
expect(result.success).toBe(true);
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
import { and, desc, eq, inArray, ne, notInArray } from "drizzle-orm";
|
|
10
10
|
|
|
11
|
-
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
12
11
|
import type { AssistantConfig } from "../../config/types.js";
|
|
13
12
|
import { estimateTextTokens } from "../../context/token-estimator.js";
|
|
14
13
|
import type { ServerMessage } from "../../daemon/message-protocol.js";
|
|
@@ -23,6 +22,11 @@ import { getDb } from "../db-connection.js";
|
|
|
23
22
|
import type { QdrantSparseVector } from "../qdrant-client.js";
|
|
24
23
|
import { memorySummaries } from "../schema.js";
|
|
25
24
|
import { conversations } from "../schema/conversations.js";
|
|
25
|
+
import {
|
|
26
|
+
evictCompactedTurns as evictCompactedTurnsV2,
|
|
27
|
+
hydrate as hydrateV2State,
|
|
28
|
+
save as saveV2State,
|
|
29
|
+
} from "../v2/activation-store.js";
|
|
26
30
|
import {
|
|
27
31
|
injectMemoryV2Block,
|
|
28
32
|
type InjectMemoryV2Mode,
|
|
@@ -206,11 +210,33 @@ export class ConversationGraphMemory {
|
|
|
206
210
|
* Notify that context compaction just happened.
|
|
207
211
|
* On the next turn, we'll re-run full context load.
|
|
208
212
|
*/
|
|
209
|
-
onCompacted(compactedMessageCount: number): void {
|
|
213
|
+
async onCompacted(compactedMessageCount: number): Promise<void> {
|
|
210
214
|
// Evict everything — compaction summarized all prior turns.
|
|
211
215
|
// The tracker can't know exactly which turns were compacted,
|
|
212
216
|
// so we conservatively clear everything and reload.
|
|
213
|
-
this.tracker.
|
|
217
|
+
const upToTurn = this.tracker.getTurn();
|
|
218
|
+
this.tracker.evictCompactedTurns(upToTurn);
|
|
219
|
+
|
|
220
|
+
// Mirror the eviction on the v2 activation row: the cached `<memory>`
|
|
221
|
+
// attachments those slugs lived on are gone, but `everInjected` would
|
|
222
|
+
// otherwise keep them deduped from per-turn deltas forever.
|
|
223
|
+
try {
|
|
224
|
+
const db = getDb();
|
|
225
|
+
const state = await hydrateV2State(db, this.conversationId);
|
|
226
|
+
if (state) {
|
|
227
|
+
await saveV2State(
|
|
228
|
+
db,
|
|
229
|
+
this.conversationId,
|
|
230
|
+
evictCompactedTurnsV2(state, upToTurn),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
} catch (err) {
|
|
234
|
+
log.warn(
|
|
235
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
236
|
+
"Failed to evict v2 activation state on compaction (non-fatal)",
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
214
240
|
this.needsReload = true;
|
|
215
241
|
log.info(
|
|
216
242
|
{ compactedMessageCount },
|
|
@@ -627,8 +653,8 @@ export class ConversationGraphMemory {
|
|
|
627
653
|
}
|
|
628
654
|
|
|
629
655
|
/**
|
|
630
|
-
* Run the v2 activation pipeline when the
|
|
631
|
-
*
|
|
656
|
+
* Run the v2 activation pipeline when the workspace config
|
|
657
|
+
* (`memory.v2.enabled`) is on.
|
|
632
658
|
*
|
|
633
659
|
* The two outcomes the caller distinguishes via `routed`:
|
|
634
660
|
* - `routed: false` — v2 disabled; caller falls through to the legacy v1
|
|
@@ -650,10 +676,7 @@ export class ConversationGraphMemory {
|
|
|
650
676
|
runMessages: Message[];
|
|
651
677
|
injectedBlockText: string | null;
|
|
652
678
|
}> {
|
|
653
|
-
if (
|
|
654
|
-
!isAssistantFeatureFlagEnabled("memory-v2-enabled", config) ||
|
|
655
|
-
!config.memory.v2.enabled
|
|
656
|
-
) {
|
|
679
|
+
if (!config.memory.v2.enabled) {
|
|
657
680
|
return { routed: false, runMessages: messages, injectedBlockText: null };
|
|
658
681
|
}
|
|
659
682
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import { makeMockLogger } from "../../__tests__/helpers/mock-logger.js";
|
|
4
|
-
import { _setOverridesForTesting } from "../../config/assistant-feature-flags.js";
|
|
5
4
|
|
|
6
|
-
// This test exercises the v1 graph search path.
|
|
7
|
-
// (
|
|
8
|
-
//
|
|
9
|
-
|
|
5
|
+
// This test exercises the v1 graph search path. `config.memory.v2.enabled`
|
|
6
|
+
// (default `true`) makes graph-search short-circuit to keep traffic off
|
|
7
|
+
// the legacy collection — force it off so the v1 path stays under test.
|
|
8
|
+
mock.module("../../config/loader.js", () => ({
|
|
9
|
+
getConfig: () => ({ memory: { v2: { enabled: false } } }),
|
|
10
|
+
}));
|
|
10
11
|
|
|
11
12
|
mock.module("../../util/logger.js", () => ({
|
|
12
13
|
getLogger: () => makeMockLogger(),
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import { getConfig } from "../../config/loader.js";
|
|
6
6
|
import type { AssistantConfig } from "../../config/types.js";
|
|
7
7
|
import { getLogger } from "../../util/logger.js";
|
|
8
|
-
import { isMemoryV2ReadActive } from "../context-search/sources/memory-v2.js";
|
|
9
8
|
import { selectedBackendSupportsMultimodal } from "../embedding-backend.js";
|
|
10
9
|
import type { EmbeddingInput } from "../embedding-types.js";
|
|
11
10
|
import { embedAndUpsert } from "../job-utils.js";
|
|
@@ -46,11 +45,11 @@ export async function searchGraphNodes(
|
|
|
46
45
|
sparseVector?: QdrantSparseVector,
|
|
47
46
|
dateRange?: { afterMs?: number; beforeMs?: number },
|
|
48
47
|
): Promise<GraphSearchResult[]> {
|
|
49
|
-
// v2 owns the read path when
|
|
50
|
-
//
|
|
48
|
+
// v2 owns the read path when enabled. The v1 `memory` collection is in
|
|
49
|
+
// active retirement and a corrupted sparse segment can OOM-crash the
|
|
51
50
|
// shared Qdrant process — short-circuiting here keeps v1 background work
|
|
52
51
|
// and stale callers from taking v2 down with them.
|
|
53
|
-
if (
|
|
52
|
+
if (getConfig().memory.v2.enabled) return [];
|
|
54
53
|
|
|
55
54
|
if (isQdrantBreakerOpen()) {
|
|
56
55
|
log.warn("Qdrant circuit breaker open, skipping graph search");
|
|
@@ -71,7 +71,6 @@ mock.module("../../providers/provider-send-message.js", () => ({
|
|
|
71
71
|
extractToolUse: () => null,
|
|
72
72
|
}));
|
|
73
73
|
|
|
74
|
-
import { _setOverridesForTesting } from "../../config/assistant-feature-flags.js";
|
|
75
74
|
import { DEFAULT_CONFIG } from "../../config/defaults.js";
|
|
76
75
|
import type { AssistantConfig } from "../../config/types.js";
|
|
77
76
|
import { resetDb } from "../db-connection.js";
|
|
@@ -82,12 +81,16 @@ import { loadContextMemory, retrieveForTurn } from "./retriever.js";
|
|
|
82
81
|
import { createNode } from "./store.js";
|
|
83
82
|
import type { NewNode } from "./types.js";
|
|
84
83
|
|
|
85
|
-
// These tests exercise v1 retrieval. v2
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
// These tests exercise v1 retrieval. `memory.v2.enabled` (default `true`)
|
|
85
|
+
// makes `loadContextMemory` short-circuit, so disable it here to keep the
|
|
86
|
+
// v1 path under test.
|
|
87
|
+
const TEST_CONFIG: AssistantConfig = {
|
|
88
|
+
...DEFAULT_CONFIG,
|
|
89
|
+
memory: {
|
|
90
|
+
...DEFAULT_CONFIG.memory,
|
|
91
|
+
v2: { ...DEFAULT_CONFIG.memory.v2, enabled: false },
|
|
92
|
+
},
|
|
93
|
+
};
|
|
91
94
|
|
|
92
95
|
function makeCapabilityNode(content: string, capId: string): NewNode {
|
|
93
96
|
const now = Date.now();
|
|
@@ -407,8 +410,10 @@ describe("loadContextMemory — dual-query capability ranking", () => {
|
|
|
407
410
|
|
|
408
411
|
// Build a config where capabilityReserve=1 so the ranking code actually
|
|
409
412
|
// prunes (it only prunes when capabilityNodes.length > capabilityReserve).
|
|
413
|
+
// memory.v2.enabled=false to keep the v1 retrieval path under test.
|
|
410
414
|
const DUAL_QUERY_CONFIG: AssistantConfig = structuredClone(DEFAULT_CONFIG);
|
|
411
415
|
DUAL_QUERY_CONFIG.memory.retrieval.injection.contextLoad.capabilityReserve = 1;
|
|
416
|
+
DUAL_QUERY_CONFIG.memory.v2.enabled = false;
|
|
412
417
|
|
|
413
418
|
// Keyword-routed embed: any text that contains a topic keyword returns a
|
|
414
419
|
// one-hot vector identifying that topic. Anything else falls back to a
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
} from "../../providers/provider-send-message.js";
|
|
15
15
|
import type { ContentBlock, ImageContent } from "../../providers/types.js";
|
|
16
16
|
import { getLogger } from "../../util/logger.js";
|
|
17
|
-
import { isMemoryV2ReadActive } from "../context-search/sources/memory-v2.js";
|
|
18
17
|
import { embedWithRetry } from "../embed.js";
|
|
19
18
|
import {
|
|
20
19
|
generateSparseEmbedding,
|
|
@@ -426,12 +425,12 @@ interface ContextLoadResult {
|
|
|
426
425
|
export async function loadContextMemory(
|
|
427
426
|
opts: ContextLoadOpts,
|
|
428
427
|
): Promise<ContextLoadResult> {
|
|
429
|
-
// v2 owns the read path when
|
|
430
|
-
//
|
|
431
|
-
//
|
|
428
|
+
// v2 owns the read path when enabled. The v1 collection is in active
|
|
429
|
+
// retirement and querying it can OOM-crash Qdrant via a corrupted sparse
|
|
430
|
+
// segment, so we skip the embedding work and downstream searches
|
|
432
431
|
// entirely. Caller (`runContextLoad`) sees zero nodes and routes to the
|
|
433
432
|
// v2 activation pipeline.
|
|
434
|
-
if (
|
|
433
|
+
if (opts.config.memory.v2.enabled) {
|
|
435
434
|
return {
|
|
436
435
|
nodes: [],
|
|
437
436
|
serendipityNodes: [],
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
// Memory Tool handlers
|
|
3
3
|
//
|
|
4
4
|
// remember: save facts to the PKB (buffer.md + daily archive) under the v1
|
|
5
|
-
// path, or to memory/buffer.md + memory/archive/<today>.md when
|
|
6
|
-
//
|
|
5
|
+
// path, or to memory/buffer.md + memory/archive/<today>.md when memory v2 is
|
|
6
|
+
// active.
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
|
|
9
9
|
import { appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
|
|
12
|
-
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
13
12
|
import type { AssistantConfig } from "../../config/types.js";
|
|
14
13
|
import { getLogger } from "../../util/logger.js";
|
|
15
14
|
import { getWorkspaceDir } from "../../util/platform.js";
|
|
@@ -46,7 +45,7 @@ export function handleRemember(
|
|
|
46
45
|
const now = new Date();
|
|
47
46
|
const entry = formatRememberEntry(input.content.trim(), now);
|
|
48
47
|
|
|
49
|
-
if (
|
|
48
|
+
if (config.memory.v2.enabled) {
|
|
50
49
|
appendBufferAndArchive({
|
|
51
50
|
rootDir: join(workspaceDir, "memory"),
|
|
52
51
|
entry,
|
|
@@ -56,10 +56,10 @@ export const graphRecallDefinition: ToolDefinition = {
|
|
|
56
56
|
/**
|
|
57
57
|
* Save a fact to the assistant's knowledge base. The fact is appended to
|
|
58
58
|
* `buffer.md` (immediately available in the next conversation) and the daily
|
|
59
|
-
* archive (permanent date-indexed record).
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
59
|
+
* archive (permanent date-indexed record). When `memory.v2.enabled` is true,
|
|
60
|
+
* writes go under `memory/`; otherwise they go under `pkb/`. Consolidation
|
|
61
|
+
* of the buffer into longer-form storage runs as a separate periodic job in
|
|
62
|
+
* both modes.
|
|
63
63
|
*/
|
|
64
64
|
export const graphRememberDefinition: ToolDefinition = {
|
|
65
65
|
name: "remember",
|
package/src/memory/indexer.ts
CHANGED
|
@@ -9,7 +9,6 @@ import { getLogger } from "../util/logger.js";
|
|
|
9
9
|
import { enqueueAutoAnalysisIfEnabled } from "./auto-analysis-enqueue.js";
|
|
10
10
|
import { isAutoAnalysisConversation } from "./auto-analysis-guard.js";
|
|
11
11
|
import { getMemoryCheckpoint, setMemoryCheckpoint } from "./checkpoints.js";
|
|
12
|
-
import { isMemoryV2ReadActive } from "./context-search/sources/memory-v2.js";
|
|
13
12
|
import { getDb } from "./db-connection.js";
|
|
14
13
|
import { selectedBackendSupportsMultimodal } from "./embedding-backend.js";
|
|
15
14
|
import { enqueueMemoryJob, upsertDebouncedJob } from "./jobs-store.js";
|
|
@@ -190,7 +189,7 @@ export async function indexMessageNow(
|
|
|
190
189
|
}
|
|
191
190
|
|
|
192
191
|
const v2Config =
|
|
193
|
-
triggerConfig != null &&
|
|
192
|
+
triggerConfig != null && triggerConfig.memory.v2.enabled
|
|
194
193
|
? triggerConfig
|
|
195
194
|
: null;
|
|
196
195
|
|