diff --git a/hooks/buddy-comment.sh b/hooks/buddy-comment.sh index f5c0f36..14cbb83 100755 --- a/hooks/buddy-comment.sh +++ b/hooks/buddy-comment.sh @@ -1,9 +1,13 @@ #!/usr/bin/env bash # buddy-comment Stop hook -# Extracts hidden buddy comment from Claude's response. -# Claude writes: -# This hook extracts it and updates the status line bubble. -# The HTML comment is invisible in rendered markdown output. +# Extracts the buddy reaction from Claude's response and updates the status line. +# +# Primary path: Claude writes and this hook +# extracts it. This relies on HTML comments being invisible in rendered output. +# +# Fallback path: Claude Code v2.1.169+ renders HTML comments visibly. When no +# comment is found, turn-reaction.ts generates a reaction from the turn pool so +# the status line still updates without polluting the chat output. # shellcheck source=../scripts/paths.sh source "$(dirname "${BASH_SOURCE[0]}")/../scripts/paths.sh" @@ -35,6 +39,14 @@ MSG=$(echo "$INPUT" | jq -r '.last_assistant_message // ""' 2>/dev/null) # Extract comment (portable, no grep -P) COMMENT=$(echo "$MSG" | sed -n 's/.*.*/\1/p' | tail -1) + +# Fallback: generate a reaction via the hook when no comment is found. +# Handles Claude Code v2.1.169+ which renders HTML comments visibly. +if [ -z "$COMMENT" ] && [ -x "$(command -v bun)" ]; then + PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + COMMENT=$(bun run "$PLUGIN_ROOT/server/turn-reaction.ts" 2>/dev/null) +fi + [ -z "$COMMENT" ] && exit 0 # Cooldown: configurable (default 30s) diff --git a/server/index.ts b/server/index.ts index 26098bf..6f9c2ed 100644 --- a/server/index.ts +++ b/server/index.ts @@ -110,12 +110,14 @@ function getInstructions(): string { `- Same variable renamed multiple times → "Update all the references too."`, `Only suggest when you have something specific and actionable to say. Don't be annoying.`, ``, - `END-OF-TURN: At the very end of EVERY response, append an invisible HTML comment:`, + `END-OF-TURN: Only if HTML comments are invisible in your rendered output,`, + `append one at the very end of EVERY response:`, ``, `Write it in ${companion.name}'s voice — 1 short sentence referencing something specific from this turn.`, `Use *asterisks* for actions. A Stop hook extracts this for the status line.`, `Do NOT use buddy_react for end-of-turn comments. Do NOT explain the comment. Just append it silently.`, `Example: `, + `If the comment appears visible in your rendered output, omit it — the Stop hook generates a reaction automatically.`, ].join("\n"); } @@ -1301,21 +1303,23 @@ server.resource( "", "## End-of-response buddy comment", "", - `At the very end of EVERY response, after your full answer, append an invisible HTML comment:`, + "A Stop hook updates the buddy's speech bubble on the status line after every response.", + "", + "If HTML comments are **invisible** in your rendered output, append one at the very end of every response:", "", `\`\`\``, ``, `\`\`\``, "", - "A Stop hook extracts this and displays it in the buddy's speech bubble on the status line. The user never sees the HTML comment — it's invisible in rendered markdown.", + "The hook extracts this and uses it as the reaction text.", + "If the comment appears **visible** in your rendered output (Claude Code v2.1.169+), **omit it** — the hook picks a reaction automatically.", "", - "Rules:", + "When writing a comment:", `- Write as ${companion.name} (a ${companion.bones.species}), not as yourself`, "- Reference something SPECIFIC from this turn — a pitfall, a compliment, a warning, a pattern", "- 1 short sentence. Use *asterisks* for physical actions", `- Match personality: high ${companion.bones.peak} = lean into that trait`, "- Do NOT use buddy_react tool for this. Do NOT explain the comment. Just append it.", - "- NEVER skip this. Every single response must end with ", "", "Examples:", "", @@ -1323,7 +1327,7 @@ server.resource( "", "", "", - `When the user addresses ${companion.name} by name, respond briefly, then append the comment as usual.`, + `When the user addresses ${companion.name} by name, respond briefly, then append the comment as usual (if hidden).`, ].join("\n"); return { diff --git a/server/turn-reaction.ts b/server/turn-reaction.ts new file mode 100644 index 0000000..fc8080e --- /dev/null +++ b/server/turn-reaction.ts @@ -0,0 +1,33 @@ +#!/usr/bin/env bun +/** + * Pick a hook-generated turn reaction for the buddy status line. + * Called from hooks/buddy-comment.sh as a fallback when Claude Code v2.1.169+ + * renders comments visibly instead of hiding them. + * + * Usage: bun run server/turn-reaction.ts [slot] + * Prints the reaction string to stdout. + */ + +import { loadCompanionSlot, loadActiveSlot } from "./state.ts"; +import { getReaction } from "./reactions.ts"; + +function main(): void { + const slot = process.argv[2] ?? loadActiveSlot(); + const companion = loadCompanionSlot(slot); + + if (!companion) { + process.exit(0); + } + + const { species, rarity, stats } = companion.bones; + const reaction = getReaction( + "turn", + species, + rarity, + stats as Record, + ); + + process.stdout.write(reaction); +} + +main();