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
+33 -15
View File
@@ -16,6 +16,7 @@ import {
listSessions,
loadSessionDetail,
readConfig,
fetchMemoryRecallHint,
rememberUserMemory,
rememberProjectContext,
uploadMindSpaceAsset,
@@ -40,6 +41,7 @@ import type {
CapabilityMap,
ChatFileAttachment,
ChatState,
MemoryRecallBadge,
Message,
MindSpaceChatContext,
PortalUser,
@@ -78,6 +80,7 @@ import {
isRelayServerErrorMessage,
pushMessage,
} from '../utils/message';
import { formatMemoryHintNotice, formatUserMemorySyncNotice, buildMemoryRecallBadge, resolveMessageRecallKey } from '../utils/memoryFeedback';
import {
appendSessionLists,
prependUnique,
@@ -358,6 +361,7 @@ export function useTKMindChat(
const [sessionsHasMore, setSessionsHasMore] = useState(false);
const [sessionSearchQuery, setSessionSearchQueryState] = useState('');
const [messages, setMessages] = useState<Message[]>([]);
const [memoryRecallByMessageId, setMemoryRecallByMessageId] = useState<Record<string, MemoryRecallBadge>>({});
const [messageHistoryLoadingMore, setMessageHistoryLoadingMore] = useState(false);
const [messageHistoryHasMore, setMessageHistoryHasMore] = useState(false);
const [messageHistoryTotal, setMessageHistoryTotal] = useState(0);
@@ -585,21 +589,8 @@ export function useTKMindChat(
}
}, [canUseProjectMemory]);
const formatUserMemoryNotice = useCallback((result: {
analyzed: number;
memories: number;
totalMemories: number;
syncedToSession: boolean;
}) => {
const fragments = [];
if (result.memories > 0) {
fragments.push(`新增 ${result.memories} 条长期记忆`);
} else if (result.analyzed > 0) {
fragments.push('已分析最近对话,暂无新的长期记忆');
} else {
fragments.push('没有新的用户对话可提炼');
}
fragments.push(`当前累计 ${result.totalMemories}`);
const formatUserMemoryNotice = useCallback((result: Parameters<typeof formatUserMemorySyncNotice>[0]) => {
const fragments = [formatUserMemorySyncNotice(result)];
if (result.syncedToSession) {
fragments.push('已同步到当前会话');
}
@@ -1022,6 +1013,30 @@ export function useTKMindChat(
}
void (async () => {
await syncSessionMessages(sessionId);
if (canUseLongTermMemory) {
try {
const hint = await fetchMemoryRecallHint(sessionId);
const memoryNotice = formatMemoryHintNotice(hint);
if (memoryNotice) setNotice(memoryNotice);
const recallBadge = buildMemoryRecallBadge(hint);
if (recallBadge) {
const currentMessages = messagesRef.current;
const lastAssistantIndex = currentMessages.findLastIndex(
(message) => message.role === 'assistant',
);
if (lastAssistantIndex >= 0) {
const target = currentMessages[lastAssistantIndex];
const recallKey = resolveMessageRecallKey(target, lastAssistantIndex);
setMemoryRecallByMessageId((prev) => ({
...prev,
[recallKey]: recallBadge,
}));
}
}
} catch {
// Memory hint is best-effort UX feedback.
}
}
const recentContext = messagesRef.current
.filter((message) => getDisplayText(message).trim())
.slice(-6)
@@ -1172,6 +1187,7 @@ export function useTKMindChat(
setMessageHistoryTotal(0);
setMessageHistoryLoadingMore(false);
setMessages([]);
setMemoryRecallByMessageId({});
setChatState('connecting');
}, [clearActiveRequestMissingTimer]);
@@ -1886,6 +1902,7 @@ export function useTKMindChat(
setMessageHistoryTotal(0);
setMessageHistoryLoadingMore(false);
setMessages([]);
setMemoryRecallByMessageId({});
setSession(null);
setError(null);
setPendingTool(null);
@@ -2042,6 +2059,7 @@ export function useTKMindChat(
sessionsHasMore,
sessionSearchQuery,
messages,
memoryRecallByMessageId,
messageHistoryLoadingMore,
messageHistoryHasMore,
messageHistoryTotal,