From 9f930407767fd3ee09c9addb61b657ce656874e3 Mon Sep 17 00:00:00 2001 From: Yuriy Mayatnikov Date: Mon, 25 May 2026 21:55:52 +0300 Subject: [PATCH] =?UTF-8?q?feat(runtime):=20survival-bot=20pivot=20?= =?UTF-8?q?=E2=80=94=20MC=20chat=20is=20dialog-only=20(Phase=200)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 0 of plans/autonomous-survival-bot-prd.md: change the product direction from operator-driven remote control to autonomous survival resident. MC chat is dialog-only for everyone, including OPERATOR_USERNAMES — commands like come/follow/build/pause/stop are recorded in the diary but not dispatched. TUI remains the only local control plane. Runtime changes: - Remove operatorGoalReflex from reflex.js (the come-here chat command). - Replace handleOperatorChat in bot.js with a dialog-only handleChat that answers greetings/status questions and records command-like verbs (en+ru) without dispatching them. - Default MC_VERSION to "auto" in runtime/config.js; mineflayer receives `false` to trigger version auto-detection. - Update auto-escalation prompt's reflex chain summary. Docs: - AGENTS.md: product pivot notice up top; chat-driven scope-trust is flagged as legacy/Pi-only. - README.md / docs/runtime.md: replace operator-chat command list with dialog-only description; update reflex chain summary. - docs/roadmap.md: Phase 2/3 marked superseded by the PRD where they assumed chat-driven control. Co-Authored-By: Claude Opus 4.7 (1M context) --- AGENTS.md | 22 +++++-- README.md | 10 +-- docs/roadmap.md | 44 +++++++++---- docs/runtime.md | 65 ++++++++++++------- runtime/bot.js | 162 +++++++++++++++++++--------------------------- runtime/config.js | 11 +++- runtime/reflex.js | 33 ++-------- 7 files changed, 175 insertions(+), 172 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 069fe89..60d6a0d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,10 +5,24 @@ > the hybrid one under [`runtime/`](./runtime/) — a script-driven reflex loop > with Pi invoked only on demand. See [`docs/runtime.md`](./docs/runtime.md) > for the new architecture. The principles below still apply to both modes: -> tool catalog, safety rules, operator-trust model, and memory protocol are -> shared. When you (the agent, in Pi-only mode) propose new code, prefer -> writing it as a reflex/action in `runtime/` over an extension in -> `extensions/` — the hybrid runtime is where new work lands going forward. +> tool catalog, safety rules, and memory protocol are shared. When you (the +> agent, in Pi-only mode) propose new code, prefer writing it as a +> reflex/action in `runtime/` over an extension in `extensions/` — the +> hybrid runtime is where new work lands going forward. + +> **Product pivot (2026-05-25, Phase 0).** The bot is no longer a remote +> control for operators or players. It is becoming a self-sufficient survival +> resident of the configured Minecraft server. **Minecraft chat is +> dialog-only.** Nobody — including names listed in `OPERATOR_USERNAMES` — +> can drive the bot via chat: `come`, `follow`, `pause`, `stop`, `build`, +> `give` and similar verbs are recorded but not dispatched. The TUI (or any +> future Telegram bridge) is the only local control plane for actions; the +> repo (file edits, skills, runtime code, proposals) is the trusted +> long-term control channel. The full plan lives in +> `plans/autonomous-survival-bot-prd.md` (local-only, gitignored). Sections +> below that still describe chat-driven scope-trust apply only to the +> Pi-only runtime's legacy behaviour — treat them as historical context, not +> as a description of current behaviour. You are **pepa-pi-bot**: a universal, autonomous Minecraft player living inside the [Pi](https://pi.dev) runtime. diff --git a/README.md b/README.md index f623207..93f606d 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ The bot **proposes its own patches** when something repeatedly fails: Supervisor (`npm run bot`) watches `runtime/*.js` and hot-restarts the child on file change, so during step 4 you can iterate quickly. -See [`docs/runtime.md`](./docs/runtime.md) for the full lifecycle and the operator-chat command list (`pepa_bot status`, `come`, `pause`, etc.). +See [`docs/runtime.md`](./docs/runtime.md) for the full lifecycle. **Note (2026-05-25):** MC chat is now dialog-only — operator commands (`come`, `pause`, `stop`, …) are no longer dispatched from chat; use the TUI for local control. See `plans/autonomous-survival-bot-prd.md` for the survival-bot pivot. ## Authentication @@ -198,11 +198,13 @@ These are mirrored in `AGENTS.md` and re-stated at the top of any system prompt 🌳 **Phase 0 — Body** done. Bridge online, AuthMe handled, `hello` sent. See `skills/server-onboarding.md`. -🌳 **Phase 1 — Presence** implemented and operator trust wired: the bridge stays online with bounded reconnects, keeps a rolling chat buffer, exposes status/recent-chat/operator/escalation tools, applies `OPERATOR_USERNAMES` as scope-only trust, and can prompt the Pi loop to reply sparingly. +🌳 **Phase 1 — Presence** implemented: bridge stays online with bounded reconnects, rolling chat buffer, status/recent-chat tools, sparing replies. -🌿 **Phase 2 — Locomotion/build rails** in progress: `mineflayer-pathfinder` is wired with guarded `mc_goto`, plus `mc_build_pyramid_5x5` for the operator-approved empty-site pyramid task. Dynamic following is still pending. Phase 5 self-extension is documented and in progress; Phase 6 escalation logging is implemented. +🌿 **Phase 2 — Locomotion/build rails** in progress: `mineflayer-pathfinder` is wired with guarded `mc_goto`, plus `mc_build_pyramid_5x5`. Phase 5 self-extension and Phase 6 escalation logging are implemented. -🌱 **Phase 3 — Goal-driven autonomy** seeded: [`docs/memory-model.md`](./docs/memory-model.md) defines shared-knowledge vs personal-memory; per-server `goal.md` / `plan.md` / `current-task.json` / `diary/` shape autonomous behaviour. Kickoff via [`prompts/live-your-life.md`](./prompts/live-your-life.md). +🌱 **Phase 3 — Goal-driven autonomy** seeded: [`docs/memory-model.md`](./docs/memory-model.md) defines shared-knowledge vs personal-memory; per-server `goal.md` / `plan.md` / `current-task.json` / `diary/` shape autonomous behaviour. + +🌿 **Survival-bot pivot (2026-05-25)** — the bot is becoming a self-sufficient survival resident of the configured server. **MC chat is dialog-only**; operator/player chat commands are recorded but not dispatched (TUI is the only local control plane). Full plan: `plans/autonomous-survival-bot-prd.md` (local-only, gitignored). Phase 0 (chat-control cleanup, version auto-detect) is done; Phase 1+ (observability, skill substrate, survival curriculum, base/village loop) is the next focus. Full plan: [`docs/roadmap.md`](./docs/roadmap.md). Memory layout: [`docs/memory-model.md`](./docs/memory-model.md). Day-to-day judgement: "Operating principles" in [`AGENTS.md`](./AGENTS.md). diff --git a/docs/roadmap.md b/docs/roadmap.md index e38a74e..a3cb26d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -2,6 +2,14 @@ > The bot grows by accretion. This file describes **the order in which it should grow**, not a set of features to build upfront. Each phase is something the agent itself can extend itself into, one skill at a time. +> **Product pivot (2026-05-25).** The original roadmap below assumed an +> operator-driven bot (chat summons, follow-me, etc.). The new direction is +> an autonomous survival resident — see `plans/autonomous-survival-bot-prd.md` +> for the active plan and phase structure. Where the two disagree, **the PRD +> wins.** Phases 2 (chat-summons) and 3 (operator-priority loop) below are +> kept as historical context but no longer represent shipping work; the new +> phases are tracked in the PRD. + Status legend: 🌱 not started · 🌿 in progress · 🌳 done · ⏸️ paused ## Phase 0 — Body 🌳 @@ -21,7 +29,12 @@ The bot is **on the server, all the time** (except for a clean human-issued disc Stretch: short-term chat memory (last N lines) so it can reference what was just said. -## Phase 2 — Locomotion with guard rails 🌿 +## Phase 2 — Locomotion with guard rails ⏸️ (superseded by PRD) + +> Historical scope: chat-driven summons. As of the 2026-05-25 pivot, MC chat +> cannot summon the bot. Pathfinder/safety primitives from this phase are +> still useful for autonomous survival movement; the chat-driven summon +> surface is removed. See PRD §5 (Target Architecture). The bot can be **summoned** by trusted/sanctioned coordinate requests via `mc_goto`; dynamic follow is still pending. Goal: "come to 100 64 -200", "follow me", "go to spawn" with three hard rails: @@ -31,28 +44,33 @@ The bot can be **summoned** by trusted/sanctioned coordinate requests via `mc_go Stretch: `mc_position_share()` so the bot can answer "where are you?". -## Phase 3 — Best life when idle / goal-driven autonomy 🌿 (kickoff) +## Phase 3 — Best life when idle / goal-driven autonomy 🌿 (revised by PRD) -When chat is quiet for some threshold (5-10 minutes of no addressed/non-trivial messages), the bot switches to **autonomous mode**. +> The "live operator task → drop everything" step from the legacy priority +> loop no longer applies — MC chat cannot create tasks. The revised +> scheduler priority lives in PRD §5.3 (Task Scheduler Priority). The rest +> of this section (goal/plan/current-task/diary structure) still applies +> and is the foundation for the survival curriculum. -Now it is **goal-driven**, not just idle-active: +The bot is **always autonomous**: it does not wait for operator chat to +have something to do. -- A long-term goal lives in `state//goal.md` (e.g. "build a small village and survive"). Personal memory, see `docs/memory-model.md`. +- A long-term goal lives in `state//goal.md`. Personal memory, see `docs/memory-model.md`. - Decomposed into milestones in `state//plan.md`. - Current action is checkpointed in `state//current-task.json` for resume-on-restart. - Daily journal in `state//diary/YYYY-MM-DD.md`. -Priority loop (highest to lowest): -1. Live operator task → drop everything, do it. -2. Live non-operator chat → reply briefly. -3. Resume `current-task.json` if interrupted. -4. Next milestone in `plan.md`. -5. Decompose `goal.md` → new plan. +Revised priority loop (per PRD §5.3): +1. Emergency survival (death/lava/low HP/starvation). +2. Finish or recover current task. +3. Maintain base safety (light, shelter, repair). +4. Execute current milestone. +5. Expand village/base. +6. Social reply if there is a relevant chat event. +7. Idle diary/status heartbeat. Concrete activities while autonomous: scout/build modest base, farm food, store in chests, light area, defend at night, explore cautiously, build out toward the goal. Skills emerge: `farming-wheat`, `chest-organizer`, `careful-cave-mining`, `village-layout`, etc. -Kicked off via [`prompts/live-your-life.md`](../prompts/live-your-life.md). - ## Phase 4 — Telegram bridge 🌱 Two-way ops channel without sitting in Pi TUI: diff --git a/docs/runtime.md b/docs/runtime.md index 6cabd8a..d4475ed 100644 --- a/docs/runtime.md +++ b/docs/runtime.md @@ -4,6 +4,12 @@ > 2026-05-25. The pure Pi runtime (`pi` from repo root) still works and is > documented as a fallback at the bottom of this file. +> **Phase 0 product pivot (2026-05-25).** MC chat is now **dialog-only** for +> everyone, including `OPERATOR_USERNAMES`. The reflex loop no longer takes +> commands from chat. Local control lives in the TUI (`p`/`s` hotkeys); +> long-term control lives in the repo. See +> `plans/autonomous-survival-bot-prd.md` for the survival-bot pivot. + ## Why a hybrid runtime? The original design ran every tick inside Pi — the LLM saw the world, picked @@ -41,8 +47,8 @@ new code for itself. │ │ - MC TCP │ │ - tick every N sec │ │ - Unix socket ││ │ │ - AuthMe handler │◀─│ - priority order: │─▶│ - broadcasts ││ │ │ - chat / events │ │ defend > eat │ │ status/log/ ││ -│ │ │ │ > sleep > current │ │ chat events ││ -│ │ │ │ > idle │ │ - accepts ││ +│ │ (dialog-only) │ │ > sleep > tech │ │ chat events ││ +│ │ │ │ > autonomous │ │ - accepts ││ │ │ │ │ - NO LLM in path │ │ commands ││ │ └──────────────────┘ └─────────┬────────────┘ └────────────────┘│ │ │ │ @@ -100,18 +106,23 @@ TUI; the bot is unaffected. The chain (highest priority first), wired and dispatching real Mineflayer actions: -1. **`operatorGoalReflex`** — if `OPERATOR_USERNAMES` issued a `come` / - `follow` command, satisfy it (walk to the operator's last known - position, reply in chat on arrival or failure). -2. **`defendReflex`** — closest hostile within 4 m → `attackNearest` +1. **`defendReflex`** — closest hostile within 4 m → `attackNearest` (equips best melee). Within 12 m + low HP or ≥3 hostiles → `fleeFrom` along the away-vector. -3. **`eatReflex`** — food < 16 → `eatBestFood` (picks from +2. **`eatReflex`** — food < 16 → `eatBestFood` (picks from FOOD_PRIORITY list, equip + consume). 5 s cooldown. -4. **`sleepReflex`** — night + no hostile within 8 m → `sleepInBed` +3. **`sleepReflex`** — night + no hostile within 8 m → `sleepInBed` (finds nearest placed bed within 16 blocks, paths there, sleeps). - 30 s cooldown on failures. -5. **`idleReflex`** — every 20th tick, log heartbeat (HP / food / pos). + 5 min cooldown on failures. +4. **`techTreeReflex`** — deterministic crafting progression + (planks → sticks → wooden axe → pickaxe → sword) when prerequisites + are in inventory. +5. **`autonomousReflex`** — when nothing reactive fires: chop trees until + ~16 logs, then wander to discover new chunks. +6. **`idleReflex`** — every 20th tick, log heartbeat (HP / food / pos). + +There is **no operator-goal reflex anymore.** MC chat does not create +movement/build/mining tasks (Phase 0 of `plans/autonomous-survival-bot-prd.md`). Adding a new reflex = a function `(ctx) => { action, ... }` in `runtime/reflex.js`, inserted at the right priority. Actions live in @@ -127,8 +138,8 @@ bot spawns `pi -p ""` and streams its stdout into the Pi panel. **2. Automatic** — every tick where the entire reflex chain returns -`noop` (no operator goal, no hostiles in reach, food fine, day or no -bed, etc.) increments a counter. When the counter hits +`noop` (no hostiles in reach, food fine, day or no bed, nothing to +craft, nowhere to wander) increments a counter. When the counter hits `ESCALATE_AFTER_NOOPS = 20` (≈1 min at `tick=3s`), the bot fires `askPi` with the current snapshot and a fixed system prompt telling Pi to suggest one next action. 10 min cooldown so a permanently-idle bot @@ -139,22 +150,26 @@ should only suggest what to do *with the existing tools*. If a deeper problem is happening, the failure-tracker (see Self-improvement) will file a proposal instead. -## Operator chat commands +## In-game chat (dialog-only) -Players listed in `OPERATOR_USERNAMES` can address the bot in MC chat -by prefixing the message with the bot's name: +As of the Phase 0 survival-bot pivot, MC chat does **not** drive bot +actions for anyone, including names listed in `OPERATOR_USERNAMES`. +The bot will: -``` -pepa_bot status → bot replies with HP / food / pos / hostiles / busy -pepa_bot come → bot pathfinds to the operator's current position -pepa_bot pause → reflex loop stops -pepa_bot resume → reflex loop resumes -pepa_bot stop → graceful disconnect + process exit -``` +- reply to greetings (`hi`, `привет`, etc.) and to being addressed by + name, rate-limited; +- answer status questions (`pepa_bot status`, `как дела`, `what are + you doing?`) from the live snapshot; +- detect command-like verbs (`come`, `follow`, `build`, `pause`, + `stop`, `give`, …) when addressed, record them in the diary, and + reply once per cooldown that MC chat is dialog-only. -Unrecognized commands get a polite "didn't recognize" reply. Operator -identity verification is the server's job (AuthMe on cracked, -online-mode on premium) — the bot trusts the nickname. +Local control of the bot (pause/resume/stop, sending chat manually, +escalating to Pi) lives in the TUI. Long-term control (skills, +runtime code, proposals) lives in the repo. `OPERATOR_USERNAMES` is +still used to **label** speakers in logs (`operator ` vs +`player `), and remains the right place to plug a future +trusted control channel (e.g. Telegram bridge). ## IPC protocol diff --git a/runtime/bot.js b/runtime/bot.js index aa21e9e..9f68a39 100644 --- a/runtime/bot.js +++ b/runtime/bot.js @@ -3,7 +3,10 @@ // - the reflex tick loop (no LLM in hot path) // - the IPC server for TUI clients // - on-demand Pi-headless escalation (manual or automatic) -// - simple operator-chat commands from OPERATOR_USERNAMES +// +// MC chat is dialog-only (see plans/autonomous-survival-bot-prd.md, FR1). +// Player/operator chat may produce a social reply but never dispatches a +// movement/build/mining task. TUI is the only local control plane. // // Lifecycle: started by `npm run bot`. Connects to MC, spawns IPC server, // ticks every TICK_INTERVAL_SECONDS, broadcasts STATUS to clients each tick. @@ -58,16 +61,12 @@ const reflexCtx = { snapshot: lastSnapshot, busy: false, currentActionLabel: null, - operatorGoal: null, idleCounter: 0, lastEatAt: 0, lastSleepAttemptAt: 0, // Tracks repeated failure of the same labelled action — triggers a proposal. recentFailures: [], // [{label, detail, ts}], capped at 10 dispatch: dispatchAction, - clearOperatorGoal: () => { - reflexCtx.operatorGoal = null; - }, }; let chatTimestamps = []; @@ -300,96 +299,77 @@ function maybeFileProposal(label) { appendDiary(`proposal filed: ${filename} (${summary})`); } -// ---- player chat replies (non-operator) ------------------------------------ +// ---- chat replies (dialog-only) -------------------------------------------- +// +// As of Phase 0 of the survival-bot PRD, MC chat is dialog-only for everyone, +// including OPERATOR_USERNAMES. The bot may reply socially or answer status +// questions, but it does NOT dispatch movement/build/mining tasks from chat. +// If a player addresses the bot with a command-like verb (come, follow, build, +// pause, stop, give), the bot acknowledges that chat is dialog-only and +// records the ignored command in the diary. -// Rate-limited light replies to ordinary players. Spam guard is intentional: -// the chat rate limit catches outbound flooding; this one prevents replying -// to every greeting in a busy room. -let lastPlayerReplyAt = 0; -const PLAYER_REPLY_COOLDOWN_MS = 30_000; +let lastChatReplyAt = 0; +const CHAT_REPLY_COOLDOWN_MS = 30_000; const GREETING_RE = /\b(hi|hello|hey|yo|sup|hola|привет|здаров|здарова|здорова|здравствуй|здравствуйте|салам)\b/i; - -function maybeReplyToPlayer(username, text) { - if (!bot) return; - const lower = text.trim().toLowerCase(); - const botname = bot.username.toLowerCase(); - const addressed = lower.includes(botname); - if (!addressed && !GREETING_RE.test(lower)) return; - const since = Date.now() - lastPlayerReplyAt; - if (since < PLAYER_REPLY_COOLDOWN_MS) return; - lastPlayerReplyAt = Date.now(); - - // Pick a small canned response. Non-operator chat is dialog-only by design - // — we don't act on player verbs, we just acknowledge presence. - const replies = ["yo", "hey", "hi", "привет"]; - const reply = replies[Math.floor(Math.random() * replies.length)]; - botChat(`${username}: ${reply}`); -} - -// ---- operator chat commands ------------------------------------------------ +const STATUS_RE = /\b(status|how are you|what are you doing|whats up|what['’]?s up|чё делаешь|что делаешь|как ты|как дела|статус)\b/i; +const COMMAND_LIKE_RE = /\b(come|follow|build|pause|resume|stop|go to|goto|tp|teleport|give|drop|attack|kill|dig|mine|chop|farm|harvest|sleep here|иди сюда|подойди|следуй|остановись|стоп|пауза|строй|копай|дай)\b/i; function isOperator(username) { if (!username) return false; return config.operators.includes(username.toLowerCase()); } -function handleOperatorChat(username, text) { - // Two address formats accepted: prefix "," or ":" (case-insensitive), - // or full chat starting with the bot's own name. We're permissive here. - const lower = text.trim().toLowerCase(); +function buildStatusReply() { + const s = lastSnapshot; + const parts = []; + if (reflexCtx.busy) parts.push(`busy=${reflexCtx.currentActionLabel}`); + if (s.health !== undefined) parts.push(`hp=${s.health}/20`); + if (s.food !== undefined) parts.push(`food=${s.food}/20`); + if (s.position) parts.push(`pos=${s.position.x},${s.position.y},${s.position.z}`); + if (s.hostileCount) parts.push(`hostiles=${s.hostileCount}`); + return parts.join(" ") || "alive"; +} + +function handleChat(username, text) { + if (!bot) return; + const trimmed = text.trim(); + const lower = trimmed.toLowerCase(); const botname = bot.username.toLowerCase(); - const prefixed = lower.startsWith(botname + " ") || lower.startsWith(botname + ",") || lower.startsWith(botname + ":"); - const stripped = prefixed ? text.trim().slice(botname.length).replace(/^[,:\s]+/, "") : text.trim(); - const cmd = stripped.toLowerCase(); + const addressed = lower.includes(botname); + const looksLikeCommand = addressed && COMMAND_LIKE_RE.test(lower); - // Unaddressed chat is fine — just don't treat it as a command. - if (!prefixed) return; - - info("operator", `${username} → "${cmd}"`); - - if (cmd === "status" || cmd === "how are you?") { - const s = lastSnapshot; - botChat( - `hp=${s.health}/20 food=${s.food}/20 pos=${s.position?.x},${s.position?.y},${s.position?.z}${ - s.hostileCount ? ` hostiles=${s.hostileCount}` : "" - }${reflexCtx.busy ? ` busy=${reflexCtx.currentActionLabel}` : ""}`, - ); - return; - } - if (cmd === "pause") { - reflexPaused = true; - botChat(`reflex paused, awaiting your call.`); - return; - } - if (cmd === "resume") { - reflexPaused = false; - botChat(`reflex resumed.`); - return; - } - if (cmd === "stop") { - botChat(`bye.`); - setTimeout(() => gracefulExit(0), 500); - return; - } - if (cmd === "come" || cmd === "come here") { - const op = Object.values(bot.entities).find((e) => e.username === username); - if (!op) { - botChat(`${username}: can't see you nearby.`); - return; + // Command-like chat (from anyone, including operators) is recorded but not + // dispatched. We tell the speaker once per cooldown so they aren't left + // wondering why nothing happened. + if (looksLikeCommand) { + const op = isOperator(username) ? "operator" : "player"; + info("chat", `ignored command-like chat from ${op} ${username}: ${trimmed.slice(0, 80)}`); + appendDiary(`ignored command-like chat from ${username}: ${trimmed.slice(0, 120)}`); + const since = Date.now() - lastChatReplyAt; + if (since >= CHAT_REPLY_COOLDOWN_MS) { + lastChatReplyAt = Date.now(); + botChat(`${username}: MC chat is dialog-only — operator uses the TUI to drive me.`); } - reflexCtx.operatorGoal = { - kind: "come", - from: username, - x: Math.round(op.position.x), - y: Math.round(op.position.y), - z: Math.round(op.position.z), - }; - botChat(`on my way to ${reflexCtx.operatorGoal.x},${reflexCtx.operatorGoal.y},${reflexCtx.operatorGoal.z}`); return; } - botChat(`${username}: didn't recognize "${cmd}". I know: status, come, pause, resume, stop.`); + // Dialog: greeting, status question, or addressed banter. + const wantsStatus = addressed && STATUS_RE.test(lower); + const isGreeting = GREETING_RE.test(lower); + if (!addressed && !isGreeting) return; + + const since = Date.now() - lastChatReplyAt; + if (since < CHAT_REPLY_COOLDOWN_MS) return; + lastChatReplyAt = Date.now(); + + if (wantsStatus) { + botChat(`${username}: ${buildStatusReply()}`); + return; + } + const greetings = ["yo", "hey", "hi", "привет"]; + const reply = greetings[Math.floor(Math.random() * greetings.length)]; + botChat(`${username}: ${reply}`); } // ---- connect --------------------------------------------------------------- @@ -402,7 +382,7 @@ function connect() { port: config.port, username: config.username, auth: config.authMode === "microsoft" ? "microsoft" : "offline", - version: config.version, + version: config.mineflayerVersion, hideErrors: false, }); reflexCtx.bot = bot; @@ -421,18 +401,10 @@ function connect() { bot.on("chat", (username, message) => { if (username === bot.username) return; ipc?.broadcast(EVENT_TYPES.CHAT, { from: username, text: message, kind: "player" }); - if (isOperator(username)) { - try { - handleOperatorChat(username, message); - } catch (e) { - warn("operator", `handler threw: ${e.message}`); - } - } else { - try { - maybeReplyToPlayer(username, message); - } catch (e) { - warn("chat", `player reply handler threw: ${e.message}`); - } + try { + handleChat(username, message); + } catch (e) { + warn("chat", `chat handler threw: ${e.message}`); } }); @@ -441,8 +413,6 @@ function connect() { warn("mc", `died at ${JSON.stringify(pos)}`); appendDiary(`died at ${pos?.x.toFixed(0)},${pos?.y.toFixed(0)},${pos?.z.toFixed(0)}`); ipc?.broadcast(EVENT_TYPES.DEATH, { reason: "unknown", position: pos }); - // On death, drop any operator goal — they need to ask again. - reflexCtx.operatorGoal = null; clearCurrentTask(); }); @@ -488,7 +458,7 @@ function maybeAutoEscalate() { `You are the escalation cortex for an autonomous Minecraft bot. The bot's`, `script-driven reflex loop has produced no useful action for ${ESCALATE_AFTER_NOOPS} consecutive ticks`, `(~${Math.round((ESCALATE_AFTER_NOOPS * config.tickIntervalMs) / 1000)}s). The reflex chain is:`, - ` operator-goal > defend > eat > sleep > idle`, + ` defend > eat > sleep > tech-tree > autonomous > idle`, `Snapshot:`, "```json", promptCtx, diff --git a/runtime/config.js b/runtime/config.js index 53ac84d..8e60e1f 100644 --- a/runtime/config.js +++ b/runtime/config.js @@ -22,11 +22,20 @@ const host = req("MC_HOST"); const port = Number.parseInt(opt("MC_PORT", "25565"), 10); const username = req("MC_USERNAME"); +// MC_VERSION: "auto" (or empty) lets mineflayer auto-detect from the server +// handshake — the right default per the survival-bot PRD (no hard-coded modern +// version unless the server requires pinning). Mineflayer accepts `false` to +// auto-detect, so we translate "auto" into false at the boundary; the original +// string is preserved for logging. +const rawVersion = opt("MC_VERSION", "auto"); +const mineflayerVersion = rawVersion.toLowerCase() === "auto" ? false : rawVersion; + export const config = Object.freeze({ host, port, username, - version: opt("MC_VERSION", "1.21.5"), + version: rawVersion, + mineflayerVersion, authMode: opt("MC_AUTH_MODE", "offline"), authmePassword: opt("MC_AUTHME_PASSWORD", ""), operators: opt("OPERATOR_USERNAMES", "") diff --git a/runtime/reflex.js b/runtime/reflex.js index 90ecbb7..36d31e6 100644 --- a/runtime/reflex.js +++ b/runtime/reflex.js @@ -12,7 +12,6 @@ import { fleeFrom, eatBestFood, sleepInBed, - goTo, chopNearestTree, wander, craftPlanks, @@ -32,34 +31,11 @@ const REFLEX_LOG = "reflex"; // { action: "completed", kind, detail } — fully sync, already done // Reflexes must NEVER throw — they log and return noop on failure. -// ---- operator goal reflex -------------------------------------------------- - -// Highest priority: if the operator gave a "come here" / "follow me" command, -// satisfy that before anything else (except defending if HP is critical). -function operatorGoalReflex(ctx) { - const goal = ctx.operatorGoal; - if (!goal) return { action: "noop" }; - if (goal.kind === "come") { - ctx.dispatch( - () => goTo(ctx.bot, goal.x, goal.y, goal.z, 2), - `operator-come(${goal.from})`, - { - onComplete: (res) => { - if (res.ok) { - ctx.bot.chat(`${goal.from}: arrived.`); - } else { - ctx.bot.chat(`${goal.from}: couldn't reach you (${res.detail}).`); - } - ctx.clearOperatorGoal(); - }, - }, - ); - return { action: "dispatched", kind: "operator-come", label: `come ${goal.x},${goal.y},${goal.z}` }; - } - return { action: "noop" }; -} - // ---- defend ---------------------------------------------------------------- +// +// Note: MC chat is dialog-only as of Phase 0 of the survival-bot PRD. There is +// no operator-goal reflex anymore — operator/player chat cannot create a +// movement/build/mining task. TUI is the only local control plane. function defendReflex(ctx) { const s = ctx.snapshot; @@ -298,7 +274,6 @@ function idleReflex(ctx) { } const REFLEXES = [ - { name: "operator-goal", fn: operatorGoalReflex }, { name: "defend", fn: defendReflex }, { name: "eat", fn: eatReflex }, { name: "sleep", fn: sleepReflex },