* 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>
71 lines
1.7 KiB
JavaScript
71 lines
1.7 KiB
JavaScript
// Ring-buffered log with fan-out: stdout + IPC broadcast + on-disk daily file.
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { stateDir } from "./config.js";
|
|
|
|
const LOGS_DIR = path.join(stateDir, "logs");
|
|
fs.mkdirSync(LOGS_DIR, { recursive: true });
|
|
|
|
const RING_SIZE = 500;
|
|
const ring = []; // newest at end
|
|
|
|
const subscribers = new Set(); // fn(entry)
|
|
|
|
function todayStamp() {
|
|
return new Date().toISOString().slice(0, 10);
|
|
}
|
|
|
|
function dailyLogPath() {
|
|
return path.join(LOGS_DIR, `${todayStamp()}.log`);
|
|
}
|
|
|
|
function appendDisk(entry) {
|
|
const line = `${entry.ts} [${entry.level}] ${entry.source}: ${entry.text}\n`;
|
|
try {
|
|
fs.appendFileSync(dailyLogPath(), line);
|
|
} catch {
|
|
// disk full or read-only — give up silently to avoid log-of-log loops
|
|
}
|
|
}
|
|
|
|
export function log(level, source, text, details) {
|
|
const entry = {
|
|
ts: new Date().toISOString(),
|
|
level,
|
|
source,
|
|
text: typeof text === "string" ? text : JSON.stringify(text),
|
|
details,
|
|
};
|
|
ring.push(entry);
|
|
if (ring.length > RING_SIZE) ring.shift();
|
|
|
|
// stdout mirror
|
|
const prefix = `[${entry.ts}] [${level}] ${source}:`;
|
|
const line = `${prefix} ${entry.text}`;
|
|
if (level === "error") console.error(line);
|
|
else console.log(line);
|
|
|
|
appendDisk(entry);
|
|
|
|
for (const sub of subscribers) {
|
|
try {
|
|
sub(entry);
|
|
} catch {}
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
export const info = (src, msg, d) => log("info", src, msg, d);
|
|
export const warn = (src, msg, d) => log("warn", src, msg, d);
|
|
export const error = (src, msg, d) => log("error", src, msg, d);
|
|
export const debug = (src, msg, d) => log("debug", src, msg, d);
|
|
|
|
export function recentLogs(n = 50) {
|
|
return ring.slice(-n);
|
|
}
|
|
|
|
export function onLog(fn) {
|
|
subscribers.add(fn);
|
|
return () => subscribers.delete(fn);
|
|
}
|