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
+105
View File
@@ -90,6 +90,111 @@ test('admin memory-v2 config routes expose config and runtime state', async () =
}
});
test('admin memory-v2 metrics and candidate review routes', async () => {
const router = createAdminApi({
jsonBody: express.json(),
getToken() {
return 'token-admin';
},
userAuth: {
async getMe(token) {
if (token !== 'token-admin') return null;
return { id: 'admin-1', role: 'admin' };
},
},
memoryV2AdminOpsService: {
async getProductMetrics() {
return {
window: { since: '7d', sinceMs: 1, untilMs: 2 },
userId: null,
events: {
memory_candidate_saved: 3,
memory_promoted: 2,
memory_resolved_injected: 1,
memory_recall_hit: 1,
},
sources: {},
};
},
async getShadowAudit() {
return {
falseStoreRate: 0,
autoAcceptRate: 0.1,
resolveHitRate: 0.5,
suspiciousCandidateCount: 0,
pgvectorLagUserCount: 0,
};
},
async countCandidatesByStatus() {
return { candidate: 4, accepted: 2 };
},
async listCandidates() {
return [{
id: 'cand-1',
userId: 'user-1',
sessionId: 'session-1',
memoryType: 'episodic',
content: '请记住我喜欢美式咖啡',
importance: 0.9,
confidence: 0.9,
status: 'candidate',
policyReason: 'explicit_memory_request',
createdAt: 1000,
updatedAt: 1000,
}];
},
async reviewCandidate(id, status, { reviewedBy }) {
assert.equal(id, 'cand-1');
assert.equal(status, 'accepted');
assert.equal(reviewedBy, 'admin-1');
return { updated: true, status, reviewedAt: 2000 };
},
},
llmProviderService: null,
memoryV2ConfigService: null,
plazaPosts: null,
plazaOps: null,
wechatAdmin: null,
subscriptionService: null,
});
const server = await startTestServer(router);
try {
const metricsRes = await fetch(`${server.baseUrl}/admin-api/memory-v2/metrics`, {
headers: { cookie: 'h5_user_session=token-admin' },
});
assert.equal(metricsRes.status, 200);
const metricsBody = await metricsRes.json();
assert.equal(metricsBody.metrics.events.memory_candidate_saved, 3);
assert.equal(metricsBody.candidateCounts.candidate, 4);
const listRes = await fetch(`${server.baseUrl}/admin-api/memory-v2/candidates`, {
headers: { cookie: 'h5_user_session=token-admin' },
});
assert.equal(listRes.status, 200);
const listBody = await listRes.json();
assert.equal(listBody.items.length, 1);
const reviewRes = await fetch(`${server.baseUrl}/admin-api/memory-v2/candidates/cand-1/review`, {
method: 'POST',
headers: {
'content-type': 'application/json',
cookie: 'h5_user_session=token-admin',
},
body: JSON.stringify({ status: 'accepted' }),
});
assert.equal(reviewRes.status, 200);
assert.deepEqual(await reviewRes.json(), {
ok: true,
updated: true,
status: 'accepted',
reviewedAt: 2000,
});
} finally {
await server.close();
}
});
test('admin goal-runs routes expose summary, list, and detail', async () => {
const router = createAdminApi({
jsonBody: express.json(),