chahinebrini ba200d54f4 fix(coach): keep SOS out of Coach chat history
SOS (urge.tsx) uses /api/coach/message as a stateless LLM proxy for game
comments, share drafts and the stream fallback — sending SOS_BOOT +
[INTERN:] prompts. The endpoint persisted the full messages array into
coachSession for pro/legend users, so those internal prompts and the raw
JSON replies leaked into the Coach chat history as visible bubbles.

- Reactivate the sosMode flag (already sent by all three SOS call-sites):
  when set, the endpoint skips coachSession persistence, memory extraction
  and feedback detection — pure LLM proxy, no shared state.
- Add a defensive filter on /api/coach/history that strips internal
  messages (SOS_BOOT, [INTERN:], [SYSTEM-HINT], raw JSON / [[CHIPS]]
  replies) so already-contaminated sessions self-heal on next load.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:45:38 +02:00

66 lines
1.9 KiB
TypeScript

import { usePrisma } from "../../utils/prisma";
/**
* GET /api/coach/history
* Lädt den gespeicherten Chat-Verlauf (nur Pro/Legend).
* Gibt das neueste CoachSession-Dokument zurück.
*/
export default defineEventHandler(async (event) => {
const user = await requireUser(event);
console.log("[coach/history] userId:", user.id);
const db = usePrisma();
const profile = await db.profile.findUnique({
where: { id: user.id },
select: { plan: true },
});
console.log("[coach/history] plan:", profile?.plan);
if (!profile || !["pro", "legend"].includes(profile.plan)) {
return { messages: [] };
}
const session = await db.coachSession.findFirst({
where: { userId: user.id },
orderBy: { createdAt: "desc" },
});
console.log(
"[coach/history] session found:",
!!session,
"msgs:",
Array.isArray(session?.content) ? (session.content as any[]).length : 0,
);
if (!session) {
return { messages: [] };
}
// Defensiver Filter: Alt-Sessions können vor dem sosMode-Fix mit SOS-Resten
// kontaminiert sein (SOS_BOOT, [INTERN:]/[SYSTEM-HINT]-Prompts, rohe
// JSON-/[[CHIPS]]-Antworten). Solche Nachrichten gehören NIE in den Coach-Chat
// → beim Laden rausfiltern, damit bestehende Verläufe automatisch heilen.
const raw =
(session.content as Array<{ role: string; content: string }>) ?? [];
const isInternal = (m: { role: string; content: string }) => {
const c = (m?.content ?? "").trimStart();
if (
c.startsWith("[SOS-MODUS") ||
c.startsWith("[INTERN:") ||
c.startsWith("[SYSTEM-HINT]")
) {
return true;
}
// Rohe Lyra-JSON-Antwort (Game-Kommentar / Share-Draft) oder Chips-Marker
if (m?.role === "assistant") {
if (c.includes("[[CHIPS]]:")) return true;
if (/^\{[\s\S]*"message"\s*:/.test(c)) return true;
}
return false;
};
return {
messages: raw.filter((m) => !isInternal(m)),
};
});