Commit Graph
6 Commits
Author SHA1 Message Date
mayatnikovandClaude Opus 4.7 39f786f219 feat(runtime): scheduler-via-runSkill + Pi banter escalation + base-site (follow-ups)
Three closures of remaining PRD follow-ups, one merge:

1. Reflex scheduler now drives behaviour from the curriculum.
   - reflex.js: replaced ad-hoc techTreeReflex + autonomousReflex with
     curriculumReflex that dispatches the skill suggested by
     snapshot.curriculum.plan via runSkill. Per-skill backoff for
     missing_tool / missing_material / no_target / no_food_source /
     unsupported_version. recover() hint with `{hint:"wander"}` swaps
     the next tick to wander for 60 s.
   - Chain is now: defend > eat > sleep > curriculum > idle.
   - reflex.test.js: 11 new tests covering busy/disconnected,
     defend/eat preemption, dispatch by id, unknown-skill fallback,
     per-skill + wander-hint backoffs, onComplete updating backoff.

2. Pi escalation for ADDRESSED_BANTER with hard rate limit.
   - bot.js: when generateReply returns {escalate:true}, spawn askPi
     with bot state + last 5 lines from that speaker (redacted via
     chatMemory). Reply capped at 200 chars, sent as one chat line.
   - Rate cap: 6 calls/hour, 90 s min gap. Suppressed escalations
     log once and silently drop.

3. Phase 4 substrate.
   - runtime/locations.js: atomic JSON store
     (state/<host>/locations.json) with setLocation / getLocation /
     nearestLocation / removeLocation; 6 tests.
   - runtime/base-site.js: scoreCurrentPosition(bot) + pure scoreSite
     bundle (wood / stone / water / flatness / no-players /
     no-foreign-builds, owned-blocks excluded from claim penalty);
     6 tests.
   - runtime/skills/choose-base.js: village.choose-base skill — scores
     the current spot, writes locations.base if score ≥ 8, otherwise
     returns code:"too_weak" with a wander recover hint.
   - curriculum.js: new final milestone village.base-site fires
     village.choose-base until a base location exists.
   - bot.js: stamps snapshot.locations from listLocations() each tick
     so the curriculum can read it without coupling to disk.

docs/runtime.md updated with three new sections.
npm test now 116/116.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:45:51 +03:00
3310cb320f feat(runtime): survival-bot pivot — MC chat is dialog-only (Phase 0) (#12)
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: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:01:26 +03:00
313ef081d1 feat(runtime): village progression — crafting, tech-tree reflex, LLM planner (#11)
Closes the loop "стой и кидай proposals" → "копит ресурсы, строит,
работает к глобальной цели". Three pieces:

1. Crafting primitives (runtime/actions.js).

   craftPlanks (4 per log, any wood type), craftSticks (4 per 2 planks),
   placeCraftingTable (crafts a table from planks if needed + places at
   reference block + reuses an existing table within 4 m), craftWoodenAxe,
   craftWoodenPickaxe, craftWoodenSword. Each uses bot.recipesFor()
   + bot.craft() with a 15s timeout. Returns the same {ok, detail}
   contract as the other actions.

   inv.{getItemCount, getAnyPlanksCount, getAnyLogCount} helpers
   exported so the reflex layer can read inventory cheaply without
   pulling mineflayer state through every reducer.

2. Tech-tree reflex (runtime/reflex.js).

   New techTreeReflex between sleep and autonomous. Inventory-driven
   progression: log+0 planks → planks; planks+0 sticks → sticks;
   planks+sticks+no axe → wooden_axe; +no pickaxe → wooden_pickaxe;
   +no sword → wooden_sword. 5 s cooldown so we don't fire on every
   tick.

   Pure script, no LLM. The progression is exactly what a player
   does in the first 10 min on a new world; making it scripted means
   the bot never burns tokens on it.

3. LLM planner (runtime/planner.js).

   Background timer (every 15 min, with a 30 s warm-up after start).
   Reads goal.md + plan.md + a slim snapshot, prompts Pi to output a
   fresh plan.md to stdout. Stripped of code fences and written
   verbatim to state/<host>/plan.md. Capped at 16 KB.

   The plan is markdown the operator can read or edit by hand. Numbered
   milestones, ✓ prefix for completed ones, kept short. The reflex
   layer doesn't auto-execute LLM text — but the planner sets the
   long-horizon shape that future reflexes (build house, plant farm)
   can read.

   5 min timeout on the pi subprocess. If it crashes or times out, the
   next 15-min tick just retries — no propagation to the reflex loop.

The progression now looks like, roughly:

  chop log (autonomous) →
  craft planks → craft sticks → wooden_axe (tech-tree) →
  chop faster (autonomous, has axe now) →
  wooden_pickaxe + wooden_sword (tech-tree) →
  mine stone … (next PR: stone tools, farm site selection,
  house frame)

Smoke-tested: all three modules import cleanly, exports check out.

Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 17:00:07 +03:00
f5361ad9a4 feat(runtime): autonomous reflex — chop wood + wander + proactive movement (#8)
Closes the "bot stands on a tree doing nothing" problem reported live
when the operator launched the TUI after PR #6 landed. The reactive
chain (operator > defend > eat > sleep > idle) was passive by design:
day-time, full HP and food, no hostile within 4 m ⇒ every reflex
returned noop. The bot perched in dark-oak canopy and never moved.

Changes

runtime/actions.js:
  - chopNearestTree: find any *_log within 32 blocks, equip best axe
    (falls back to fists), path to the block, dig. Per-bot 5-min
    blacklist of unreachable log positions so we don't grind on the
    same impossible target.
  - wander: pick a random offset 6-16 blocks away and path there.
  - setMovementsForGather / setMovementsForTravel: every action that
    uses pathfinder now sets its own Movements profile (canDig=true)
    instead of inheriting whatever the previous caller left. The old
    behaviour caused chop to inherit flee's canDig=false and get stuck
    in the canopy.
  - fleeFrom now uses canDig=true too — the user observed the bot
    permanently stuck on a leaf block because escape required digging.

runtime/reflex.js:
  - new autonomousReflex between sleep and idle. Cooldown 10s. Picks
    chop when log count < 16, else wander. When chop reports "no
    reachable log within 32 blocks" we switch to wander for 60s so we
    don't re-fire chop against the same impossible position.
  - defendReflex tightened: only flee when closest is ≤8m (or ≤12m
    on low HP). Avoids the "82 distant hostiles ⇒ constant flee
    loop" pathology observed at this spawn.
  - flee cooldown: same mob name within 60s ⇒ noop, so we yield to
    other reflexes if flee keeps timing out.
  - sleepReflex retry cooldown raised 30s → 5min. Sleeping fails
    permanently if no bed is around; the short retry blocked
    autonomous behaviour every tick.
  - ctx.lastReflex now records {name, label, ts} after each
    dispatched/completed reflex so the TUI can show what the bot
    just decided.

runtime/bot.js:
  - per-tick snapshot adds lastReflex and busy fields for the TUI.
  - maybeReplyToPlayer: light canned greetings (yo/hey/hi/привет)
    to non-operators when they address the bot. 30s cooldown so we
    don't spam.

tui/tui.tsx:
  - status bar shows either "▸ busy: <label>" while an action is
    in flight, or "last reflex: <name> (<label>) Ns ago" when idle.
    Gives an at-a-glance answer to "what is the bot doing right now?"

Smoke-tested live (play.xmatic.team, 2026-05-25T13:30-13:39):
  - bot did dispatch chop tree (oak_log at 617,82,95)
  - real bug surfaced and 3-fail rule filed a proposal automatically
  - after the runtime fix, chop returns "no reachable log" gracefully
  - bot switched to wander on the next tick
  - position moved from (623.31, 85, 95.12) to (623.41, 86.02, 96.7)
    — the first observable movement in this dark-oak spawn

Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 16:40:36 +03:00
ee2b4c26eb feat(runtime): real reflex bodies + auto-escalation + operator chat (#5)
runtime/actions.js — Mineflayer wrappers with hard timeouts and structured
{ok, detail} returns:
  - attackNearest: equip best melee, lookAt, single swing per call
  - fleeFrom: lazy-load pathfinder, walk N blocks away (canDig=false to
    avoid burrowing through walls under panic)
  - eatBestFood: scan inventory by FOOD_PRIORITY, equip + consume
  - sleepInBed: find nearest placed bed within 16 blocks, path to it, sleep
  - goTo: pathfinder.goto for operator come/follow

runtime/reflex.js — bodies now dispatch real actions via ctx.dispatch:
  - operator-goal (highest): satisfy come/follow command
  - defend: ≤4m attack, ≤12m + low HP/many hostiles flee
  - eat: food < 16 + 5s cooldown
  - sleep: night + safe + 30s retry cooldown
  - idle: heartbeat every 20th tick
Reflex returns "skipped" when ctx.busy so we don't count busy ticks as
either productive or noop in the escalation counter.

runtime/bot.js:
  - ctx.dispatch fire-and-forget wrapper with busy gate, onComplete hook
  - consecutiveNoops counter; after ESCALATE_AFTER_NOOPS (=20, ~1 min at
    tick=3s), askPi with the current snapshot. 10-min cooldown.
  - operator chat handler: parses `<botname> <verb>` messages from
    OPERATOR_USERNAMES. Verbs: status, pause, resume, stop, come.
  - Death drops any pending operator goal.

Smoke-tested live against play.xmatic.team:25565: bot connected, logged
in via AuthMe, reflex chain dispatched flee/sleep, hard timeout fired
when pathfinder couldn't reach the flee target (expected — no usable
ground path in dark_forest at this spawn).

Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 16:11:53 +03:00
1e3b36a9a1 feat(runtime): hybrid script reflex + Ink TUI + Pi-on-demand escalation (#4)
* fix(mindcraft-skills): hard timeout on every skill call

mc_avoid_enemies (and 7 other tools) wrapped only in safeCall without a
withTimeout. When mindcraft's underlying pathfinder/pvp goal couldn't be
satisfied, the call never resolved — the Pi tick loop blocked forever.
Observed live: mc_avoid_enemies pending >10 minutes after one mc_observe.

safeCall now takes timeoutMs (default 30s) and wraps withTimeout itself,
so every tool gets a hard ceiling. Per-tool overrides:
  - goToPosition / goToNearestBlock: 120s / 90s (unchanged from before)
  - defendSelf / avoidEnemies: 45s
  - stay: secs*1000 + 10s
  - craft / consume / pickup / place: 30s
  - equip: 15s
collectBlock still uses its bespoke per-iter 75s loop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(runtime): script-driven reflex daemon + Ink TUI dashboard

Pure-Pi runtime had three failure modes in practice:
  - slow: 20-60s per decision because LLM was in the hot path
  - expensive: every tick (defend, eat, idle) paid for a reasoning pass
  - invisible: required tmux capture-pane to know what the bot was doing

New runtime/ layer is a long-running Node daemon that owns the MC
connection, ticks a priority-ordered reflex chain (defend > eat > sleep
> idle) with NO LLM in the hot path, and exposes status + commands over
a Unix-socket IPC. tui/ is an Ink dashboard that attaches over IPC and
can detach freely — multiple TUI clients can connect at once.

Pi/Codex are still available, but as on-demand escalation: TUI hotkey
'a' spawns `pi -p "<prompt>"` as a subprocess and streams stdout into
the dashboard. The self-improvement loop (proposals → operator approval
→ Pi-driven patch → hot reload) is documented in docs/runtime.md but
not yet wired.

Reflex bodies are stubs today — they log decisions but don't drive
Mineflayer actions yet. The priority chain, IPC contract, and TUI are
fully working; subsequent commits will fill in defend/eat/sleep bodies
and wire automatic escalation.

Run with `npm run bot` + `npm run tui`. Pi-only fallback stays at
`npm run agent`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 16:04:48 +03:00