Commit Graph
3 Commits
Author SHA1 Message Date
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
cd14bbf89a feat(runtime): state persistence + proposals + supervisor hot-restart (#6)
Closes the self-improvement loop end-to-end:

  reflex fails 3× → proposal file → operator approves in TUI →
  `npm run propose:apply <file>` spawns Pi on a feature branch →
  Pi commits the patch → supervisor watches runtime/*.js and
  restarts the child on change.

runtime/state-store.js — atomic current-task.json writes, daily diary
  append, proposals/ + proposals/approved/ helpers.

runtime/bot.js:
  - ctx.dispatch writes current-task.json on start and updates it on
    completion / failure / throw.
  - failure tracker: 3 consecutive same-label failures → writeProposal()
    with the snapshot, labels, and a suggested-next-step section.
    30-min cooldown prevents proposal spam.
  - on startup, surfaces resume info (previous task + pending proposal
    count); on death, clears current-task.json + writes diary line.
  - new IPC commands: PROPOSAL_LATEST returns the newest pending
    proposal body; PROPOSAL_APPROVE moves it to proposals/approved/.

tui/tui.tsx — status bar shows `[proposals N, press y]` badge when
  bot.pendingProposals > 0. Hotkey 'y' opens the proposal panel; 'y'
  approves, 'n'/Esc closes.

scripts/propose-apply.js — given an approved proposal filename, creates
  a `feat/proposal-<slug>` branch and spawns `pi -p` with the proposal
  + repo-conventions prompt. Refuses on dirty tree. No auto-push, no
  auto-merge — operator reviews the diff and decides.

runtime/supervisor.js — forks bot.js as a child, watches runtime/*.js,
  restarts on file change or on child exit code 42. Rate-limited at 5
  restarts/minute. SIGINT/SIGTERM forward cleanly. `npm run bot` now
  goes through the supervisor; `npm run bot:bare` skips it.

Smoke-tested: supervisor spawned, bot connected to MC, spawned at
expected coords, diary line written, state cleanup on SIGTERM correct.

Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 16:20:09 +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