feat(memory-v2): close Phase A with auto-review, product events, and H5 recall UI.

Add candidate auto-review pipeline, shadow audit tooling, admin metrics page,
and user-visible memory recall hints in chat with phase-a readiness checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-01 17:14:06 +08:00
parent 666db0b939
commit 6f3e53a56a
50 changed files with 3730 additions and 38 deletions
+83 -1
View File
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { attachPortalUserMemoryRoutes } from './portal-user-memory-routes.mjs';
import { attachPortalUserMemoryRoutes, resolveLatestMemoryRecallHint } from './portal-user-memory-routes.mjs';
function createRouterRecorder() {
const routes = new Map();
@@ -69,6 +69,7 @@ test('User Memory module preserves route inventory and order', () => {
assert.deepEqual([...api.routes.keys()], [
'POST /user-memory/v1/remember-recent',
'GET /user-memory/v1/recall-hint',
'POST /user-memory/v1/sync',
'GET /user-memory/v1/items',
'DELETE /user-memory/v1/items/:memoryId',
@@ -212,6 +213,11 @@ test('remember-recent preserves write, sync, resolve order and response projecti
memories: 2,
totalMemories: 3,
syncedToSession: true,
personalMemory: {
savedPreviews: [],
autoReviewed: 0,
pendingReview: 0,
},
});
});
@@ -419,3 +425,79 @@ test('delete preserves forwarding, unavailable fallback, success, and errors', a
assert.equal(failureRes.statusCode, 500);
assert.deepEqual(failureRes.body, { message: 'forget failed' });
});
test('resolveLatestMemoryRecallHint merges recall event and saved candidate preview', async () => {
const pool = {
async query(sql, params) {
if (String(sql).includes('agent_memory_resolved')) {
return [[{
data_json: JSON.stringify({
memoryCount: 3,
injectionEnabled: true,
mode: 'canary',
memoryPreviews: ['我喜欢喝美式咖啡', '每周三做代码评审'],
}),
created_at: 1000,
run_id: 'run-1',
}]];
}
if (String(sql).includes('h5_memory_v2_candidates')) {
assert.deepEqual(params, ['user-1', 'session-1']);
return [[{ content: '我喜欢喝美式咖啡', updated_at: 900 }]];
}
return [[]];
},
};
const hint = await resolveLatestMemoryRecallHint(pool, {
userId: 'user-1',
sessionId: 'session-1',
});
assert.deepEqual(hint, {
memoryCount: 3,
injectionEnabled: true,
mode: 'canary',
createdAt: 1000,
runId: 'run-1',
savedPreview: '我喜欢喝美式咖啡',
savedAt: 900,
memoryPreviews: ['我喜欢喝美式咖啡', '每周三做代码评审'],
});
});
test('recall-hint preserves capability gate and returns merged hint payload', async () => {
const api = createRouterRecorder();
attachPortalUserMemoryRoutes(
api,
createDependencies({
getPool: () => ({
async query(sql) {
if (String(sql).includes('agent_memory_resolved')) {
return [[{
data_json: JSON.stringify({ memoryCount: 2, injectionEnabled: true, mode: 'canary' }),
created_at: 2000,
run_id: 'run-9',
}]];
}
return [[]];
},
}),
}),
);
const res = createResponseRecorder();
await api.routes.get('GET /user-memory/v1/recall-hint')(
createRequest({ query: { sessionId: 'session-9' } }),
res,
);
assert.equal(res.statusCode, 200);
assert.deepEqual(res.body, {
ok: true,
memoryCount: 2,
injectionEnabled: true,
mode: 'canary',
createdAt: 2000,
runId: 'run-9',
savedPreview: null,
savedAt: null,
memoryPreviews: [],
});
});