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>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { config as loadDotenv } from "dotenv";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
export const REPO_ROOT = path.resolve(__dirname, "..");
|
||||
|
||||
loadDotenv({ path: path.join(REPO_ROOT, ".env") });
|
||||
|
||||
function req(name) {
|
||||
const v = process.env[name]?.trim();
|
||||
if (!v) throw new Error(`Missing required env var: ${name}`);
|
||||
return v;
|
||||
}
|
||||
|
||||
function opt(name, fallback = "") {
|
||||
return process.env[name]?.trim() || fallback;
|
||||
}
|
||||
|
||||
const host = req("MC_HOST");
|
||||
const port = Number.parseInt(opt("MC_PORT", "25565"), 10);
|
||||
const username = req("MC_USERNAME");
|
||||
|
||||
export const config = Object.freeze({
|
||||
host,
|
||||
port,
|
||||
username,
|
||||
version: opt("MC_VERSION", "1.21.5"),
|
||||
authMode: opt("MC_AUTH_MODE", "offline"),
|
||||
authmePassword: opt("MC_AUTHME_PASSWORD", ""),
|
||||
operators: opt("OPERATOR_USERNAMES", "")
|
||||
.split(",")
|
||||
.map((s) => s.trim().toLowerCase())
|
||||
.filter(Boolean),
|
||||
tickIntervalMs: Math.max(1, Number.parseInt(opt("TICK_INTERVAL_SECONDS", "3"), 10)) * 1000,
|
||||
chatRateLimitPerMin: Number.parseInt(opt("CHAT_RATE_LIMIT_PER_MIN", "15"), 10),
|
||||
});
|
||||
|
||||
export const serverKey = `${host}_${port}`;
|
||||
export const stateDir = path.join(REPO_ROOT, "state", serverKey);
|
||||
export const socketPath = path.join(stateDir, "bot.sock");
|
||||
|
||||
// Redacted env view for logs — never include the AuthMe password.
|
||||
export function redactedConfig() {
|
||||
const { authmePassword, ...rest } = config;
|
||||
return { ...rest, authmePassword: authmePassword ? "***" : "(unset)" };
|
||||
}
|
||||
Reference in New Issue
Block a user