basant307/AI_Governance_Project
048
1/**2 * @license3 * Copyright 2026 Qwen Team4 * SPDX-License-Identifier: Apache-2.05 */6 7import * as fs from 'node:fs/promises';8import * as os from 'node:os';9import * as path from 'node:path';10import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';11import type { Config } from '../config/config.js';12import { runAutoMemoryExtractionByAgent } from './extractionAgentPlanner.js';13import { runManagedAutoMemoryDream } from './dream.js';14import { planManagedAutoMemoryDreamByAgent } from './dreamAgentPlanner.js';15import { MemoryManager } from './manager.js';16import { rebuildManagedAutoMemoryIndex } from './indexer.js';17import { getAutoMemoryFilePath, getAutoMemoryIndexPath } from './paths.js';18import { resolveRelevantAutoMemoryPromptForQuery } from './recall.js';19import { scanAutoMemoryTopicDocuments } from './scan.js';20import { ensureAutoMemoryScaffold } from './store.js';21 22vi.mock('./extractionAgentPlanner.js', () => ({23 runAutoMemoryExtractionByAgent: vi.fn(),24}));25 26vi.mock('./dreamAgentPlanner.js', () => ({27 planManagedAutoMemoryDreamByAgent: vi.fn(),28}));29 30describe('managed auto-memory lifecycle integration', () => {31 let tempDir: string;32 let projectRoot: string;33 let mockConfig: Config;34 let extractionCount: number;35 let mgr: MemoryManager;36 37 beforeEach(async () => {38 mgr = new MemoryManager();39 tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'memory-lifecycle-int-'));40 projectRoot = path.join(tempDir, 'project');41 await fs.mkdir(projectRoot, { recursive: true });42 await ensureAutoMemoryScaffold(43 projectRoot,44 new Date('2026-04-01T00:00:00.000Z'),45 );46 mockConfig = {47 getSessionId: () => 'session-1',48 getModel: () => 'qwen3-coder-plus',49 } as Config;50 vi.clearAllMocks();51 extractionCount = 0;52 vi.mocked(runAutoMemoryExtractionByAgent).mockImplementation(53 async (_config, root: string) => {54 extractionCount += 1;55 const topic = extractionCount > 1 ? 'reference' : 'user';56 const relativePath =57 topic === 'reference'58 ? path.join('reference', 'latency-dashboard.md')59 : path.join('user', 'terse-responses.md');60 const filePath = getAutoMemoryFilePath(root, relativePath);61 await fs.mkdir(path.dirname(filePath), { recursive: true });62 const description =63 topic === 'reference'64 ? 'https://grafana.example/d/api-latency'65 : 'I prefer terse responses.';66 await fs.writeFile(67 filePath,68 [69 '---',70 `type: ${topic}`,71 `name: ${topic === 'reference' ? 'Latency Dashboard' : 'Terse Responses'}`,72 `description: ${description}`,73 '---',74 '',75 description,76 '',77 ].join('\n'),78 'utf-8',79 );80 81 return {82 touchedTopics: [topic],83 touchedProjectScope: true,84 touchedUserScope: false,85 hasToolActivity: true,86 systemMessage: undefined,87 };88 },89 );90 vi.mocked(planManagedAutoMemoryDreamByAgent).mockResolvedValue({91 status: 'completed',92 finalText: 'Consolidated memory files and updated the index.',93 filesTouched: [94 getAutoMemoryFilePath(95 projectRoot,96 path.join('user', 'terse-responses.md'),97 ),98 getAutoMemoryFilePath(99 projectRoot,100 path.join('reference', 'latency-dashboard.md'),101 ),102 ],103 });104 });105 106 afterEach(async () => {107 mgr.resetExtractStateForTests();108 await fs.rm(tempDir, {109 recursive: true,110 force: true,111 maxRetries: 3,112 retryDelay: 10,113 });114 });115 116 it('supports a durable memory lifecycle across extraction, recall, and dream', async () => {117 const firstExtraction = mgr.scheduleExtract({118 projectRoot,119 sessionId: 'session-1',120 config: mockConfig,121 history: [122 { role: 'user', parts: [{ text: 'I prefer terse responses.' }] },123 ],124 });125 126 const queuedExtraction = await mgr.scheduleExtract({127 projectRoot,128 sessionId: 'session-1',129 config: mockConfig,130 history: [131 { role: 'user', parts: [{ text: 'I prefer terse responses.' }] },132 { role: 'model', parts: [{ text: 'Understood.' }] },133 {134 role: 'user',135 parts: [136 {137 text: 'The latency dashboard is https://grafana.example/d/api-latency',138 },139 ],140 },141 ],142 });143 144 expect(queuedExtraction.skippedReason).toBe('queued');145 146 const firstResult = await firstExtraction;147 expect(firstResult.touchedTopics).toEqual(['user']);148 149 const drained = await mgr.drain({150 timeoutMs: 1_000,151 });152 expect(drained).toBe(true);153 154 const projectPath = getAutoMemoryFilePath(155 projectRoot,156 path.join('project', 'latency-dashboard.md'),157 );158 await fs.mkdir(path.dirname(projectPath), { recursive: true });159 await fs.writeFile(160 projectPath,161 [162 '---',163 'type: project',164 'name: Latency Dashboard',165 'description: The latency dashboard is https://grafana.example/d/api-latency',166 '---',167 '',168 'The latency dashboard is https://grafana.example/d/api-latency',169 '',170 'Why: This is temporary for this task.',171 ].join('\n'),172 'utf-8',173 );174 await rebuildManagedAutoMemoryIndex(projectRoot);175 176 const duplicateUserPath = getAutoMemoryFilePath(177 projectRoot,178 path.join('user', 'terse-duplicate.md'),179 );180 await fs.mkdir(path.dirname(duplicateUserPath), { recursive: true });181 await fs.writeFile(182 duplicateUserPath,183 [184 '---',185 'type: user',186 'name: User Memory Duplicate',187 'description: Duplicate terse preference',188 '---',189 '',190 'I prefer terse responses.',191 '',192 'Why: User repeatedly asks for concise replies.',193 ].join('\n'),194 'utf-8',195 );196 await rebuildManagedAutoMemoryIndex(projectRoot);197 198 const dreamResult = await runManagedAutoMemoryDream(199 projectRoot,200 new Date('2026-04-01T03:00:00.000Z'),201 mockConfig,202 );203 expect(dreamResult.touchedTopics).toContain('user');204 expect(dreamResult.dedupedEntries).toBe(0);205 206 const indexContent = await fs.readFile(207 getAutoMemoryIndexPath(projectRoot),208 'utf-8',209 );210 const docs = await scanAutoMemoryTopicDocuments(projectRoot);211 const userDoc = docs.find((doc) => doc.type === 'user');212 const projectDoc = docs.find((doc) => doc.type === 'project');213 const referenceDoc = docs.find((doc) => doc.type === 'reference');214 215 expect(userDoc?.body).toContain('I prefer terse responses.');216 expect(userDoc?.body).toContain(217 'Why: User repeatedly asks for concise replies.',218 );219 expect(referenceDoc?.body).toContain('grafana.example/d/api-latency');220 expect(projectDoc?.body).toContain('This is temporary for this task.');221 expect(indexContent).toContain('user/');222 223 const recall = await resolveRelevantAutoMemoryPromptForQuery(224 projectRoot,225 'Check the latency dashboard and use a terse answer.',226 );227 expect(recall.strategy).toBe('heuristic');228 expect(recall.prompt).toContain('## Relevant memory');229 expect(recall.prompt).toContain('user/');230 expect(recall.prompt).toContain('reference/');231 });232});233 