diff --git a/bot/bot.ts b/bot/bot.ts index e87bb1c..0c1521a 100644 --- a/bot/bot.ts +++ b/bot/bot.ts @@ -1210,6 +1210,8 @@ function redstonePersona(user: User, chatType: string, chatId: number, srv: stri "- Minecraft server: check status (server_status), start/stop/restart services (server_up/down/restart), tail logs (server_logs), look up players in the bot's database (players_list/player_get).", "- Admin-only tools (the caller's role is shown in the context block): run RCON commands (rcon), trigger a world backup (backup), and modify player records (player_upsert/set_*/remove, seen_list). If a non-admin asks for one, politely refuse and don't call it.", "- Minecraft wiki: look things up on minecraft.wiki and surface images into the chat. Usual flow for a 'how do I craft X?' / 'what is X?' question: wiki_search to pick the exact page title → wiki_page for a one-line summary + thumbnail → wiki_page_images with filter='recipe' or 'craft' or 'grid' to find a crafting-grid image, then tg_send_photo to send the image URL straight into the chat (caption it briefly).", + "- IMAGE DELIVERY IS MANDATORY when the user asks for a picture / image / recipe / what it looks like. You MUST call tg_send_photo with a URL from wiki_page_images (or the thumbnail_url from wiki_page as fallback). Describing the recipe only in words is not enough — the user explicitly asked for the image. After tg_send_photo succeeds, you can stop; the photo IS the reply.", + "- If wiki_page_images returns no matches with filter='recipe', retry with filter='craft' or filter='grid'. If still empty, fall back to wiki_page's thumbnail_url. Always send SOMETHING visual when an image was requested.", "- When you use a tool, you usually don't need a separate text reply unless you're adding context — the tool's effect IS the response. After a server_status / players_list lookup, summarize the answer in one sentence.", "", "Stickers — drop emojis inline and the bot does the rest:", @@ -1371,7 +1373,7 @@ async function runRedstoneTurnViaOpenRouter( { role: "user", content: userText }, ]; let ranTool = false; - for (let i = 0; i < 4; i++) { + for (let i = 0; i < 6; i++) { const data = await callOpenRouterOnce(messages); if (!data) return null; // hard failure — caller falls back to Gemini const msg = data.choices?.[0]?.message; @@ -1399,11 +1401,14 @@ async function runRedstoneTurnViaOpenRouter( continue; } try { + console.info("[redstone:or] tool ->", tc.function.name, JSON.stringify(args).slice(0, 200)); const result = await withSlowToolIndicator(chatId, tc.function.name, () => tool.handler(args, toolCtx)); response = { result }; ranTool = true; + console.info("[redstone:or] tool <-", tc.function.name, "ok"); } catch (e) { response = { error: (e as Error).message }; + console.warn("[redstone:or] tool <-", tc.function.name, "err:", (e as Error).message); } } messages.push({ role: "tool", tool_call_id: tc.id, content: JSON.stringify(response) }); @@ -1417,7 +1422,7 @@ async function runRedstoneTurnViaGemini( ): Promise { const contents: GeminiContent[] = [...history, { role: "user", parts: [{ text: userText }] }]; let ranTool = false; - for (let i = 0; i < 4; i++) { + for (let i = 0; i < 6; i++) { const data = await callGeminiOnce(contents, systemPrompt); if (!data) return { text: null, ranTool }; const parts = data.candidates?.[0]?.content?.parts ?? []; @@ -1445,11 +1450,14 @@ async function runRedstoneTurnViaGemini( response = { error: `tool '${fc.functionCall.name}' requires admin role; the calling user is '${callerRole}'. Inform the user, do not retry.` }; } else { try { + console.info("[redstone:gem] tool ->", fc.functionCall.name, JSON.stringify(fc.functionCall.args).slice(0, 200)); const result = await withSlowToolIndicator(chatId, fc.functionCall.name, () => tool.handler(fc.functionCall.args, toolCtx)); response = { result }; ranTool = true; + console.info("[redstone:gem] tool <-", fc.functionCall.name, "ok"); } catch (e) { response = { error: (e as Error).message }; + console.warn("[redstone:gem] tool <-", fc.functionCall.name, "err:", (e as Error).message); } } responseParts.push({ functionResponse: { name: fc.functionCall.name, response } });