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
+77
View File
@@ -31,6 +31,7 @@ function plazaRouteError(res, req, error) {
* @param {object} deps.userAuth
* @param {object|null} deps.llmProviderService
* @param {object|null} deps.memoryV2ConfigService
* @param {object|null} deps.memoryV2AdminOpsService
* @param {object|null} deps.goalRunAdminOpsService
* @param {object|null} deps.orchestratorConfigService
* @param {object|null} deps.orchestratorObservabilityService
@@ -51,6 +52,7 @@ export function createAdminApi({
assetGatewayConfigService,
imageMakeAdminConfigService,
memoryV2ConfigService,
memoryV2AdminOpsService,
goalRunAdminOpsService,
orchestratorConfigService,
orchestratorObservabilityService,
@@ -233,6 +235,81 @@ export function createAdminApi({
return res.json(result);
});
adminApi.get('/memory-v2/metrics', requireAdmin, async (req, res) => {
if (!memoryV2AdminOpsService?.getProductMetrics) {
return res.status(503).json({ message: 'Memory V2 指标服务未启用' });
}
try {
const since = String(req.query?.since ?? '7d').trim() || '7d';
const userId = String(req.query?.userId ?? '').trim() || null;
const metrics = await memoryV2AdminOpsService.getProductMetrics({ since, userId });
const audit = await memoryV2AdminOpsService.getShadowAudit({ since, userId });
const counts = await memoryV2AdminOpsService.countCandidatesByStatus();
return res.json({ metrics, audit, candidateCounts: counts });
} catch (err) {
return res.status(500).json({
message: err instanceof Error ? err.message : '读取 Memory V2 指标失败',
});
}
});
adminApi.get('/memory-v2/candidates', requireAdmin, async (req, res) => {
if (!memoryV2AdminOpsService?.listCandidates) {
return res.status(503).json({ message: 'Memory V2 候选服务未启用' });
}
try {
const status = String(req.query?.status ?? 'candidate').trim() || 'candidate';
const userId = String(req.query?.userId ?? '').trim() || null;
const limit = Number(req.query?.limit ?? 50);
const offset = Number(req.query?.offset ?? 0);
const items = await memoryV2AdminOpsService.listCandidates({ status, userId, limit, offset });
return res.json({ items, status, limit, offset });
} catch (err) {
return res.status(400).json({
message: err instanceof Error ? err.message : '读取候选记忆失败',
});
}
});
adminApi.post('/memory-v2/candidates/auto-review', requireAdmin, async (req, res) => {
if (!memoryV2AdminOpsService?.runAutoReview) {
return res.status(503).json({ message: 'Memory V2 候选服务未启用' });
}
try {
const userId = String(req.body?.userId ?? '').trim() || null;
const limit = Number(req.body?.limit ?? 200);
const result = await memoryV2AdminOpsService.runAutoReview({ userId, limit });
return res.json({ ok: true, ...result });
} catch (err) {
return res.status(500).json({
message: err instanceof Error ? err.message : '自动审核候选记忆失败',
});
}
});
adminApi.post('/memory-v2/candidates/:id/review', requireAdmin, async (req, res) => {
if (!memoryV2AdminOpsService?.reviewCandidate) {
return res.status(503).json({ message: 'Memory V2 候选服务未启用' });
}
const status = String(req.body?.status ?? '').trim();
if (!['accepted', 'rejected'].includes(status)) {
return res.status(400).json({ message: 'status 必须是 accepted 或 rejected' });
}
try {
const result = await memoryV2AdminOpsService.reviewCandidate(req.params.id, status, {
reviewedBy: req.currentUser.id,
});
if (!result.updated) {
return res.status(409).json({ message: '候选不存在或已审核' });
}
return res.json({ ok: true, ...result });
} catch (err) {
return res.status(400).json({
message: err instanceof Error ? err.message : '审核候选记忆失败',
});
}
});
adminApi.get('/goal-runs/runtime', requireAdmin, async (_req, res) => {
if (!goalRunAdminOpsService?.getRuntime) {
return res.status(503).json({ message: 'Goal Run 管理服务未启用' });