From c7d6f427f80d060d98a01eca57891ac2d3df4ab1 Mon Sep 17 00:00:00 2001 From: teahubxyz Date: Tue, 20 Oct 2026 05:37:15 +0000 Subject: [PATCH] havre: a photo or clip they sent is told to the model in their own turn, as a stage direction, not in yours (characters took "you sent me a photo" as yours and got confused); a reply that narrates sending a photo makes one likely Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01VAvUH72XsdCitHdh3t7mjr --- ventures/havre/abilities.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/ventures/havre/abilities.js b/ventures/havre/abilities.js index 9af7882..443a385 100644 --- a/ventures/havre/abilities.js +++ b/ventures/havre/abilities.js @@ -270,25 +270,27 @@ export function clipOptions(model, last = {}) { /** * A conversation with pictures and voice notes in it, as plain messages for a text model. A photo or clip the - * character sent is mentioned in the next thing the person says ("(You sent me a photo: …)"), so the model knows - * about it without learning to write "[photo]" itself. Your photo is "[a photo: what it shows]", your voice note is - * the words you said. + * character sent is in their own turn, as a stage direction ("*sends a photo: …*"), the way characters narrate what + * they do, so the model knows what it sent and who sent it (told in the person's turn, "you sent me a photo" read as + * the person's, and characters got confused about whose photo it was). Your photo is "[a photo: what it shows]", your + * voice note is the words you said. */ export function apiHistory(history) { const out = []; - let notes = []; for (const m of history || []) { if (!m || m.demo) continue; const media = m.media || null; if (m.role === "assistant") { if (media && (media.kind === "photo" || media.kind === "clip")) { const shot = clip(str(media.shot).trim(), 300).replace(/[.!?…\s]+$/, ""); - notes.push(`(You sent me ${media.kind === "clip" ? "a short video" : "a photo"}${shot ? ": " + shot : ""}.)`); + const sent = `*sends ${media.kind === "clip" ? "a short video" : "a photo"}${shot ? ": " + shot : ""}*`; + const last = out.at(-1); + if (last && last.role === "assistant") last.content += "\n\n" + sent; // with the words it came after + else out.push({ role: "assistant", content: sent }); } else if (str(m.content).trim()) out.push({ role: "assistant", content: str(m.content) }); } else if (m.role === "user") { let content = str(m.content); if (media && media.kind === "photo-in") content = `[a photo${media.saw ? ": " + media.saw : ""}]` + (content ? " " + content : ""); - if (notes.length) { content = notes.join(" ") + (content ? "\n\n" + content : ""); notes = []; } out.push({ role: "user", content, ...(m.hidden ? { hidden: true } : {}) }); } } @@ -357,7 +359,7 @@ export function normalizeMove(o) { // words that make a move likely: asking to see or hear them, or a reply about where they are and what they're doing const ASKS = /\b(pics?|picture|photo|selfie|snap|show me|see you|see it|what (?:do )?you look like|look like|wearing|outfit|send (?:me )?(?:a|one|it)|video|clip|film|hear you|your voice|say it|sing|whisper|out loud|aloud)\b/i; -const SHOWS = /\b(look at (?:this|my|the)|wearing|outfit|dress|view from|sunset|sunrise|my (?:room|place|desk|garden|kitchen|car|boat|bed|window)|just got (?:back|a|my)|i'?m (?:at|in|on) the|picture|photo|selfie|whisper)\b/i; +const SHOWS = /\b(sends? (?:you )?(?:a |another )?(?:photo|pic|selfie|snap)|look at (?:this|my|the)|wearing|outfit|dress|view from|sunset|sunrise|my (?:room|place|desk|garden|kitchen|car|boat|bed|window)|just got (?:back|a|my)|i'?m (?:at|in|on) the|picture|photo|selfie|whisper)\b/i; /** * Whether to think about a move after this reply: when the person asked to see or hear them, when the reply talks