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>
This commit is contained in:
parent
848b517d22
commit
ba200d54f4
@ -36,7 +36,30 @@ export default defineEventHandler(async (event) => {
|
||||
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: session.content as Array<{ role: string; content: string }>,
|
||||
messages: raw.filter((m) => !isInternal(m)),
|
||||
};
|
||||
});
|
||||
|
||||
@ -460,11 +460,17 @@ export default defineEventHandler(async (event) => {
|
||||
const user = await requireUser(event);
|
||||
|
||||
const body = await readBody(event);
|
||||
// sosMode ist deprecated — Coach-Page sendet es nicht mehr.
|
||||
// Wird hier nur noch für Logging akzeptiert, beeinflusst kein Routing.
|
||||
const { messages, locale } = body as {
|
||||
// sosMode: die SOS-Page (urge.tsx) nutzt diesen Endpoint als zustandslosen
|
||||
// LLM-Proxy für Game-Kommentar / Share-Draft / Stream-Fallback. Diese Calls
|
||||
// tragen SOS_BOOT + [INTERN:]-Prompts in der messages-Array und DÜRFEN NICHT
|
||||
// in die Coach-Chat-History (coachSession) geschrieben werden — sonst tauchen
|
||||
// SOS-Reste (z.B. "[INTERN: …Snake…PB 13…]") als Bubbles im Coach-Tab auf.
|
||||
// SOS und Coaching sind strikt getrennt: bei sosMode === true keine
|
||||
// Persistenz, keine Memory-Extraction, keine Feedback-Detection.
|
||||
const { messages, locale, sosMode } = body as {
|
||||
messages: Array<{ role: "user" | "assistant"; content: string }>;
|
||||
locale?: string;
|
||||
sosMode?: boolean;
|
||||
};
|
||||
|
||||
if (!messages || !Array.isArray(messages)) {
|
||||
@ -705,9 +711,12 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
// Feedback-Detection + LLM parallel starten
|
||||
// (lastUserMsg ist bereits oben für Sprach-Detection berechnet)
|
||||
const feedbackPromise = lastUserMsg?.content
|
||||
? detectAndSaveFeedback(lastUserMsg.content, user.id, config)
|
||||
: Promise.resolve(false);
|
||||
// Im sosMode ist lastUserMsg ein [INTERN:]-Prompt, kein echtes User-Feedback
|
||||
// → Detection überspringen.
|
||||
const feedbackPromise =
|
||||
!sosMode && lastUserMsg?.content
|
||||
? detectAndSaveFeedback(lastUserMsg.content, user.id, config)
|
||||
: Promise.resolve(false);
|
||||
|
||||
let text: string | null = null;
|
||||
let usedModel: string | null = null;
|
||||
@ -736,11 +745,13 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
const feedbackSaved = await feedbackPromise;
|
||||
|
||||
// Memory: markReferenced + Extraction fire-and-forget
|
||||
// Memory: markReferenced + Extraction fire-and-forget.
|
||||
// sosMode überspringt Extraction — die messages-Array enthält SOS_BOOT +
|
||||
// [INTERN:]-Prompts, daraus würden nur Müll-Memories entstehen.
|
||||
if (loadedMemoryIds.length > 0) {
|
||||
markReferenced(loadedMemoryIds).catch(() => {});
|
||||
}
|
||||
if (text) {
|
||||
if (text && !sosMode) {
|
||||
const allMessages = [
|
||||
...messages,
|
||||
{ role: "assistant" as const, content: text },
|
||||
@ -751,10 +762,12 @@ export default defineEventHandler(async (event) => {
|
||||
);
|
||||
}
|
||||
|
||||
// Chat-Verlauf für Pro/Legend in DB speichern
|
||||
// Chat-Verlauf für Pro/Legend in DB speichern.
|
||||
// sosMode NICHT persistieren — SOS und Coach-Chat sind strikt getrennt;
|
||||
// SOS-Reste dürfen nie in der Coach-History (coachSession) landen.
|
||||
// `plan` ist bereits oben deklariert (LLM-Routing-Block)
|
||||
console.log("[coach/message] plan:", plan, "userId:", user.id);
|
||||
if (plan === "pro" || plan === "legend") {
|
||||
console.log("[coach/message] plan:", plan, "userId:", user.id, "sosMode:", !!sosMode);
|
||||
if (!sosMode && (plan === "pro" || plan === "legend")) {
|
||||
const fullHistory = [
|
||||
...messages,
|
||||
{ role: "assistant" as const, content: text },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user