Companion to cinny's `concurrency: cancel-in-progress`. The poll loop latched
origin/lotus once at startup; with CI cancel-in-progress a superseded run's
status flips to error/failure, so polling the latched (now-cancelled) SHA would
abort and — with the flock skipping the newer push's deploy — strand the newest
commit undeployed (a deploy freeze, the exact class this script fought before).
- Re-resolve origin/lotus each poll iteration; retarget the CI gate to HEAD if
it advanced (fresh MAX_WAIT window).
- On a failure/error status, re-check HEAD before aborting — only a genuine
failure of the CURRENT HEAD aborts; a superseded/blip SHA is followed instead.
- Reset to the gated $COMMIT_SHA (not a bare origin/lotus that may have advanced
past the gate after the loop) so we build exactly what passed CI.
shellcheck clean; reviewed (SHIP).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the translation feature to the landing page (Newest July 2026
prose + comparison-table row) and the README custom-features table.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The production config.json (cp'd from cinny/config.json by deploy/lxc106-cinny.sh)
lost its "gifApiKey": "" placeholder, so lotus_deploy.sh's sed injection had no
field to fill -> the GIF composer button disappeared (it renders only when
gifApiKey is non-empty). Restore the placeholder so future deploys inject the key.
Also redact the real Giphy key that was committed in the README (it belongs only in
/etc/lotus-deploy.env on LXC 106). NOTE: the key remains in git history (commit
f3a7bcd) — it should be rotated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- README Custom Features: 'Inline media embeds' row (16 providers, facade, CSP
note incl. the live LXC 106 frame-src allowlist vs the repo wildcard fallback).
- landing: add inline media embeds to the July 2026 feature list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds Strict-Transport-Security (2y, includeSubDomains, preload) and a
Permissions-Policy that allows only the features the app uses (camera/mic/
display-capture for calls, geolocation for location share, autoplay/fullscreen/
encrypted-media) and denies the rest. Complements the existing X-Frame/CSP/
Referrer headers.
Apply: reload nginx on the LXC. TLS terminates upstream (listen 80), so verify
the header reaches the browser (front proxy must pass it through) — else set
HSTS at the TLS terminator. Verify a call + location share still work.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shellcheck directives bind to the NEXT command; on the compound line
`set -a; source /etc/lotus-deploy.env; set +a` the existing
`# shellcheck disable=SC1091` bound to `set -a`, so the info-level SC1091
finding on the runtime-only env file still failed the lint workflow
(find -exec shellcheck exits non-zero on any finding). Split the line so the
directive sits directly above `source` (as `source=/dev/null`, the standard
idiom for host-only env files). Verified with CI's exact invocation:
`find . -name "*.sh" -exec shellcheck {} +` now exits 0 (shellcheck 0.9.0).
No runtime behavior change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- landing: Threads row upgraded to ✓ (full side panel + unread chips); prose
sentence for the new batch (threads, KaTeX math, opt-in encrypted-search
index, session hardening, crypto diagnostics).
- README: two new rows in the Lotus Cinny custom-features table.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- README Custom Features table: add rows for the in-call soundboard (P5-15),
call quality controls, and room call-permissions (P5-31).
- landing: mention the soundboard, per-user quality controls, and
server-enforced room call-permissions in the feature blurb.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update the client-comparison copy to reflect that calls run our self-built
Element Call fork and that the ML noise-suppression tier offers RNNoise, Speex,
DTLN, and DeepFilterNet 3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend voice-limit-guard to enforce a per-room publish-source policy
(io.lotus.room_quality allow_screenshare/allow_camera) for ALL Matrix clients,
alongside the existing participant limit.
- At token issue, re-sign the LiveKit JWT's canPublishSources to drop forbidden
sources (microphone always kept). Verifies our own secret signed the token
first and fails open on mismatch, so a secret drift can never mint a token the
SFU rejects. Limit check and source policy are independent (one's outage can't
skip the other).
- Live (mid-call) enforcement: a background reconcile loop calls LiveKit
UpdateParticipant to revoke a forbidden source from participants who joined
before the policy changed -- which unpublishes their in-progress
screenshare/camera server-side within ~3s and blocks re-publish. Only removes
sources (never grants), preserves other permission flags, fails open, and runs
as a daemon thread that cannot crash or block token issuance.
- Endpoint-specific room-id extraction (/get_token->room_id, /sfu/get->room) so
a client sending both keys can't get a different room's policy applied.
- Auto-deploy the guard on LXC 151 (py_compile-gated, backup + rollback).
- Unit tests: JWT re-sign/verify + tamper, secret-mismatch, source narrowing,
reconcile (never-grant / preserve-flags / disable-on-empty), fail-open.
Numeric bitrate/fps caps are NOT server-enforceable on an SFU (LiveKit forwards,
never transcodes) and remain a Lotus-client-cooperative setting; the
screenshare/camera permission is the hard cross-client lever.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cinny/lotus-deploy.sh (hyphen) force-deploy variant was an untracked,
redundant duplicate of the CI-gated cinny/lotus_deploy.sh (underscore) added in
c13549f. It's been removed, so its install block here referenced a file that no
longer exists. The CI-gated lotus_deploy.sh is the single source of truth for
the webhook web deploy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The live /usr/local/bin/lotus_deploy.sh (the `lotus-deploy` webhook target) was
never under version control and had rotted into two deploy-killing bugs that
froze chat.lotusguild.org on an old build:
1. CI gate: it waited on the WHOLE workflow run with a 15-min cap. Web CI shares
the single act_runner with the slow Tauri desktop builds, so a web run could
sit queued >15 min -> "result: timeout" -> deploy aborted. Now it gates only
on the "Build & Quality Checks" commit-status context (build + unit tests),
decoupled from "Trigger Desktop Build", and waits up to 45 min.
2. Dead element-call copy: `cp node_modules/@element-hq/element-call-embedded/...`
under `set -e` aborted every deploy after the widget was forked to
@lotusguild/element-call-embedded. The build already emits dist/public/
element-call; replaced the copy with a presence check.
Also: rsync now excludes config.json so the app deploy stops clobbering the
production runtime config (homeserver list / allowCustomHomeservers) that the
matrix repo owns. lxc106-cinny.sh now installs this script (syntax-checked).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Now that the client supports MSC3861 OIDC login, add mozilla.org to the
homeserverList and its origins to the CSP. mozilla delegates: homeserver ->
mozilla.modular.im, OIDC issuer -> chat.mozilla.org, identity -> vector.im.
- connect-src += mozilla.org mozilla.modular.im chat.mozilla.org vector.im
- img-src += mozilla.org mozilla.modular.im
Applied live to LXC 106 and synced here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Federated matrix.org users load avatars/images from their own media endpoint
(matrix-client.matrix.org), which img-src still blocked — so every avatar
tripped a CSP violation. Add https://matrix.org + https://*.matrix.org to
img-src to match connect-src. (media-src already allows https: so video/audio
were fine.) Applied live to LXC 106 and synced here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The chat.lotusguild.org nginx config (LXC 106) was edited directly on the box
and never tracked — which is how its CSP drifted (kept a dead Sentry URL and
blocked matrix.org logins). Snapshot it as cinny/nginx.conf (verbatim from prod,
incl. the corrected connect-src that now allows matrix.org/*.matrix.org) and
deploy it via lxc106-cinny.sh: back up the live file, swap, `nginx -t`, and
reload only on success (auto-restore the backup if validation fails, so a bad
config can't take the site down). TLS terminates at the NPM proxy, so this is a
plain HTTP server block with no secrets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add matrix.org to homeserverList so federated friends with matrix.org accounts
can sign into chat.lotusguild.org. defaultHomeserver stays 0 (lotusguild), and
allowCustomHomeservers stays false — only the two listed servers are selectable,
so the client isn't opened up to arbitrary homeservers.
Deploys via lxc106-cinny.sh (cp -> /var/www/html/config.json); lotus-build.sh
preserves the live config across app rebuilds, so this is the authoritative copy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tag the EC embed row and add a callout explaining the plan to fork
element-hq/element-call and self-build it for true ownership (decorations,
focus/screenshare, reconnect mic, theming, call-audio injection — all unfixable
against the prebuilt @element-hq/element-call-embedded bundle). Infra notes:
EC uses our LiveKit SFU (livekit/, LXC 151) + lk-jwt-service; a new build/deploy
pipeline will be needed. Full plan: LotusGuild/cinny → HANDOFF_ELEMENT_CALL_FORK.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- README: correct embedded Element Call version 0.19.3 -> 0.19.4 in the
Custom Features and Tech Stack tables
- landing/index.html: add a "Noise suppression" row to the Voice & Video
comparison table (Lotus = 3 tiers incl. on-device RNNoise ML) and note
the feature in the June 2026 narrative
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
99 curated APNG overlay frames stored in user Matrix profile (MSC4133),
visible to other Lotus Chat users in real time across timeline, members
list, and @mention autocomplete. Includes the Lotus Flower decoration.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a fail-open Python sidecar (livekit/voice-limit-guard.py) that fronts
lk-jwt-service to enforce per-room voice participant caps for ALL Matrix
clients, not just Lotus Chat:
- lk-jwt-service moved to :8071 (systemd drop-in), guard owns :8070 so NPM's
existing /sfu/get + /get_token proxy targets are unchanged
- guard reads io.lotus.voice_limit.max_users (Synapse admin API, cached),
forwards to lk-jwt-service, and on an issued token decodes the LiveKit alias
+ requester, counts distinct Matrix users via LiveKit ListParticipants, and
returns 403 when the room is full (rejoins/extra devices allowed)
- any error fails open (returns upstream response) so calls never break
- systemd/voice-limit-guard.service; README documents ports, setup, revert
Also update landing page: voice limit is now server-enforced for all clients.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Feature description paragraph: added AFK auto-mute (1–30 min voice idle
timeout) and knock-to-join admin badge (live count on Members button)
- Comparison table: new AFK auto-mute row in Voice & Video section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New table row for 5 animated CSS wallpapers (rain, stars, grid pulse,
aurora, fireflies). Feature blurb updated to mention the animated
backgrounds and the glassmorphism body-background fix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds inline GIF preview and collapsible long messages rows to the
feature comparison table; extends the June 2026 feature list with all
five newly completed items (P3-5, P3-9, P5-19, P5-23, P5-26).
Includes pre-staged README additions for presence tracking, encrypted
search, privacy settings, draft persistence, and PiP persistence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Update comparison table date to June 2026
- Add push-to-deafen (M key), night light filter, message length
counter, and TDS orange typing dots to also-available paragraph
- PTT row notes M = push-to-deafen
- Add Night Light row to UX & Extras comparison table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
README.md:
- Replaced the stale 'Auto-revert spotlight on screenshare' entry
(that 600ms revert-to-grid code was removed — it caused fullscreen
to show avatars instead of the screenshare)
- Added accurate entries for all four features added this cycle:
Screenshare fullscreen, PiP screenshare focus, Screenshare audio
mute, Custom status message
landing/index.html:
- Updated Lotus Fork feature description paragraph to mention
screenshare fullscreen, screenshare audio mute, PTT, and custom
status messages
- Added PTT row to Voice & Video comparison table
- Updated Screenshare row for Lotus Chat to note fullscreen + audio mute
- Added 'Custom status message' row to UX & Extras section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LXC 139 NPM proxy host 49 now proxies both /sfu/get and /get_token
to lk-jwt-service (port 8070). Note that re-saving via NPM UI will
overwrite the conf and require re-adding the location blocks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Document the new member list presence indicators and per-member device
sessions panel with per-device SAS verification in both the landing
page feature list and the README custom features table.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The runner intermittently times out (5m) downloading the gitleaks binary
from GitHub. Add --retry 3 --retry-delay 5 --max-time 120 so transient
network blips don't fail the job.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add per-message read receipts comparison row (8-col, including new official
Cinny column from dc8f588). Update Lotus Chat feature description to include
per-message read receipt avatars and chat wallpaper in calls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OOM observed during rendering-chunks phase at 896MB and 3072MB.
6144MB heap with 8GB LXC memory is confirmed working.
Also update README rebuild command to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add !cancel command (anyone cancels own blackjack; PL50+ clears all room games)
- Add !wordlestats top-level command (wraps wordle stats function)
- Add !cleanwelcome admin command to purge stale welcome DM records
- !help now hides management section from sub-PL50 users, hides !health from non-admins
- !announce uses nio room cache for join_rule instead of an API call per room
- Fix _INVITEALL_BLOCKED comment (Commands is knock-gated, not restricted)
- welcome.py: skip duplicate DM if a pending welcome already exists for the user
- welcome.py: add clean_stale_dm_messages() helper
- welcome.py: replace no-op post_welcome_message with log_ready()
- bot.py: update import/call to match welcome.py rename
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Commands has join_rule=restricted so the join-rule filter didn't catch
it. Added _INVITEALL_BLOCKED set with the Commands room ID — any room
in that set is skipped regardless of join rule. Invite-only rooms
(Management, Cool Kids, Spam and Stuff) are still excluded by the
existing join_rule check.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
!setpl now iterates every Space room via the hierarchy API and updates
the power_levels state event in each. Setting a user to the room's
users_default cleans up the explicit entry rather than leaving a
stale PL0. Rooms where the bot lacks permission are counted and
reported but don't block the rest.
!inviteall skips rooms with join_rule=invite (Management, Cool Kids,
Spam and Stuff) — only public/restricted rooms get the invite. Also
skips rooms where the target is already a member.
_get_space_room_ids() fetches the Space child list via the v1 hierarchy
API with pagination support.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Five new commands, all gated behind is_elevated() (power level >= 50):
!mkroom <name> — clone #general's power levels, join rules, encryption,
history visibility, and avatar into a fresh v12 room,
auto-adds it to the Lotus Guild Space, and invites
the caller.
!roominfo — show room display name, ID, member count, join rule,
encryption status, and all users with PL > 0.
!topic [text] — set or clear the current room's topic.
!invite @user — invite any Matrix user to the current room.
!setpl @user <n> — update a user's power level (0-100); cannot exceed
the caller's own level.
Also adds urllib.parse.quote and MATRIX_HOMESERVER to imports, and
adds a "Management (PL50+)" section to !help.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add is_elevated() helper that reads the room power level from the nio
client store. Users at PL50+ (Nerdy Council and above) skip the cooldown
check entirely. The timestamp is still recorded so cooldown applies
if their power level is later reduced below 50.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each player now gets exactly one attempt per question. If their guess
is wrong it's recorded and they're locked out for that round. If both
players have guessed wrong the correct answer is revealed and the game
moves to the next question immediately (no need to wait for the 45s
timeout).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of a raw 6-word slice (which left dangling fragments like
'Partly - Not exclusively American; has'), extract the first complete
sentence (up to 10 words). Falls back to the 6-word cap only if no
sentence boundary is found in the response.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reduce word cap from 12 to 6 and add explicit instructions not to use
proper nouns, brand names, or place names in answers. Fixes the case
where the model blurted 'The Grand Canyon State (Arizona)' in response
to a geography question.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a rolling cache of the last 30 answers (persisted to
twentyq_cache.json) and pass the recent list to the model as an
explicit avoid clause. Also prompt the model to vary categories
each round. If the model still returns a cached answer it is
rejected and one retry is attempted automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allow open-ended questions like "What color is it?" or "How big is it?"
The model now gives brief descriptive answers (capped at 12 words) for
open questions while still answering Yes/No/Sometimes/Partly for binary
ones. Updated command descriptions and in-game prompts accordingly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Import MATRIX_USER_ID in commands.py (was missing — caused !ttt and
!triviaduel to crash with NameError on every invocation)
- Blackjack is now per-player per-room: multiple players can each run
their own game simultaneously; !hit and !stand operate on the caller's
own game only
- !hottake: pick a random topic from 20 categories and pass it to the
model so takes aren't all nostalgia-flavoured
- !nhie: tighter prompt with topic rotation and a word-count cap so
generated scenarios are simpler and more relatable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New commands: numguess/ng, wordchain/wc/endwc, acronym/ac,
20q/q/answer, nhie, hottake, ttt/move, blackjack/hit/stand,
triviaduel/da. All per-room with AI-generated content where
applicable. callbacks.py wired up for new reaction handlers
(acronym votes, nhie, hottake). Help and README updated with
full command reference.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The old \O/ ears row looked like 6 limbs when combined with /|\ arms.
New extended progression:
0-6: same as standard (head → body → arms → legs)
7: left foot (/ in the previously empty row below legs)
8: both feet (/ \)
9: @ head (anguish — full figure visible)
10: X head (dead)
Each stage is visually distinct with no overlapping limb confusion.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-r requirements.txt causes pip-audit to spawn an internal venv which
calls ensurepip, failing with exit 127 on the standalone Python build.
--local avoids the venv. CVE-2026-3219 is in pip itself (not our deps)
so we ignore it explicitly with --ignore-vuln.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- !help Games section now includes !guess
- !8ball description mentions --debug flag
- pip-audit now scans only requirements.txt instead of --local (which
was flagging CVE-2026-3219 in pip itself, not our dependencies)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors riddle/trivia cache pattern: loads hangman_cache.json on startup,
appends each new word, caps at 30, saves after each game. Recent words
are passed to the model prompt to avoid repeats.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Silent failures (word too long/short, has hyphens, empty hint) are now
logged as warnings showing the actual word/hint returned. Retry up to
2 times before giving up, matching riddle's behavior.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The creative model was producing fortune-cookie mysticism ("navigate
the curve of fate") instead of 8-ball answers. New system prompt
explicitly requires YES/NO/UNCERTAIN category answers, 2-6 words,
funny and direct — no cryptic prophecies.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
phi4-mini refused to argue FOR controversial topics, instead deflecting
with neutral takes. The abliterated model with a committed debater system
prompt will actually take both sides without hedging or disclaimers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added _extract_riddle_answer() with dual fallback: JSON parse first,
then regex extraction of quoted riddle/answer values directly from text
- _generate_riddle() now retries up to 2 times on parse/network failure
- Hangman, scramble, WYR, and trivia now catch JSONDecodeError and log
the raw model output instead of letting the exception propagate silently
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
riddle_cache.json: stores last 30 riddle texts + answers
trivia_cache.json: stores last 20 questions per category
Both files are capped at their respective maxes so they never grow
unboundedly. Loaded on startup, saved after each new question.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
It's used for 8ball, roasts, riddles, WYR, and debate — not just the
magic 8-ball anymore. CREATIVE_MODEL better reflects its role as the
uncensored/abliterated model for creative generation tasks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
wyr:
- Reject options that end on a dangling word (but/and/or/with/never etc.)
so truncated sentences like 'but never' return None and retry
- Add 'via Llama 3.2 3B (abliterated)' credit to the poll message
riddle:
- Add 'via Llama 3.2 3B (abliterated)' credit to the riddle message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous system prompt was basically empty. Now it explicitly:
- Requires the answer to be unambiguously correct
- Bans vague, ambiguous, or invented facts
- Requires plausible-but-wrong distractors
- Includes a concrete example of a good question
- Tells the model to pick a simpler topic if unsure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
phi4-mini is too conservative and defaults to the same 2-3 answers.
Use BALL_MODEL (abliterated Llama 3.2) like WYR does.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
riddle:
- Cache answers separately so the same answer (e.g. 'shadow') can't
appear twice in a session even if the riddle text differs
- Explicitly ban 'shadow' in the prompt and append avoid-answers clause
- Ban question endings ('what am I?', 'what could it be?') more strictly
wyr:
- Hard-cap options at 10 words server-side so the model can't ignore
the word limit and generate paragraph-length options
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 3 assistant-turn examples to lock in the JSON format and tone
- Construct the 'question' field from option_a/option_b so it's always
well-formed regardless of what the model puts in the 'question' key
- Switch from phi4-mini to the abliterated Llama 3.2 model for edgier,
uncensored dilemmas
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
riddle:
- Tighten generation prompt with explicit rules: specific noun answer,
no answer word in the riddle, no 'what could it possibly mean', clues
must logically point to ONE answer, prefer concrete things
- Fix answer matching: strip articles (a/an/the), allow partial match
so 'person' hits 'a person' and 'shadow' hits 'my shadow' etc.
wyr:
- Prompt now asks for genuinely difficult dilemmas with real downsides
on both sides; explicitly bans boring options like dolphins/karaoke
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keep a rolling list of the last 30 riddles used and inject them into
the prompt as an avoid clause, same pattern as trivia's per-category cache.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
nio has a dedicated ReactionEvent type with .reacts_to and .key attributes.
The callback was registered for UnknownEvent so reaction events were silently
dropped. Register for ReactionEvent and use its native attributes; keep the
UnknownEvent fallback for edge cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add edit_html() to utils using m.replace so messages can be updated
- Hangman board now edits in place on every guess — shows progressing
ASCII figure as wrong guesses accumulate instead of spamming new messages
- Extract _hangman_board_html() helper for consistent board rendering
- wyr: add INFO-level logging to reaction callback to diagnose vote tracking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When Jared asks about a @mentioned third party, give a neutral honest
prediction instead of hijacking the answer to be about Jared.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The oracle should address Leon ('you survived Raccoon City...') not
impersonate him ('I'm not buying it').
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
api/generate has no system role — the model was ignoring the character
context and giving generic one-word answers. Chat API with a proper
system message forces the Leon voice.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _hangman_display compared uppercase word chars against lowercase
guessed_letters set, so letters were never revealed after correct guesses
- Word guess wrong path now shows the board and remaining guesses
- Winner display now includes the guesser's name on correct word guess
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add _WYR_POLLS dict keyed by poll event_id to accumulate votes
- record_wyr_vote() called from callbacks.reaction() on every reaction
- reveal() reads actual vote counts and announces winner with percentage
- Handles tie and zero-vote cases
- Remove the useless 'check the reactions above' message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
phi4-mini can queue behind other requests and take >20s under load,
causing TimeoutError and silent failures in wyr/riddle/hangman/scramble.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
hangman, scramble, riddle, and wyr all used api/generate which has no
system role. The model would wrap JSON in prose or markdown fences,
causing json.loads() to throw and the command to silently die after
the 'Generating...' message.
Fix for all four: switch to api/chat with a system message enforcing
raw JSON output, strip markdown fences, and use regex to extract the
JSON object even if surrounded by extra text.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch to api/chat with a system prompt for better JSON compliance,
and use regex extraction to find the JSON object even if the model
wraps it in extra text or markdown fences.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch from api/generate to api/chat so we can set a system role that
instructs the model to be genuinely savage. Add a few-shot example so
it knows what a roast looks like vs a backhanded compliment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Reframe prompt as a consented comedy roast between friends so the
model doesn't refuse on safety grounds
- Add lore for lonely (Cole, 23, dishwasher, gamer) and
natcofragomatic (Nathan, DCO Tech 3 at AWS, ginger, tape-drive nerd)
- Use a lookup table (_ROAST_LORE) so adding new users is one line
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>