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>
51 lines
1.8 KiB
JavaScript
51 lines
1.8 KiB
JavaScript
// Shared IPC contract between runtime/bot.js (server) and tui/tui.tsx (client).
|
|
// Frame format: one JSON object per line over a Unix-domain socket.
|
|
// Socket path: state/<server-key>/bot.sock (created by server, removed on shutdown).
|
|
|
|
export const SOCKET_BASENAME = "bot.sock";
|
|
|
|
// Server → client messages.
|
|
export const EVENT_TYPES = Object.freeze({
|
|
STATUS: "status", // periodic snapshot (HP/food/pos/task/connection)
|
|
LOG: "log", // free-form log line { level, source, text }
|
|
CHAT: "chat", // MC chat { from, text, kind: "player" | "system" }
|
|
DEATH: "death", // death event { reason, position }
|
|
ERROR: "error", // recoverable runtime error { source, text }
|
|
ASK_PI_CHUNK: "ask-pi-chunk", // streamed stdout chunk from Pi subprocess
|
|
ASK_PI_DONE: "ask-pi-done", // Pi subprocess exited { code, durationMs }
|
|
HELLO: "hello", // sent on client connect with current snapshot
|
|
});
|
|
|
|
// Client → server commands.
|
|
export const COMMAND_TYPES = Object.freeze({
|
|
PAUSE: "cmd:pause", // reflex loop stops ticking; connection stays
|
|
RESUME: "cmd:resume", // reflex loop resumes
|
|
STOP: "cmd:stop", // graceful disconnect + process exit
|
|
CHAT: "cmd:chat", // { text } sent into MC as bot
|
|
ASK_PI: "cmd:ask-pi", // { prompt } spawn `pi -p` and stream output
|
|
SNAPSHOT: "cmd:snapshot", // request immediate STATUS event
|
|
});
|
|
|
|
export function encodeFrame(obj) {
|
|
return JSON.stringify(obj) + "\n";
|
|
}
|
|
|
|
// Stateful line splitter — instance per socket.
|
|
export function createLineParser(onObject) {
|
|
let buf = "";
|
|
return (chunk) => {
|
|
buf += chunk.toString("utf8");
|
|
let idx;
|
|
while ((idx = buf.indexOf("\n")) !== -1) {
|
|
const line = buf.slice(0, idx).trim();
|
|
buf = buf.slice(idx + 1);
|
|
if (!line) continue;
|
|
try {
|
|
onObject(JSON.parse(line));
|
|
} catch (e) {
|
|
onObject({ __parseError: true, raw: line, err: String(e) });
|
|
}
|
|
}
|
|
};
|
|
}
|