Redstone can now query the official Minecraft wiki and deliver
crafting recipe / item images straight into the chat.
New: mcp/lib/minecraft-wiki-tools.ts exports three tools, all hitting
https://minecraft.wiki/api.php (MediaWiki API, formatversion=2):
- wiki_search(query, limit?) — opensearch top results, namespace 0,
returns { title, description, url } for each hit.
- wiki_page(title, thumb_size?) — extracts|pageimages → intro extract
(plain text, ~600 chars) + thumbnail_url + page_url.
- wiki_page_images(title, filter?, limit?) — generator=images +
prop=imageinfo (iiprop=url|mime|size) returns every image embedded
on the page along with its direct https URL. The `filter`
substring is case-insensitive against the filename — pass 'recipe'
/ 'craft' / 'grid' to surface crafting-recipe diagrams.
Bot wiring (bot/bot.ts):
- import minecraftWikiTools, merge into allTools so it flows through
the existing OpenRouter + Gemini function/tool schemas automatically.
- Persona prompt teaches the model the canonical flow:
wiki_search → wiki_page → wiki_page_images(filter='recipe') →
tg_send_photo(url, caption). Composes with the already-existing
tg_send_photo tool — no new telegram tool needed.
User-Agent on every wiki request, 15s timeout, JSON-only.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the old "Up All / Server ▾ / Tunnel ▾" five-button grid with a
single power-toggle button (▶️ Start / 🛑 Stop) plus three flat-nav
buttons (Logs / Users / Refresh). Granular per-service control stays
reachable via the existing /server_up, /server_down, /pf_up, /pf_down
slash commands.
Message body redesigned: top-line health pill (🟢 Online / 🟡 Partial /
🔴 Offline), then aligned status rows for Server and Tunnel, then the
update timestamp. Uses the same 🟢/🔴 pair throughout for consistency,
dropping the mixed ✅/⛔ + 🟢/🟡 from before.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
actUpAll was running `docker compose up -d` with no service filter, so it
re-evaluated every service in the project (including the bot). docker
compose then recreated the bot container, SIGTERM'd the running process
mid-RPC, and left a renamed replacement (`<oldhash>_minecraft-bot`) stuck
in "Created" state because the original container hadn't yet released
the name. End result: /up silently took the bot offline and required
manual `docker rm + docker compose up -d bot` to recover.
Scope to SERVER_SVC + PF_SVC (matching actDownAll which already does the
same thing). The bot can't sensibly restart itself anyway.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
sendMessageDraft (Bot API 9.5) only works in private chats. Group,
supergroup and channel peers reject it with TEXTDRAFT_PEER_INVALID,
so Redstone was silently failing the entire reply path in groups
after the streaming feature landed in cab5337.
Keep replyWithStream for private chats (the typing animation is
the whole point); fall back to plain ctx.reply() everywhere else.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Redstone now hits OpenRouter's `openrouter/free` auto-router first,
falling back to direct Gemini if the OpenRouter call fails (no key,
network error, or `:free` upstream rejected tool use). The auto-
router filters its free-model pool by the request's tool-calling
requirement, so we don't have to pin a specific free model.
- bot/bot.ts:
- OPENROUTER_API_KEY / _FILE env (mirrors GEMINI_API_KEY_FILE)
- OPENROUTER_MODEL defaults to "openrouter/free"
- openRouterTools[] derived from existing geminiFunctionDeclarations
(OpenAI-style {type:"function", function:{name,description,parameters}})
- callOpenRouterOnce + runRedstoneTurnViaOpenRouter (parses JSON-string
tool_calls.arguments, replies with role:"tool" + tool_call_id)
- Existing Gemini path moved into runRedstoneTurnViaGemini
- runRedstoneTurn dispatches OpenRouter first, Gemini on null
- Early-return gate now passes if either key is configured
- docker-compose.yml:
- new openrouter_token secret -> ./openrouter.token (gitignored)
- OPENROUTER_API_KEY_FILE + OPENROUTER_MODEL env wired to the bot
- .gitignore: add openrouter.token (plus pre-existing gitea.token entry
that was sitting uncommitted).
The key file itself is NOT committed (verified via git check-ignore).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two related changes to how Redstone behaves in chat.
Streaming: replies now animate word-by-word into Telegram using the new
sendMessageDraft endpoint (Bot API 9.5, March 2026) via the @grammyjs/stream
plugin, with @grammyjs/auto-retry on the API layer to swallow 429s
transparently. The previous editMessageText-based approach is gone —
sendMessageDraft is designed for this and animates natively on the client
without hitting the 1/sec-per-chat edit limit. Pace lives in
STREAM_WORD_DELAY_MS=50; tunable in one spot.
server_down footgun: the MCP tool was running `compose down` with no
service arg, which tore down the whole project — including the bot
container running the tool call, which then got SIGTERM mid-conversation
and didn't come back (unless-stopped doesn't restart on a clean compose
down). Behaviour now matches the existing /stop slash command:
compose stop minecraft autossh, leaving the bot up. Pass an explicit
service to stop just that one.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Persona polish that grew out of using the bot in the group.
Voice: replace the generic 'friendly assistant' personality block with
laid-back hippy dialect — 'man', 'groovy', 'good vibes', occasional
loose grammar — wrapped around the existing Minecraft-themed flavor.
Capped to one or two hippy words per reply so it doesn't read as a
caricature; still helpful first, vibe second; dials down if the user
is terse.
Name: persona is now 'Redstone Steve' — combines the mineral and the
default player. Prompt notes it also answers to plain 'Redstone' or
'Steve' so existing chat history isn't suddenly off-character.
Typing indicator: startTypingLoop() re-ticks sendChatAction every 4s
for the whole AI roundtrip (Telegram auto-clears after ~5s), wrapped
in a try/finally so the indicator clears (fades naturally) on both
success and failure paths. Previously it fired once and faded mid-call
on long requests, making the bot look stalled.
Sticker swap: only 35% of palette-emoji matches actually fire a
sticker now (STICKER_SEND_PROBABILITY = 0.35). Keeps stickers as a
treat rather than a tic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A handful of related Telegram-bot improvements that piled up in one
working session.
Slash menu: /start now brings the whole stack up (was: panel render —
duplicated by /menu) and a new /stop takes it down. /up and /down kept
as undocumented aliases. /clear wipes the current chat's rows from the
messages table — DM-self for users, admin-only in groups — so Redstone's
recentTurns history can be reset on demand.
Sticker auto-collection: every incoming sticker is upserted into a new
sticker_library table (file_id PK, plus emoji, set_name, first_seen,
last_seen, seen_count). No /setsticker needed. The original event-keyed
stickers table is untouched and still drives the bot's own event
triggers (maybeSendSticker("up") etc).
Emoji → sticker swap (replaces the AI-driven sticker tool): instead of
listing 15+ file_ids in the system prompt and exposing tg_send_sticker
as a function call, the prompt now lists just the *emoji palette* the
library knows about. After Redstone replies, pickStickerForReply()
scans the text for the first palette-emoji that appears and fires the
matching sticker as a follow-up via ctx.replyWithSticker — without
inserting a row into the messages table, so the audit log stays
text-only and Redstone's next recentTurns isn't polluted. tg_send_sticker
and tg_stickers_known are filtered out of Redstone's geminiFunctionDeclarations
(still exposed via the MCP server for Claude / external clients).
Reliability: Gemini call timeout bumped from 25s to 45s after a real
timeout in production with the previous (too-large) sticker prompt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Redstone previously only saw the 23 tg_* tools — it had no idea the
Minecraft stack existed, so questions like "is the server up?" went
unanswered. This change extracts the 15 Minecraft tools (lifecycle,
rcon, backup, players, seen) into the same shared catalog the Telegram
tools already used, so both Gemini and external MCP clients see them.
mcp/lib/types.ts (new) holds the shared shape: Tool<P>, ToolCtx,
createTgClient, createMcRuntime (a Bun.spawn-based wrapper for docker
compose / docker exec), and toolToGeminiFunction (zod → Gemini schema,
now also stripping exclusiveMinimum/Maximum since Gemini rejects them).
mcp/lib/minecraft-tools.ts (new) is the catalog itself. Eight handlers
are flagged requiresAdmin: rcon, backup, and all destructive player_*
writes plus seen_list. mcp/server.ts trusts the caller (Claude / Paul
on the host) and ignores the flag; bot/bot.ts honours it at dispatch
time, returning {error: "...requires admin role..."} to Gemini so it
can explain to the user instead of attempting the call.
mcp/server.ts shrinks from 423 lines to 70 — a single loop over both
catalogs replaces the hand-rolled registrations.
bot/bot.ts wires both catalogs into the function declarations and adds
the admin gate. It also gains a defensive re-check on every incoming
group/DM text: the Redstone handler now does its own lookup of
ctx.from.id against the users table and refuses to reply unless
status='active'. This is belt-and-braces — the auth middleware already
short-circuits unauthorized callers earlier in the chain, but with
privacy-mode-off groups now feeding every message through, a future
refactor that reorders middleware shouldn't be able to make Redstone
respond to strangers.
Drops thinkingConfig from the Gemini call (gemini-2.5-flash-lite
rejects it outright with HTTP 400).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops two new capabilities on top of the existing Telegram bot + Minecraft
stack, both built around a shared toolkit so there's one source of truth
for the actions either side can take.
mcp/ — a Bun-native MCP server exposing 38 tools to MCP clients (Claude
Code, etc.): server lifecycle, rcon, backup, player/db CRUD, plus 23
Telegram Bot API methods (messaging, reactions, polls, dice, photos,
stickers, pins, forum topics, chat config). Runs over stdio.
mcp/lib/telegram-tools.ts — the Telegram tool catalog as Zod-typed
handlers. Imported by both mcp/server.ts (registers each as an MCP tool)
and bot/bot.ts (exposes each as a Gemini function declaration), so adding
a tool in one place lights it up everywhere.
bot/bot.ts — replaces the silent-on-unknown-text behaviour with Redstone,
an in-bot persona driven by gemini-2.5-flash-lite with native function
calling. In DMs it always responds; in groups only when @-mentioned or
replied to. The tool-use loop (max 4 rounds) lets it decide to send a
poll, react with an emoji, roll dice, etc. via the shared handlers rather
than just text. Thinking budget zeroed and system prompt locked down so
the model doesn't leak its reasoning into replies.
docker-compose.yml — adds google_key as a docker secret and passes
GEMINI_API_KEY_FILE + GEMINI_MODEL to the bot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>