fix cross-repo path deps; remove el compiler from neuron-lang/

- 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/)
This commit is contained in:
2026-04-29 03:27:39 -05:00
parent 78fc3a909a
commit b77f537dc6
54 changed files with 8065 additions and 2420 deletions
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "neuron-runtime"
version = "0.1.0"
edition = "2021"
[dependencies]
# el crates (path deps)
el-compiler = { path = "../../../../foundation/el/engrams/el-compiler" }
# engram core (for EngramDb type)
engram-core = { path = "../../../../foundation/engram/engrams/engram-core" }
# neuron-rs workspace crates
neuron-domain = { path = "../neuron-domain" }
neuron-store = { path = "../neuron-store" }
axon-events = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
uuid = { workspace = true }
chrono = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
thiserror = { workspace = true }
anyhow = { workspace = true }
ureq = { version = "2", features = ["json"] }
crossterm = "0.28"
[dev-dependencies]
tempfile = "3"
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
//! neuron-runtime — Execution harness for el bytecode.
//!
//! Loads compiled `.el` modules, executes them on the el VM,
//! and bridges native function calls into neuron-store and Axon events.
pub mod bindings;
pub mod loader;
pub mod registry;
pub mod runtime;
pub mod vm;
pub use registry::ToolRegistry;
pub use runtime::NeuronRuntime;
+165
View File
@@ -0,0 +1,165 @@
//! .el source compiler and .elc bytecode loader.
use std::collections::HashMap;
use std::path::Path;
use el_compiler::{Bytecode, Compiler, CompilerOptions, deserialize_bytecode};
use thiserror::Error;
// ── RuntimeError (load/compile phase) ────────────────────────────────────────
#[derive(Debug, Error)]
pub enum LoadError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("compile error for {module}: {detail}")]
Compile { module: String, detail: String },
#[error("deserialize error: {0}")]
Deserialize(String),
}
// ── compile_source ────────────────────────────────────────────────────────────
/// Compile an el source string to bytecode instructions.
pub fn compile_source(source: &str) -> Result<Vec<Bytecode>, LoadError> {
let output = Compiler::compile(source, CompilerOptions::default())
.map_err(|e| LoadError::Compile {
module: "<source>".into(),
detail: e.to_string(),
})?;
deserialize_bytecode(&output.artifact).map_err(|e| LoadError::Compile {
module: "<source>".into(),
detail: e,
})
}
// ── load_elc ─────────────────────────────────────────────────────────────────
/// Load pre-compiled .elc file (bytecode JSON) from disk.
pub fn load_elc(path: &Path) -> Result<Vec<Bytecode>, LoadError> {
let bytes = std::fs::read(path)?;
deserialize_bytecode(&bytes).map_err(LoadError::Deserialize)
}
// ── load_directory ────────────────────────────────────────────────────────────
/// Load and compile all `.el` files from a directory (non-recursive).
///
/// Each file is compiled independently; the map key is the filename stem
/// (e.g. `memory.el` → `"memory"`). `.elc` files are also accepted and
/// loaded without recompilation.
///
/// Files that fail to compile are skipped with a warning rather than causing
/// the entire load to fail — callers can detect missing modules at dispatch
/// time.
pub fn load_directory(dir: &Path) -> Result<HashMap<String, Vec<Bytecode>>, LoadError> {
let mut modules: HashMap<String, Vec<Bytecode>> = HashMap::new();
let entries = std::fs::read_dir(dir)?;
for entry in entries {
let entry = entry?;
let path = entry.path();
if !path.is_file() {
continue;
}
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
let stem = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_string();
if stem.is_empty() {
continue;
}
let result = match ext {
"el" => {
let source = std::fs::read_to_string(&path)?;
compile_source(&source).map_err(|e| {
tracing::warn!("skipping {}: {}", path.display(), e);
e
})
}
"elc" => load_elc(&path).map_err(|e| {
tracing::warn!("skipping {}: {}", path.display(), e);
e
}),
_ => continue,
};
match result {
Ok(bytecode) => {
modules.insert(stem, bytecode);
}
Err(_) => {
// Already warned above; continue loading other modules.
}
}
}
Ok(modules)
}
// ── Tests ─────────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
use el_compiler::{Bytecode, Value};
#[test]
fn test_compile_source_arithmetic() {
// A simple expression that the compiler can handle via eval semantics.
let instructions = compile_source("1 + 2").unwrap();
assert!(!instructions.is_empty(), "should produce instructions");
}
#[test]
fn test_compile_source_invalid() {
// Guaranteed to fail (unbalanced paren).
let result = compile_source("(((");
assert!(result.is_err());
}
#[test]
fn test_load_elc_roundtrip() {
use tempfile::NamedTempFile;
let instructions = vec![
Bytecode::Push(Value::Int(1)),
Bytecode::Push(Value::Int(2)),
Bytecode::Add,
Bytecode::Halt,
];
let bytes = el_compiler::serialize_bytecode(&instructions).unwrap();
let mut tmp = NamedTempFile::with_suffix(".elc").unwrap();
std::io::Write::write_all(&mut tmp, &bytes).unwrap();
let loaded = load_elc(tmp.path()).unwrap();
assert_eq!(loaded, instructions);
}
#[test]
fn test_load_directory_empty() {
let dir = tempfile::tempdir().unwrap();
let modules = load_directory(dir.path()).unwrap();
assert!(modules.is_empty());
}
#[test]
fn test_load_directory_with_elc() {
use tempfile::tempdir;
let dir = tempdir().unwrap();
let instructions = vec![Bytecode::Push(Value::Int(42)), Bytecode::Halt];
let bytes = el_compiler::serialize_bytecode(&instructions).unwrap();
std::fs::write(dir.path().join("mymod.elc"), &bytes).unwrap();
let modules = load_directory(dir.path()).unwrap();
assert!(modules.contains_key("mymod"));
assert_eq!(modules["mymod"], instructions);
}
}
+232
View File
@@ -0,0 +1,232 @@
//! Tool → el function mapping.
//!
//! The registry maps Axon tool names (e.g. `"remember"`) to the module and
//! function name in the loaded `.el` bytecode (e.g. `("memory", "remember")`).
use std::collections::HashMap;
// ── ToolRegistry ──────────────────────────────────────────────────────────────
/// Maps Axon tool names to `(module_name, function_name)` pairs.
pub struct ToolRegistry {
entries: HashMap<String, (String, String)>,
}
impl ToolRegistry {
/// Create an empty registry.
pub fn new() -> Self {
Self {
entries: HashMap::new(),
}
}
/// Register the default Neuron tool → function mapping.
pub fn default_neuron() -> Self {
let mut r = Self::new();
// ── Memory ────────────────────────────────────────────────────────────
r.register("remember", "memory", "remember");
r.register("recall", "memory", "recall");
r.register("search_memories", "memory", "search_memories");
r.register("inspect_memories", "memory", "list_memories");
r.register("forget", "memory", "forget");
r.register("evolve_memory", "memory", "promote_memory");
// ── Knowledge ─────────────────────────────────────────────────────────
r.register("capture_knowledge", "knowledge", "capture_knowledge");
r.register("retrieve_knowledge", "knowledge", "retrieve_knowledge");
r.register("search_knowledge", "knowledge", "search_knowledge");
r.register("browse_knowledge", "knowledge", "browse_knowledge");
r.register("evolve_knowledge", "knowledge", "evolve_knowledge");
r.register("remove_knowledge", "knowledge", "remove_knowledge");
// ── Backlog ───────────────────────────────────────────────────────────
r.register("plan_work", "backlog", "plan_work");
r.register("review_backlog", "backlog", "review_backlog");
r.register("track_work", "backlog", "track_work");
// ── Context ───────────────────────────────────────────────────────────
r.register("begin_work", "context", "begin_work");
r.register("progress_work", "context", "progress_work");
r.register("check_work", "context", "check_work");
r.register("list_work", "context", "list_work");
// ── Artifact ──────────────────────────────────────────────────────────
r.register("draft_artifact", "artifact", "draft_artifact");
r.register("find_artifacts", "artifact", "find_artifacts");
r.register("retrieve_artifact", "artifact", "retrieve_artifact");
r.register("revise_artifact", "artifact", "revise_artifact");
r.register("manage_artifact", "artifact", "manage_artifact");
// ── ISE ───────────────────────────────────────────────────────────────
r.register("log_internal_state_event", "ise", "log_internal_state");
r.register("list_internal_state_events", "ise", "list_internal_state");
r.register("get_internal_state_event", "ise", "get_internal_state");
// ── Config ────────────────────────────────────────────────────────────
r.register("inspect_config", "config", "inspect_config");
r.register("tune_config", "config", "tune_config");
// ── Graph ─────────────────────────────────────────────────────────────
r.register("inspect_graph", "graph", "inspect_graph");
r.register("traverse_graph", "graph", "traverse_graph");
r.register("link_entities", "graph", "link_entities");
r.register("search_graph", "graph", "search_graph");
// ── Process ───────────────────────────────────────────────────────────
r.register("define_process", "process", "define_process");
r.register("browse_processes", "process", "browse_processes");
// ── Session ───────────────────────────────────────────────────────────
r.register("begin_session", "session", "begin_session");
r.register("compile_ctx", "session", "compile_ctx");
r.register("consolidate", "session", "consolidate");
// ── Inference / Chat ──────────────────────────────────────────────────
// Routes to native_chat binding — calls neuron.neurontechnologies.ai
// (OpenAI-compatible). For user installs: NEURON_INFERENCE_KEY=their key.
// For dev installs: override NEURON_INFERENCE_URL + NEURON_INFERENCE_KEY.
r.register("chat", "chat", "chat");
// ── Autonomous agent ──────────────────────────────────────────────────
r.register("agent_tick", "agent", "agent_tick");
r
}
/// Register a single tool → (module, function) mapping.
pub fn register(&mut self, tool: &str, module: &str, func: &str) {
self.entries
.insert(tool.to_string(), (module.to_string(), func.to_string()));
}
/// Resolve a tool name to `(module, function)`. Returns `None` if not
/// registered.
pub fn resolve(&self, tool_name: &str) -> Option<(&str, &str)> {
self.entries
.get(tool_name)
.map(|(m, f)| (m.as_str(), f.as_str()))
}
/// Number of registered tools.
pub fn len(&self) -> usize {
self.entries.len()
}
/// True if no tools are registered.
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
/// Iterate over all registered entries.
pub fn iter(&self) -> impl Iterator<Item = (&str, &str, &str)> {
self.entries
.iter()
.map(|(tool, (module, func))| (tool.as_str(), module.as_str(), func.as_str()))
}
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::new()
}
}
// ── Tests ─────────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_resolve_memory() {
let r = ToolRegistry::default_neuron();
assert_eq!(r.resolve("remember"), Some(("memory", "remember")));
}
#[test]
fn test_registry_resolve_knowledge() {
let r = ToolRegistry::default_neuron();
assert_eq!(
r.resolve("capture_knowledge"),
Some(("knowledge", "capture_knowledge"))
);
assert_eq!(
r.resolve("search_knowledge"),
Some(("knowledge", "search_knowledge"))
);
}
#[test]
fn test_registry_resolve_backlog() {
let r = ToolRegistry::default_neuron();
assert_eq!(r.resolve("plan_work"), Some(("backlog", "plan_work")));
assert_eq!(
r.resolve("review_backlog"),
Some(("backlog", "review_backlog"))
);
assert_eq!(r.resolve("track_work"), Some(("backlog", "track_work")));
}
#[test]
fn test_registry_resolve_context() {
let r = ToolRegistry::default_neuron();
assert_eq!(r.resolve("begin_work"), Some(("context", "begin_work")));
assert_eq!(
r.resolve("progress_work"),
Some(("context", "progress_work"))
);
}
#[test]
fn test_registry_resolve_unknown() {
let r = ToolRegistry::default_neuron();
assert_eq!(r.resolve("totally_unknown_tool"), None);
}
#[test]
fn test_registry_custom_register() {
let mut r = ToolRegistry::new();
r.register("my_tool", "my_module", "my_func");
assert_eq!(r.resolve("my_tool"), Some(("my_module", "my_func")));
}
#[test]
fn test_registry_default_neuron_not_empty() {
let r = ToolRegistry::default_neuron();
assert!(!r.is_empty());
assert!(r.len() > 20);
}
#[test]
fn test_registry_ise_tools() {
let r = ToolRegistry::default_neuron();
assert_eq!(
r.resolve("log_internal_state_event"),
Some(("ise", "log_internal_state"))
);
}
#[test]
fn test_registry_artifact_tools() {
let r = ToolRegistry::default_neuron();
assert_eq!(
r.resolve("draft_artifact"),
Some(("artifact", "draft_artifact"))
);
assert_eq!(
r.resolve("retrieve_artifact"),
Some(("artifact", "retrieve_artifact"))
);
}
#[test]
fn test_registry_all_tools_unique_modules() {
let r = ToolRegistry::default_neuron();
let memory_tools: Vec<_> = r
.iter()
.filter(|(_, module, _)| *module == "memory")
.collect();
assert!(!memory_tools.is_empty());
}
}
+375
View File
@@ -0,0 +1,375 @@
//! Top-level NeuronRuntime — loads modules and dispatches Axon tool calls.
use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use el_compiler::Bytecode;
use serde_json::Value;
use tracing::{debug, warn};
use crate::bindings::NativeBindings;
use crate::loader::{self, LoadError};
use crate::registry::ToolRegistry;
use crate::vm::{el_value_to_json, json_to_el_value, Vm};
use engram_core::EngramDb;
use neuron_store::{BacklogStore, ContextStore, IseStore, KnowledgeStore, MemoryStore, open_db};
// ── RuntimeError ──────────────────────────────────────────────────────────────
#[derive(Debug, thiserror::Error)]
pub enum RuntimeError {
#[error("load error: {0}")]
Load(#[from] LoadError),
#[error("vm error: {0}")]
Vm(#[from] crate::vm::VmError),
#[error("storage error: {0}")]
Storage(String),
}
// ── NeuronRuntime ─────────────────────────────────────────────────────────────
/// The main neuron runtime — holds loaded bytecode modules and dispatches
/// Axon tool calls through the el VM.
pub struct NeuronRuntime {
bindings: Arc<NativeBindings>,
registry: ToolRegistry,
/// module_name → compiled bytecode
modules: HashMap<String, Vec<Bytecode>>,
}
impl NeuronRuntime {
/// Create a runtime with pre-built components (for testing).
pub fn from_parts(
bindings: Arc<NativeBindings>,
registry: ToolRegistry,
modules: HashMap<String, Vec<Bytecode>>,
) -> Self {
Self {
bindings,
registry,
modules,
}
}
/// Create a runtime from an already-open `EngramDb` handle and a module directory.
///
/// The caller opens sled once and passes a clone here; no second `sled::open`
/// call is made, so the single-writer lock is never contested.
pub fn from_dir(
module_dir: &Path,
db: EngramDb,
) -> Result<Self, RuntimeError> {
let memory = Arc::new(MemoryStore::new(db.clone()));
let knowledge = Arc::new(KnowledgeStore::new(db.clone()));
let backlog = Arc::new(BacklogStore::new(db.clone()));
let context = Arc::new(ContextStore::new(db.clone()));
let ise = Arc::new(IseStore::new(db));
let bindings = Arc::new(NativeBindings::new(memory, knowledge, backlog, context, ise));
let registry = ToolRegistry::default_neuron();
let modules = loader::load_directory(module_dir)?;
Ok(Self {
bindings,
registry,
modules,
})
}
/// Convenience: open a new database at `db_path` then call `from_dir`.
/// Only use this when you are the sole opener (e.g. in tests).
pub fn from_path(module_dir: &Path, db_path: &Path) -> Result<Self, RuntimeError> {
let db = open_db(db_path).map_err(|e| RuntimeError::Storage(e.to_string()))?;
Self::from_dir(module_dir, db)
}
/// Dispatch an Axon tool call. Returns a JSON result value.
///
/// Resolution order:
/// 1. Look up `tool_name` in the registry → (module, func).
/// 2. Find the module's bytecode.
/// 3. Find the function entry-point label (`__fn_<func>`) in the bytecode.
/// 4. Execute the VM from that entry-point with `params` on the stack.
pub async fn dispatch(&self, tool_name: &str, params: Value) -> Value {
debug!("NeuronRuntime::dispatch tool={}", tool_name);
let (module_name, func_name) = match self.registry.resolve(tool_name) {
None => {
warn!("dispatch: unknown tool '{}'", tool_name);
return serde_json::json!({"error": format!("unknown tool: {}", tool_name)});
}
Some(pair) => pair,
};
let bytecode = match self.modules.get(module_name) {
None => {
// Module not loaded — fall back to native dispatch
debug!(
"dispatch: module '{}' not loaded, attempting native fallback for '{}'",
module_name, tool_name
);
return self.native_fallback(tool_name, params);
}
Some(bc) => bc,
};
// Execute the module, looking for a function entry-point.
let mut vm = Vm::new(self.bindings.clone());
// Pre-scan bytecode for function entry-points labelled by convention
// as a StoreLocal of the sentinel name "__fn_<func>". In the
// generated bytecode a function is expected to start after a
// `Push(Str("__fn_<name>"))` / `StoreLocal("__fn_<name>")` pair.
// For robustness we scan for the target function start here.
let entry_ip = find_function_entry(bytecode, func_name);
// Push params as the first local.
let params_el = json_to_el_value(params.clone());
let result = if let Some(ip) = entry_ip {
// Register all functions we found.
register_all_functions(&mut vm, bytecode);
vm.execute_from(ip, params_el, bytecode)
} else {
// No labelled entry-point: run the full module (eval semantics).
// This is the common case while .el files are being developed.
vm.locals_mut().insert("params".to_string(), params_el);
vm.run(bytecode)
};
match result {
Ok(val) => el_value_to_json(&val),
Err(e) => {
warn!("dispatch vm error for '{}': {}", tool_name, e);
serde_json::json!({"error": e.to_string()})
}
}
}
/// Native fallback: invoke a registered native binding directly using a
/// naming convention (`tool_name` → `native_<tool_name>`).
fn native_fallback(&self, tool_name: &str, params: Value) -> Value {
let native_name = format!("native_{}", tool_name);
if self.bindings.has(&native_name) {
self.bindings.call(&native_name, params)
} else {
serde_json::json!({"error": format!("module not loaded and no native fallback for: {}", tool_name)})
}
}
/// Scan all loaded modules for an `on_startup` function and run each
/// in a dedicated background thread.
///
/// Convention: any `.el` module that exports `fn on_startup(...)` will
/// be called once at daemon boot in a new thread. The function runs for
/// the lifetime of the daemon — it can contain a `while true` loop with
/// `native_sleep` for periodic autonomous work.
///
/// Each thread gets its own VM instance but shares the NativeBindings Arc,
/// so storage operations are safe across threads.
pub fn run_startup_hooks(&self) {
for (module_name, bytecode) in &self.modules {
if find_function_entry(bytecode, "on_startup").is_some() {
let bindings = self.bindings.clone();
let bytecode = bytecode.clone();
let module_name = module_name.clone();
std::thread::spawn(move || {
tracing::info!("on_startup: launching background thread for module '{}'", module_name);
let mut vm = Vm::new(bindings);
register_all_functions(&mut vm, &bytecode);
let entry = match find_function_entry(&bytecode, "on_startup") {
Some(ip) => ip,
None => return,
};
let params = json_to_el_value(serde_json::json!({}));
match vm.execute_from(entry, params, &bytecode) {
Ok(_) => tracing::info!("on_startup: module '{}' exited normally", module_name),
Err(e) => tracing::error!("on_startup: module '{}' error: {}", module_name, e),
}
});
}
}
}
/// Number of loaded modules.
pub fn module_count(&self) -> usize {
self.modules.len()
}
/// Names of all loaded modules.
pub fn module_names(&self) -> Vec<&str> {
self.modules.keys().map(|s| s.as_str()).collect()
}
}
// ── Vm extension for call-from ────────────────────────────────────────────────
impl Vm {
/// Execute from a specific instruction pointer with a params value
/// pre-loaded into the "params" local.
pub fn execute_from(
&mut self,
start_ip: usize,
params: el_compiler::Value,
instructions: &[Bytecode],
) -> Result<el_compiler::Value, crate::vm::VmError> {
self.locals_mut().insert("params".to_string(), params);
self.set_ip(start_ip);
self.execute(instructions)
}
/// Expose mutable locals for the runtime layer.
pub fn locals_mut(&mut self) -> &mut std::collections::HashMap<String, el_compiler::Value> {
// We expose the top-level locals (no call frame active here).
self.top_locals_mut()
}
/// Set the instruction pointer directly.
pub fn set_ip(&mut self, ip: usize) {
self.set_ip_internal(ip);
}
}
// ── Bytecode scanning helpers ─────────────────────────────────────────────────
/// Find the instruction offset for a named function entry-point.
///
/// Convention: a function `foo` is prefixed by:
/// `Push(Str("__fn_foo"))` at some ip N
/// and the function body starts at ip N+1.
fn find_function_entry(bytecode: &[Bytecode], func_name: &str) -> Option<usize> {
let sentinel = format!("__fn_{}", func_name);
for (i, instr) in bytecode.iter().enumerate() {
if let Bytecode::Push(el_compiler::Value::Str(s)) = instr {
if s == &sentinel {
// Function body starts at the next instruction.
if i + 1 < bytecode.len() {
return Some(i + 1);
}
}
}
}
None
}
/// Scan bytecode for all function entry-points and register them in the VM.
fn register_all_functions(vm: &mut Vm, bytecode: &[Bytecode]) {
for (i, instr) in bytecode.iter().enumerate() {
if let Bytecode::Push(el_compiler::Value::Str(s)) = instr {
if let Some(func_name) = s.strip_prefix("__fn_") {
if i + 1 < bytecode.len() {
// arity 1 by convention (params map)
vm.register_function(func_name.to_string(), i + 1, 1);
}
}
}
}
}
// ── Tests ─────────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
use crate::bindings::NativeBindings;
use el_compiler::{Bytecode, Value};
fn make_stub_runtime(modules: HashMap<String, Vec<Bytecode>>) -> NeuronRuntime {
NeuronRuntime::from_parts(
Arc::new(NativeBindings::stub()),
ToolRegistry::default_neuron(),
modules,
)
}
#[tokio::test]
async fn test_dispatch_unknown_tool() {
let rt = make_stub_runtime(HashMap::new());
let result = rt.dispatch("does_not_exist", serde_json::json!({})).await;
assert!(result.get("error").is_some());
}
#[tokio::test]
async fn test_dispatch_known_tool_module_not_loaded() {
// "remember" is registered but module "memory" is not loaded.
// Falls back to native_remember, which the stub doesn't have → error.
let rt = make_stub_runtime(HashMap::new());
let result = rt.dispatch("remember", serde_json::json!({})).await;
// Should get some kind of error (module not loaded, no native fallback).
assert!(result.get("error").is_some());
}
#[tokio::test]
async fn test_dispatch_module_with_bytecode() {
// Module "memory" that just pushes 42 and halts.
let mut modules = HashMap::new();
modules.insert(
"memory".to_string(),
vec![Bytecode::Push(Value::Int(42)), Bytecode::Halt],
);
let rt = make_stub_runtime(modules);
let result = rt.dispatch("remember", serde_json::json!({"content": "hello"})).await;
// Eval semantics: result = 42
assert_eq!(result, serde_json::json!(42));
}
#[tokio::test]
async fn test_dispatch_native_uuid() {
// Register a module "util" that calls native_uuid.
let mut modules = HashMap::new();
modules.insert(
"util".to_string(),
vec![
Bytecode::Call { name: "native_uuid".into(), arity: 0 },
Bytecode::Halt,
],
);
// Add a tool mapping.
let mut registry = ToolRegistry::default_neuron();
registry.register("get_uuid", "util", "get_uuid");
let rt = NeuronRuntime::from_parts(
Arc::new(NativeBindings::stub()),
registry,
modules,
);
let result = rt.dispatch("get_uuid", serde_json::json!({})).await;
let uuid_str = result.as_str().expect("expected a string UUID");
assert!(uuid::Uuid::parse_str(uuid_str).is_ok());
}
#[test]
fn test_find_function_entry_found() {
let bc = vec![
Bytecode::Push(Value::Str("__fn_remember".into())),
Bytecode::Push(Value::Int(99)),
Bytecode::Halt,
];
assert_eq!(find_function_entry(&bc, "remember"), Some(1));
}
#[test]
fn test_find_function_entry_not_found() {
let bc = vec![Bytecode::Push(Value::Int(1)), Bytecode::Halt];
assert_eq!(find_function_entry(&bc, "remember"), None);
}
#[test]
fn test_module_count() {
let mut modules = HashMap::new();
modules.insert("memory".into(), vec![]);
modules.insert("knowledge".into(), vec![]);
let rt = make_stub_runtime(modules);
assert_eq!(rt.module_count(), 2);
}
#[test]
fn test_registry_resolve() {
let r = ToolRegistry::default_neuron();
assert_eq!(r.resolve("remember"), Some(("memory", "remember")));
assert_eq!(r.resolve("plan_work"), Some(("backlog", "plan_work")));
assert_eq!(r.resolve("capture_knowledge"), Some(("knowledge", "capture_knowledge")));
}
}
File diff suppressed because it is too large Load Diff