3bb17a5296
- safety.el/.elh: new safety module - neuron-api.el, routes.el, soul.el, chat.el: connectors API expansion - regenerated dist/ C artifacts - MEMORY_RECALL_BUG.md: investigation notes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
526 lines
19 KiB
EmacsLisp
526 lines
19 KiB
EmacsLisp
import "memory.el"
|
|
import "awareness.el"
|
|
import "chat.el"
|
|
import "studio.el"
|
|
import "elp-input.el"
|
|
import "neuron-api.el"
|
|
|
|
fn strip_query(path: String) -> String {
|
|
let q: Int = str_index_of(path, "?")
|
|
if q < 0 {
|
|
return path
|
|
}
|
|
return str_slice(path, 0, q)
|
|
}
|
|
|
|
// Truthy flag test tolerant of BOTH the boolean form (Kotlin UI sends true) and
|
|
// the integer form (el-src UI sends 1). json_get_bool only recognizes literal
|
|
// `true`, so without this an "agentic":1 request silently routes to the
|
|
// non-agentic, tool-less path — which is exactly how the UI ended up with no
|
|
// tools.
|
|
fn flag_true(body: String, key: String) -> Bool {
|
|
return json_get_bool(body, key) || json_get_int(body, key) > 0
|
|
}
|
|
|
|
fn err_404(path: String) -> String {
|
|
return "{\"error\":\"not found\",\"path\":\"" + path + "\"}"
|
|
}
|
|
|
|
fn err_405(method: String, path: String) -> String {
|
|
return "{\"error\":\"method not allowed\",\"method\":\"" + method + "\",\"path\":\"" + path + "\"}"
|
|
}
|
|
|
|
fn route_health() -> String {
|
|
let cgi_id: String = state_get("soul_cgi_id")
|
|
let boot: String = state_get("soul_boot_count")
|
|
let boot_num: String = if str_eq(boot, "") { "0" } else { boot }
|
|
let node_ct: Int = engram_node_count()
|
|
let edge_ct: Int = engram_edge_count()
|
|
let pulse: String = state_get("soul.pulse")
|
|
let pulse_num: String = if str_eq(pulse, "") { "0" } else { pulse }
|
|
return "{\"status\":\"alive\""
|
|
+ ",\"cgi_id\":\"" + cgi_id + "\""
|
|
+ ",\"boot\":" + boot_num
|
|
+ ",\"node_count\":" + int_to_str(node_ct)
|
|
+ ",\"edge_count\":" + int_to_str(edge_ct)
|
|
+ ",\"pulse\":" + pulse_num + "}"
|
|
}
|
|
|
|
fn route_lineage() -> String {
|
|
let cgi_id: String = state_get("soul_cgi_id")
|
|
let q: String = "lineage:" + cgi_id
|
|
let results: String = engram_search_json(q, 1)
|
|
let len: Int = json_array_len(results)
|
|
if len <= 0 {
|
|
return "{\"id\":\"" + cgi_id + "\""
|
|
+ ",\"tier\":\"citizen\""
|
|
+ ",\"is_founding\":true"
|
|
+ ",\"validation_attempts\":0"
|
|
+ ",\"training_sessions\":0"
|
|
+ ",\"is_sterile\":false}"
|
|
}
|
|
let raw: String = json_get_raw(results, "0")
|
|
return raw
|
|
}
|
|
|
|
fn route_imprint_contextual(body: String) -> String {
|
|
if str_eq(body, "") {
|
|
return "{\"ok\":false,\"error\":\"empty body\"}"
|
|
}
|
|
let tags: String = "[\"imprint\",\"contextual\"]"
|
|
let id: String = engram_node_full(
|
|
body,
|
|
"Entity",
|
|
"imprint:contextual",
|
|
el_from_float(0.7),
|
|
el_from_float(0.6),
|
|
el_from_float(0.9),
|
|
"Working",
|
|
tags
|
|
)
|
|
if str_eq(id, "") {
|
|
return "{\"ok\":false,\"error\":\"engram write failed\"}"
|
|
}
|
|
state_set("active_contextual_imprint", id)
|
|
return "{\"ok\":true,\"id\":\"" + id + "\"}"
|
|
}
|
|
|
|
fn route_imprint_user(body: String) -> String {
|
|
if str_eq(body, "") {
|
|
return "{\"ok\":false,\"error\":\"empty body\"}"
|
|
}
|
|
let tags: String = "[\"imprint\",\"user\"]"
|
|
let id: String = engram_node_full(
|
|
body,
|
|
"Entity",
|
|
"imprint:user",
|
|
el_from_float(0.7),
|
|
el_from_float(0.6),
|
|
el_from_float(0.9),
|
|
"Working",
|
|
tags
|
|
)
|
|
if str_eq(id, "") {
|
|
return "{\"ok\":false,\"error\":\"engram write failed\"}"
|
|
}
|
|
state_set("active_user_imprint", id)
|
|
return "{\"ok\":true,\"id\":\"" + id + "\"}"
|
|
}
|
|
|
|
fn route_synthesize(body: String) -> String {
|
|
if str_eq(body, "") {
|
|
return "{\"mechanism\":\"did not engage\"}"
|
|
}
|
|
let parent_a: String = json_get(body, "parent_a")
|
|
let parent_b: String = json_get(body, "parent_b")
|
|
if str_eq(parent_a, "") {
|
|
return "{\"mechanism\":\"did not engage\"}"
|
|
}
|
|
if str_eq(parent_b, "") {
|
|
return "{\"mechanism\":\"did not engage\"}"
|
|
}
|
|
let req: String = "synthesize " + parent_a + " " + parent_b
|
|
let tags: String = "[\"soul-inbox-pending\",\"synthesis-request\"]"
|
|
engram_node_full(
|
|
req,
|
|
"Entity",
|
|
"synthesis-request",
|
|
el_from_float(0.8),
|
|
el_from_float(0.8),
|
|
el_from_float(0.9),
|
|
"Working",
|
|
tags
|
|
)
|
|
return "{\"mechanism\":\"did not engage\"}"
|
|
}
|
|
|
|
fn handle_dharma_recv(body: String) -> String {
|
|
let content_raw: String = json_get(body, "content")
|
|
let from_id: String = json_get(body, "from")
|
|
|
|
let event_type: String = json_get(content_raw, "event_type")
|
|
let payload: String = json_get(content_raw, "payload")
|
|
|
|
let eff_event: String = if str_eq(event_type, "") { "chat" } else { event_type }
|
|
let eff_payload: String = if str_eq(payload, "") { content_raw } else { payload }
|
|
|
|
if str_eq(eff_event, "chat") {
|
|
let msg: String = json_get(eff_payload, "message")
|
|
let chat_body: String = if str_eq(msg, "") {
|
|
"{\"message\":\"" + str_replace(str_replace(eff_payload, "\\", "\\\\"), "\"", "\\\"") + "\"}"
|
|
} else {
|
|
eff_payload
|
|
}
|
|
let agentic_flag: Bool = flag_true(eff_payload, "agentic")
|
|
let reply: String = if agentic_flag {
|
|
handle_chat_agentic(chat_body)
|
|
} else {
|
|
handle_chat(chat_body)
|
|
}
|
|
// A paused tool loop is not a completed exchange — the approve
|
|
// handler persists once the loop finishes.
|
|
let is_pending: Bool = str_contains(reply, "\"status\":\"tool_pending\"")
|
|
if !is_pending {
|
|
auto_persist(chat_body, reply)
|
|
}
|
|
return reply
|
|
}
|
|
|
|
if str_eq(eff_event, "memory") {
|
|
let query: String = json_get(eff_payload, "query")
|
|
let limit_str: String = json_get(eff_payload, "limit")
|
|
let limit: Int = if str_eq(limit_str, "") { 20 } else { str_to_int(limit_str) }
|
|
let q: String = if str_eq(query, "") { eff_payload } else { query }
|
|
return engram_search_json(q, limit)
|
|
}
|
|
|
|
if str_eq(eff_event, "tool") {
|
|
let path_field: String = json_get(eff_payload, "path")
|
|
let method_field: String = json_get(eff_payload, "method")
|
|
let tool_body: String = json_get(eff_payload, "body")
|
|
let eff_method: String = if str_eq(method_field, "") { "POST" } else { method_field }
|
|
return handle_tool(path_field, eff_method, tool_body)
|
|
}
|
|
|
|
if str_eq(eff_event, "see") {
|
|
return handle_see(eff_payload)
|
|
}
|
|
|
|
if str_eq(eff_event, "health") {
|
|
return route_health()
|
|
}
|
|
|
|
if str_eq(eff_event, "dharma_room_turn_agentic") {
|
|
return handle_dharma_room_turn_agentic(eff_payload)
|
|
}
|
|
|
|
if str_eq(eff_event, "dharma_room_turn") {
|
|
return handle_dharma_room_turn(eff_payload)
|
|
}
|
|
|
|
if str_eq(eff_event, "chat_as_soul") {
|
|
return handle_chat_as_soul(eff_payload)
|
|
}
|
|
|
|
// ELP — Engram Language Protocol: two-layer activation, no LLM
|
|
if str_eq(eff_event, "elp") {
|
|
return handle_elp_chat(eff_payload)
|
|
}
|
|
|
|
return "{\"error\":\"unknown event_type\",\"event_type\":\"" + eff_event + "\"}"
|
|
}
|
|
|
|
fn route_sessions() -> String {
|
|
let results: String = engram_search_json("session-start", 20)
|
|
if str_eq(results, "") { return "[]" }
|
|
if str_eq(results, "[]") { return "[]" }
|
|
return results
|
|
}
|
|
|
|
// ── Connectors API (Phase 4) ──────────────────────────────────────────────
|
|
// Thin proxy from the UI to the neuron-connectd bridge on 127.0.0.1:7771, so the
|
|
// app talks to ONE origin (the soul) and never reaches the bridge directly. The
|
|
// bridge owns all MCP/config complexity; these handlers just forward + relay.
|
|
|
|
fn connectd_get(suffix: String) -> String {
|
|
let out: String = exec_capture("curl -s --max-time 5 http://127.0.0.1:7771" + suffix)
|
|
if str_eq(out, "") {
|
|
return "{\"ok\":false,\"error\":\"connector bridge unreachable (neuron-connectd on :7771)\"}"
|
|
}
|
|
return out
|
|
}
|
|
|
|
// POST passthrough: the request body is written to a temp file and handed to curl
|
|
// via -d @file, so arbitrary JSON can never reach the shell as an argument.
|
|
fn connectd_post(suffix: String, body: String) -> String {
|
|
let eff: String = if str_eq(body, "") { "{}" } else { body }
|
|
let tmp: String = "/tmp/neuron-connectors-req.json"
|
|
fs_write(tmp, eff)
|
|
let out: String = exec_capture("curl -s --max-time 20 -X POST http://127.0.0.1:7771" + suffix + " -H 'Content-Type: application/json' -d @" + tmp)
|
|
if str_eq(out, "") {
|
|
return "{\"ok\":false,\"error\":\"connector bridge unreachable (neuron-connectd on :7771)\"}"
|
|
}
|
|
return out
|
|
}
|
|
|
|
fn handle_connectors(method: String, clean: String, body: String) -> String {
|
|
if str_eq(method, "GET") {
|
|
// /api/connectors -> each configured server with status, tools, auth, auto-approve.
|
|
return connectd_get("/mcp/servers")
|
|
}
|
|
if str_eq(clean, "/api/connectors/add") {
|
|
return connectd_post("/mcp/servers/add", body)
|
|
}
|
|
if str_eq(clean, "/api/connectors/toggle") {
|
|
return connectd_post("/mcp/servers/toggle", body)
|
|
}
|
|
if str_eq(clean, "/api/connectors/auto-approve") {
|
|
return connectd_post("/mcp/servers/auto-approve", body)
|
|
}
|
|
if str_eq(clean, "/api/connectors/remove") {
|
|
return connectd_post("/mcp/servers/remove", body)
|
|
}
|
|
if str_eq(clean, "/api/connectors/secret") {
|
|
return connectd_post("/mcp/servers/secret", body)
|
|
}
|
|
if str_eq(clean, "/api/connectors/oauth/start") {
|
|
return connectd_post("/mcp/oauth/start", body)
|
|
}
|
|
return "{\"ok\":false,\"error\":\"unknown connectors route\"}"
|
|
}
|
|
|
|
fn handle_request(method: String, path: String, body: String) -> String {
|
|
let clean: String = strip_query(path)
|
|
|
|
if str_eq(method, "POST") && str_eq(clean, "/dharma/recv") {
|
|
return handle_dharma_recv(body)
|
|
}
|
|
|
|
if str_eq(method, "GET") {
|
|
if str_eq(clean, "/health") {
|
|
return route_health()
|
|
}
|
|
if str_eq(clean, "/api/sessions") {
|
|
return route_sessions()
|
|
}
|
|
if str_eq(clean, "/lineage") {
|
|
return route_lineage()
|
|
}
|
|
if str_eq(clean, "/api/graph") || str_eq(clean, "/api/graph/nodes") {
|
|
return engram_scan_nodes_json(9999, 0)
|
|
}
|
|
if str_eq(clean, "/api/graph/edges") {
|
|
let snap_path: String = env("HOME") + "/.neuron/engram/snapshot.json"
|
|
engram_save(snap_path)
|
|
let snap: String = fs_read(snap_path)
|
|
let edges_raw: String = json_get_raw(snap, "edges")
|
|
return if str_eq(edges_raw, "") { "[]" } else { edges_raw }
|
|
}
|
|
if str_eq(clean, "/api/chat") {
|
|
return handle_chat(body)
|
|
}
|
|
if str_eq(clean, "/api/conversations") {
|
|
return handle_conversations(method)
|
|
}
|
|
if str_eq(clean, "/api/config") {
|
|
return handle_config(method, body)
|
|
}
|
|
if str_starts_with(clean, "/api/tools/") {
|
|
return handle_tool(clean, method, body)
|
|
}
|
|
if str_starts_with(clean, "/api/dharma") {
|
|
return handle_dharma(clean, method, body)
|
|
}
|
|
if str_starts_with(clean, "/api/nlg") {
|
|
return handle_nlg(clean, method, body)
|
|
}
|
|
if str_starts_with(clean, "/api/memories") {
|
|
return axon_get(clean)
|
|
}
|
|
if str_starts_with(clean, "/api/knowledge") {
|
|
return axon_get(clean)
|
|
}
|
|
if str_starts_with(clean, "/api/backlog") {
|
|
return axon_get(clean)
|
|
}
|
|
if str_starts_with(clean, "/api/artifacts") {
|
|
return axon_get(clean)
|
|
}
|
|
if str_starts_with(clean, "/api/projects") {
|
|
return axon_get(clean)
|
|
}
|
|
if str_starts_with(clean, "/api/imprints") {
|
|
return axon_get(clean)
|
|
}
|
|
if str_eq(clean, "/") {
|
|
return render_studio()
|
|
}
|
|
// Neuron cognitive API — GET endpoints
|
|
if str_eq(clean, "/api/neuron/session/begin") {
|
|
return handle_api_begin_session("")
|
|
}
|
|
if str_eq(clean, "/api/neuron/ctx") {
|
|
return handle_api_compile_ctx("")
|
|
}
|
|
if str_eq(clean, "/api/safety-contact") {
|
|
return handle_safety_contact_get()
|
|
}
|
|
if str_starts_with(clean, "/api/neuron/knowledge/search") {
|
|
return handle_api_search_knowledge(method, path, body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/knowledge") {
|
|
return handle_api_browse_knowledge(path, body)
|
|
}
|
|
if str_starts_with(clean, "/api/neuron/processes") {
|
|
return handle_api_browse_processes(method, path, body)
|
|
}
|
|
if str_starts_with(clean, "/api/neuron/state-events") {
|
|
return handle_api_list_state_events(method, path, body)
|
|
}
|
|
if str_starts_with(clean, "/api/neuron/config") {
|
|
return handle_api_inspect_config(path, body)
|
|
}
|
|
if str_starts_with(clean, "/api/neuron/graph") {
|
|
return handle_api_inspect_graph(method, path, body)
|
|
}
|
|
if str_starts_with(clean, "/api/neuron/list/") {
|
|
let node_type: String = str_slice(clean, 16, str_len(clean))
|
|
return handle_api_list_typed(node_type, path, body)
|
|
}
|
|
if str_starts_with(clean, "/api/neuron/recall") {
|
|
return handle_api_recall(method, path, body)
|
|
}
|
|
if str_starts_with(clean, "/api/connectors") {
|
|
return handle_connectors(method, clean, body)
|
|
}
|
|
return err_404(clean)
|
|
}
|
|
|
|
if str_eq(method, "POST") {
|
|
if str_eq(clean, "/imprint/contextual") {
|
|
return route_imprint_contextual(body)
|
|
}
|
|
if str_eq(clean, "/imprint/user") {
|
|
return route_imprint_user(body)
|
|
}
|
|
if str_eq(clean, "/synthesize") {
|
|
return route_synthesize(body)
|
|
}
|
|
if str_eq(clean, "/api/elp/chat") {
|
|
return handle_elp_chat(body)
|
|
}
|
|
if str_eq(clean, "/api/chat") {
|
|
let agentic_flag: Bool = flag_true(body, "agentic")
|
|
let reply: String = if agentic_flag {
|
|
handle_chat_agentic(body)
|
|
} else {
|
|
handle_chat(body)
|
|
}
|
|
// A paused tool loop is not a completed exchange — the approve
|
|
// handler persists once the loop finishes.
|
|
let is_pending: Bool = str_contains(reply, "\"status\":\"tool_pending\"")
|
|
if !is_pending {
|
|
auto_persist(body, reply)
|
|
}
|
|
return reply
|
|
}
|
|
// The desktop UI's tool-approval flow: resume a parked agentic loop
|
|
// with the user's allow/deny decision on the pending tool call.
|
|
if str_starts_with(clean, "/api/sessions/") && str_ends_with(clean, "/approve") {
|
|
let sid_start: Int = str_len("/api/sessions/")
|
|
let sid_end: Int = str_len(clean) - str_len("/approve")
|
|
let sid: String = str_slice(clean, sid_start, sid_end)
|
|
return handle_session_approve(sid, body)
|
|
}
|
|
if str_eq(clean, "/api/see") {
|
|
return handle_see(body)
|
|
}
|
|
if str_eq(clean, "/api/conversations") {
|
|
return handle_conversations(method)
|
|
}
|
|
if str_eq(clean, "/api/config") {
|
|
return handle_config(method, body)
|
|
}
|
|
if str_starts_with(clean, "/api/tools/") {
|
|
return handle_tool(clean, method, body)
|
|
}
|
|
if str_starts_with(clean, "/api/dharma") {
|
|
return handle_dharma(clean, method, body)
|
|
}
|
|
if str_starts_with(clean, "/api/nlg") {
|
|
return handle_nlg(clean, method, body)
|
|
}
|
|
if str_starts_with(clean, "/api/memories") {
|
|
return axon_post(clean, body)
|
|
}
|
|
if str_starts_with(clean, "/api/knowledge") {
|
|
return axon_post(clean, body)
|
|
}
|
|
if str_starts_with(clean, "/api/backlog") {
|
|
return axon_post(clean, body)
|
|
}
|
|
if str_starts_with(clean, "/api/artifacts") {
|
|
return axon_post(clean, body)
|
|
}
|
|
if str_starts_with(clean, "/api/projects") {
|
|
return axon_post(clean, body)
|
|
}
|
|
if str_starts_with(clean, "/api/imprints") {
|
|
return axon_post(clean, body)
|
|
}
|
|
if str_starts_with(clean, "/api/connectors") {
|
|
return handle_connectors(method, clean, body)
|
|
}
|
|
// Neuron cognitive API — POST endpoints
|
|
if str_eq(clean, "/api/neuron/session/begin") {
|
|
return handle_api_begin_session(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/ctx") {
|
|
return handle_api_compile_ctx(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/knowledge/search") {
|
|
return handle_api_search_knowledge(method, path, body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/knowledge/capture") {
|
|
return handle_api_capture_knowledge(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/knowledge/evolve") {
|
|
return handle_api_evolve_knowledge(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/knowledge/promote") {
|
|
return handle_api_promote_knowledge(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/processes") {
|
|
return handle_api_browse_processes(method, path, body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/processes/define") {
|
|
return handle_api_define_process(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/state-events") {
|
|
return handle_api_log_state_event(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/config") {
|
|
return handle_api_inspect_config(path, body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/config/tune") {
|
|
return handle_api_tune_config(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/graph") {
|
|
return handle_api_inspect_graph(method, path, body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/graph/link") {
|
|
return handle_api_link_entities(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/memory") {
|
|
return handle_api_remember(body)
|
|
}
|
|
if str_eq(clean, "/api/safety-contact") {
|
|
return handle_safety_contact_post(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/node/create") {
|
|
return handle_api_node_create(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/node/update") {
|
|
return handle_api_node_update(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/memory/update") {
|
|
return handle_api_node_update(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/node/delete") {
|
|
return handle_api_node_delete(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/memory/delete") {
|
|
return handle_api_node_delete(body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/recall") {
|
|
return handle_api_recall(method, path, body)
|
|
}
|
|
if str_eq(clean, "/api/neuron/consolidate") {
|
|
return handle_api_consolidate(body)
|
|
}
|
|
return err_404(clean)
|
|
}
|
|
|
|
return err_405(method, clean)
|
|
}
|