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:
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user