fix: portal stability, session list DB fallback, and UX polish

- Free stale Memind listeners on 8081 before startup and exit cleanly on EADDRINUSE
- Backfill owned sessions missing from Goose via h5_conversation_messages summaries
- Strip Memind task orchestration prefixes from user-facing chat text
- Repair missing public docx links before release MindSpace link checks

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-05 11:26:34 +08:00
parent bdeab23b83
commit 00a00a1f69
9 changed files with 278 additions and 44 deletions
+53 -6
View File
@@ -901,16 +901,41 @@ export function createTkmindProxy({
// For sessions still carrying a default name, fill in a title derived from the
// first user message (kept in the snapshot cache) so the history list shows a
// meaningful label instead of "会话 <id>". Best-effort: never blocks the list.
async function enrichSessionHistory(sessions) {
if (!sessionSnapshotService?.isEnabled?.()) return;
async function enrichSessionHistory(sessions, userId) {
const needsSummary = sessions.filter(
(session) => hasDefaultSessionName(session) || Number(session?.message_count ?? 0) === 0,
);
if (needsSummary.length === 0) return;
if (sessionSnapshotService?.isEnabled?.()) {
try {
const summaries = await sessionSnapshotService.getHistorySummaries(needsSummary.map((s) => s.id));
for (const session of needsSummary) {
const summary = summaries.get(session.id);
if (!summary) continue;
if (summary.title && hasDefaultSessionName(session)) {
session.name = summary.title;
}
if (Number(session?.message_count ?? 0) === 0 && Number(summary.messageCount ?? 0) > 0) {
session.message_count = Number(summary.messageCount);
}
}
} catch {
// Non-fatal: fall back to DB summaries below.
}
}
const stillNeedsSummary = needsSummary.filter(
(session) => hasDefaultSessionName(session) || Number(session?.message_count ?? 0) === 0,
);
if (stillNeedsSummary.length === 0 || !conversationMemoryService?.loadSessionListSummaries) return;
try {
const summaries = await sessionSnapshotService.getHistorySummaries(needsSummary.map((s) => s.id));
for (const session of needsSummary) {
const summary = summaries.get(session.id);
const dbSummaries = await conversationMemoryService.loadSessionListSummaries(
userId,
stillNeedsSummary.map((session) => session.id),
);
for (const session of stillNeedsSummary) {
const summary = dbSummaries.get(session.id);
if (!summary) continue;
if (summary.title && hasDefaultSessionName(session)) {
session.name = summary.title;
@@ -918,6 +943,9 @@ export function createTkmindProxy({
if (Number(session?.message_count ?? 0) === 0 && Number(summary.messageCount ?? 0) > 0) {
session.message_count = Number(summary.messageCount);
}
if (!session.updated_at && summary.updatedAt) {
session.updated_at = summary.updatedAt;
}
}
} catch {
// Non-fatal: fall back to the client-side label.
@@ -1627,8 +1655,27 @@ export function createTkmindProxy({
}
}
const stillMissingFromGoose = [...owned].filter((sessionId) => !sessionsById.has(sessionId));
if (stillMissingFromGoose.length > 0 && conversationMemoryService?.loadSessionListSummaries) {
const dbSummaries = await conversationMemoryService
.loadSessionListSummaries(req.currentUser.id, stillMissingFromGoose)
.catch(() => new Map());
for (const sessionId of stillMissingFromGoose) {
const summary = dbSummaries.get(sessionId);
if (!summary) continue;
sessionsById.set(sessionId, {
id: sessionId,
name: summary.title || sessionId,
message_count: Number(summary.messageCount ?? 0),
updated_at: summary.updatedAt,
user_set_name: false,
recipe: null,
});
}
}
const sessions = [...sessionsById.values()].sort(sortSessionsByRecent);
await enrichSessionHistory(sessions);
await enrichSessionHistory(sessions, req.currentUser.id);
if (typeof userAuth.getSessionOrigins === 'function' && sessions.length > 0) {
try {
const origins = await userAuth.getSessionOrigins(sessions.map((s) => s.id));