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
+90
View File
@@ -893,6 +893,96 @@ export async function patchMemoryV2Config(patch: Record<string, unknown>) {
export async function fetchMemoryV2Runtime() {
return adminFetch<MemoryV2RuntimeState>('/admin-api/memory-v2/runtime');
}
export type MemoryV2ProductMetrics = {
window: { since: string; sinceMs: number; untilMs: number };
userId: string | null;
events: {
memory_candidate_saved: number;
memory_promoted: number;
memory_resolved_injected: number;
memory_recall_hit: number;
};
sources: Record<string, string>;
};
export type MemoryV2ShadowAuditSummary = {
falseStoreRate: number | null;
autoAcceptRate: number | null;
resolveHitRate: number | null;
suspiciousCandidateCount: number;
pgvectorLagUserCount: number;
};
export type MemoryV2MetricsResponse = {
metrics: MemoryV2ProductMetrics;
audit: MemoryV2ShadowAuditSummary;
candidateCounts: Record<string, number>;
};
export type MemoryV2CandidateRow = {
id: string;
userId: string;
sessionId: string | null;
memoryType: string;
content: string;
importance: number;
confidence: number;
status: string;
policyReason: string;
createdAt: number;
updatedAt: number;
};
export async function fetchMemoryV2Metrics(params?: { since?: string; userId?: string }) {
const query = new URLSearchParams();
if (params?.since) query.set('since', params.since);
if (params?.userId) query.set('userId', params.userId);
const suffix = query.toString() ? `?${query.toString()}` : '';
return adminFetch<MemoryV2MetricsResponse>(`/admin-api/memory-v2/metrics${suffix}`);
}
export async function fetchMemoryV2Candidates(params?: {
status?: string;
userId?: string;
limit?: number;
offset?: number;
}) {
const query = new URLSearchParams();
if (params?.status) query.set('status', params.status);
if (params?.userId) query.set('userId', params.userId);
if (params?.limit != null) query.set('limit', String(params.limit));
if (params?.offset != null) query.set('offset', String(params.offset));
const suffix = query.toString() ? `?${query.toString()}` : '';
return adminFetch<{ items: MemoryV2CandidateRow[]; status: string; limit: number; offset: number }>(
`/admin-api/memory-v2/candidates${suffix}`,
);
}
export async function runMemoryV2CandidateAutoReview(params?: { userId?: string; limit?: number }) {
return adminFetch<{
ok: true;
scanned: number;
accepted: number;
rejected: number;
pending: number;
samples?: Array<{ id: string; action: string; reason: string }>;
}>('/admin-api/memory-v2/candidates/auto-review', {
method: 'POST',
body: JSON.stringify({
userId: params?.userId,
limit: params?.limit ?? 200,
}),
});
}
export async function reviewMemoryV2Candidate(id: string, status: 'accepted' | 'rejected') {
return adminFetch<{ ok: true; updated: boolean }>(`/admin-api/memory-v2/candidates/${encodeURIComponent(id)}/review`, {
method: 'POST',
body: JSON.stringify({ status }),
});
}
export type GoalRunRuntimeState = {
enabled: boolean;
canaryUserIds: string[];