b77f537dc6
- neuron-runtime, neuron-store, neuron-migrate: fix path deps (products/engram/ → foundation/engram/engrams/, products/el/ → foundation/el/engrams/) - neuron-rs workspace: fix axon-events/axon-protocol paths (../../../platform/ → ../../platform/, paths were off by one level) - neuron-lang/compiler/ removed (el self-hosting compiler now lives in foundation/el/el-compiler/)
140 lines
5.7 KiB
EmacsLisp
140 lines
5.7 KiB
EmacsLisp
// main-user.el — Neuron daemon entry point. User build.
|
|
// Written in Engram.
|
|
//
|
|
// Principal identity is a compile-time constant — NOT read from config.
|
|
// This file is the user build. The developer build is main.el.
|
|
// Built with: el build --manifest el-user.toml
|
|
//
|
|
// Responsibilities:
|
|
// 1. Write PID file to ~/.neuron-user/data/daemon.pid
|
|
// 2. Start API/proxy server on :7750 (blocking)
|
|
// - Proxies /axon/, /api/ routes to neuronrs at :7770
|
|
// - Health check at /health
|
|
//
|
|
// The handle_request function is called by http_serve for every request.
|
|
|
|
import "daemon_config.el"
|
|
import "proxy.el"
|
|
import "health.el"
|
|
import "loop.el"
|
|
import "plugins/host.el"
|
|
|
|
// ── Principal identity (baked in at compile time) ─────────────────────────────
|
|
// This literal is compiled into the bytecode. For prod builds it is sealed
|
|
// inside AES-256-GCM — cannot be changed without the deployment key.
|
|
|
|
let principal: String = "user"
|
|
|
|
// ── Load operational config ───────────────────────────────────────────────────
|
|
|
|
let cfg: String = load_config()
|
|
let axon_base: String = config_api_url(cfg)
|
|
let token: String = config_api_token(cfg)
|
|
let ui_dir: String = config_ui_dir(cfg)
|
|
let data_dir: String = config_data_dir(cfg)
|
|
let port: Int = config_port(cfg)
|
|
state_set("neuron_principal", principal)
|
|
|
|
// ── Write PID file ────────────────────────────────────────────────────────────
|
|
|
|
let pid: Int = getpid()
|
|
let pid_str: String = int_to_str(pid)
|
|
fs_mkdir(data_dir)
|
|
let pid_path: String = data_dir + "/daemon.pid"
|
|
fs_write(pid_path, pid_str)
|
|
|
|
println(color_bold("Neuron daemon") + " — pid " + pid_str + " — " + principal)
|
|
println(" API → http://localhost:" + int_to_str(port))
|
|
println(" Axon → " + axon_base)
|
|
println(" Data → " + data_dir)
|
|
println("")
|
|
|
|
// ── Request handler (API server on :7750) ─────────────────────────────────────
|
|
//
|
|
// Called by http_serve for every incoming request.
|
|
// Must be named exactly "handle_request" — http_serve looks it up by that name.
|
|
|
|
fn handle_request(method: String, path: String, body: String) -> String {
|
|
// Health check
|
|
if str_eq(path, "/health") {
|
|
return health_response()
|
|
}
|
|
|
|
// Axon tool dispatch — proxy to neuronrs
|
|
if str_starts_with(path, "/axon/") {
|
|
return proxy_request(axon_base, method, path, body, token)
|
|
}
|
|
|
|
// Intelligence REST API — proxy to neuronrs
|
|
if str_starts_with(path, "/api/memories") {
|
|
return proxy_request(axon_base, method, path, body, token)
|
|
}
|
|
if str_starts_with(path, "/api/knowledge") {
|
|
return proxy_request(axon_base, method, path, body, token)
|
|
}
|
|
if str_starts_with(path, "/api/backlog") {
|
|
return proxy_request(axon_base, method, path, body, token)
|
|
}
|
|
if str_starts_with(path, "/api/contexts") {
|
|
return proxy_request(axon_base, method, path, body, token)
|
|
}
|
|
if str_starts_with(path, "/api/ise") {
|
|
return proxy_request(axon_base, method, path, body, token)
|
|
}
|
|
|
|
// ── Plugin status ─────────────────────────────────────────────────────────
|
|
if str_eq(path, "/plugin/status") {
|
|
return host_status()
|
|
}
|
|
|
|
// ── Runtime loop control ──────────────────────────────────────────────────
|
|
if str_eq(path, "/loop/status") {
|
|
return loop_status_json()
|
|
}
|
|
if str_eq(path, "/loop/signal") {
|
|
let sig: String = json_get(body, "signal")
|
|
if str_eq(sig, "") {
|
|
return "{\"error\":\"missing signal\"}"
|
|
}
|
|
state_set("loop_signal", sig)
|
|
return "{\"ok\":true,\"signal\":\"" + sig + "\"}"
|
|
}
|
|
if str_eq(path, "/loop/tier") {
|
|
let new_tier: String = json_get(body, "tier")
|
|
let new_min: String = json_get(body, "min_tier")
|
|
if !str_eq(new_min, "") {
|
|
state_set("loop_min_tier", new_min)
|
|
}
|
|
if !str_eq(new_tier, "") {
|
|
state_set("loop_override_tier", new_tier)
|
|
}
|
|
return "{\"ok\":true,\"tier\":\"" + new_tier + "\",\"min_tier\":\"" + new_min + "\"}"
|
|
}
|
|
|
|
return not_found_response(path)
|
|
}
|
|
|
|
// ── Initialise plugin host ────────────────────────────────────────────────────
|
|
|
|
host_on_startup()
|
|
|
|
// ── Initialise loop state ─────────────────────────────────────────────────────
|
|
|
|
state_set("loop_tier", "watching")
|
|
state_set("loop_min_tier", "resting")
|
|
state_set("loop_ticks", "0")
|
|
state_set("loop_bell_fires", "0")
|
|
state_set("loop_tier_changes", "0")
|
|
state_set("loop_idle_ticks", "0")
|
|
state_set("loop_signal", "")
|
|
state_set("loop_override_tier", "")
|
|
state_set("loop_last_tick_idle", "0")
|
|
|
|
// ── Spawn the heartbeat thread ────────────────────────────────────────────────
|
|
|
|
spawn_thread("loop_main")
|
|
|
|
// ── Start API server (blocking) ───────────────────────────────────────────────
|
|
|
|
http_serve(port)
|