Compare commits

..

9 Commits

Author SHA1 Message Date
11e2a79bf6 ... 2025-08-25 07:07:18 +02:00
efd8ffb102 ... 2025-08-25 07:06:53 +02:00
Maxime Van Hees
473b9ca753 remove ai references for docs 2025-08-14 14:19:40 +02:00
Maxime Van Hees
fc2a17d519 updates actor naming 2025-08-14 14:18:50 +02:00
Maxime Van Hees
f1a01e5fe2 Merge branch 'main' of git.ourworld.tf:herocode/actor_osis 2025-08-14 14:18:35 +02:00
Maxime Van Hees
32282f465e updated actor to following naming conventions 2025-08-14 14:15:19 +02:00
Timur Gordon
cc700a0abe add heroledger example 2025-08-08 09:50:43 +02:00
Timur Gordon
5fd068f45d Merge branch 'main' of https://git.ourworld.tf/herocode/actor_osis 2025-08-07 11:58:04 +02:00
Timur Gordon
c19f938fde implement tui cmd 2025-08-07 10:41:00 +02:00
11 changed files with 306 additions and 65 deletions

3
.gitignore vendored
View File

@@ -1 +1,2 @@
target target
logs

48
Cargo.lock generated
View File

@@ -6,6 +6,7 @@ version = 4
name = "actor_osis" name = "actor_osis"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"async-trait", "async-trait",
"baobab_actor", "baobab_actor",
"chrono", "chrono",
@@ -191,11 +192,14 @@ dependencies = [
[[package]] [[package]]
name = "baobab_actor" name = "baobab_actor"
version = "0.1.0" version = "0.1.0"
source = "git+https://git.ourworld.tf/herocode/baobab.git#6c5c97e64782f7d05ff22bbafba2498f4b906463"
dependencies = [ dependencies = [
"anyhow",
"anyhow",
"async-trait", "async-trait",
"chrono", "chrono",
"clap", "clap",
"crossterm",
"crossterm",
"env_logger", "env_logger",
"hero_job", "hero_job",
"hero_supervisor", "hero_supervisor",
@@ -203,6 +207,8 @@ dependencies = [
"heromodels-derive", "heromodels-derive",
"heromodels_core", "heromodels_core",
"log", "log",
"ratatui",
"ratatui",
"redis", "redis",
"rhai", "rhai",
"serde", "serde",
@@ -337,9 +343,11 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.42" version = "4.5.43"
version = "4.5.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f"
checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -347,9 +355,11 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.42" version = "4.5.43"
version = "4.5.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65"
checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -935,9 +945,11 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.11" version = "0.4.12"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@@ -954,9 +966,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.4" version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [ dependencies = [
"allocator-api2", "allocator-api2",
"equivalent", "equivalent",
@@ -978,7 +990,6 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]] [[package]]
name = "hero_job" name = "hero_job"
version = "0.1.0" version = "0.1.0"
source = "git+https://git.ourworld.tf/herocode/baobab.git#6c5c97e64782f7d05ff22bbafba2498f4b906463"
dependencies = [ dependencies = [
"chrono", "chrono",
"log", "log",
@@ -993,7 +1004,7 @@ dependencies = [
[[package]] [[package]]
name = "hero_logger" name = "hero_logger"
version = "0.1.0" version = "0.1.0"
source = "git+https://git.ourworld.tf/herocode/baobab.git?branch=logger#9c4fa1a78bf3cbeb57802d261ee9c9fb115ba219" source = "git+https://git.ourworld.tf/herocode/baobab.git?branch=logger#0da7b9363c2956e6f17ac78232152c549f1d5e68"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@@ -1010,7 +1021,6 @@ dependencies = [
[[package]] [[package]]
name = "hero_supervisor" name = "hero_supervisor"
version = "0.1.0" version = "0.1.0"
source = "git+https://git.ourworld.tf/herocode/baobab.git#6c5c97e64782f7d05ff22bbafba2498f4b906463"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@@ -1188,7 +1198,8 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"h2 0.4.11", "h2 0.4.12",
"h2 0.4.12",
"http 1.3.1", "http 1.3.1",
"http-body 1.0.1", "http-body 1.0.1",
"httparse", "httparse",
@@ -2459,7 +2470,12 @@ dependencies = [
[[package]] [[package]]
name = "reth-ipc" name = "reth-ipc"
version = "1.6.0" version = "1.6.0"
source = "git+https://github.com/paradigmxyz/reth#bf2700aa3e722a8f51b57cea9a71045da5420c1a" <<<<<<< HEAD
source = "git+https://github.com/paradigmxyz/reth#c23e53377922d8a842df55ededa6700e2b938d64"
=======
source = "git+https://github.com/paradigmxyz/reth#59e4a5556fa54f1c210e45412b6a91f2351bea19"
source = "git+https://github.com/paradigmxyz/reth#59e4a5556fa54f1c210e45412b6a91f2351bea19"
>>>>>>> 473b9ca753b9e350a8824e527789d53928b867d4
dependencies = [ dependencies = [
"bytes", "bytes",
"futures", "futures",
@@ -2917,9 +2933,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.10" version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]] [[package]]
name = "smallvec" name = "smallvec"

View File

@@ -9,7 +9,11 @@ path = "src/lib.rs"
[[bin]] [[bin]]
name = "actor_osis" name = "actor_osis"
path = "cmd/actor_osis.rs" path = "cmd/actor.rs"
[[bin]]
name = "actor_osis_tui"
path = "cmd/terminal_ui.rs"
[[example]] [[example]]
name = "engine" name = "engine"
@@ -22,6 +26,7 @@ path = "examples/actor.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = "1.0"
redis = { version = "0.25.0", features = ["tokio-comp"] } redis = { version = "0.25.0", features = ["tokio-comp"] }
rhai = { version = "1.21.0", features = ["std", "sync", "decimal", "internals"] } rhai = { version = "1.21.0", features = ["std", "sync", "decimal", "internals"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
@@ -35,8 +40,11 @@ chrono = { version = "0.4", features = ["serde"] }
toml = "0.8" toml = "0.8"
thiserror = "1.0" thiserror = "1.0"
async-trait = "0.1" async-trait = "0.1"
hero_job = { git = "https://git.ourworld.tf/herocode/baobab.git"} # hero_job = { git = "https://git.ourworld.tf/herocode/baobab.git"}
baobab_actor = { git = "https://git.ourworld.tf/herocode/baobab.git"} # baobab_actor = { git = "https://git.ourworld.tf/herocode/baobab.git"}
# TODO: uncomment above; only work with local paths whilst developig
hero_job = { path = "/home/maxime/baobab/core/job"}
baobab_actor = { path = "/home/maxime/baobab/core/actor"}
heromodels = { git = "https://git.ourworld.tf/herocode/db.git" } heromodels = { git = "https://git.ourworld.tf/herocode/db.git" }
heromodels_core = { git = "https://git.ourworld.tf/herocode/db.git" } heromodels_core = { git = "https://git.ourworld.tf/herocode/db.git" }
heromodels-derive = { git = "https://git.ourworld.tf/herocode/db.git" } heromodels-derive = { git = "https://git.ourworld.tf/herocode/db.git" }

View File

@@ -38,4 +38,42 @@ let handle = spawn_osis_actor(
- **Actor ID**: `"osis"` (constant) - **Actor ID**: `"osis"` (constant)
- **Actor Type**: `"OSIS"` - **Actor Type**: `"OSIS"`
- **Processing Model**: Sequential, blocking - **Processing Model**: Sequential, blocking
- **Script Engine**: Rhai with OSIS-specific DSL extensions - **Script Engine**: Rhai with OSIS-specific DSL extensions
## Canonical Redis queues and verification
The project uses canonical dispatch queues per script type. For OSIS, the work queue is:
- hero:q:work:type:osis
Consumer behavior:
- The in-repo actor derives ScriptType=OSIS from its actor_id containing "osis" and BLPOPs hero:q:work:type:osis.
- This repos OSIS actor has been updated so its actor_id is "osis", ensuring it consumes the canonical queue.
Quick verification (redis-cli):
- List work queues:
- KEYS hero:q:work:type:*
- Check OSIS queue length:
- LLEN hero:q:work:type:osis
- Inspect a specific job (replace {job_id} with the printed id):
- HGET hero:job:{job_id} status
- HGET hero:job:{job_id} output
Run options:
- Option A: Run the example which spawns the OSIS actor and dispatches jobs to the canonical queue.
1) Start Redis (if not already): redis-server
2) In this repo:
- cargo run --example actor
3) Observe the console: job IDs will be printed as they are created and dispatched.
4) In a separate terminal, verify with redis-cli:
- LLEN hero:q:work:type:osis (will briefly increment, then return to 0 as the actor consumes)
- HGET hero:job:{job_id} status (should transition to started then finished)
- HGET hero:job:{job_id} output (should contain the script result)
- Option B: Run the standalone actor binary and dispatch from another process that pushes to the canonical type queue.
1) Start the actor:
- cargo run --bin actor_osis
2) From any producer, LPUSH hero:q:work:type:osis {job_id} after persisting the job hash hero:job:{job_id}.
3) Use the same redis-cli checks above to confirm consumption and completion.
Notes:
- Hash-only result model is the default. The job result is written to hero:job:{job_id}.output and status=finished.
- Reply queues (hero:q:reply:{job_id}) are optional and not required for OSIS to function.

148
cmd/terminal_ui.rs Normal file
View File

@@ -0,0 +1,148 @@
//! Simplified main function for Baobab Actor TUI
//!
//! This binary provides a clean entry point for the actor monitoring and job dispatch interface.
use anyhow::{Result, Context};
use baobab_actor::terminal_ui::{App, setup_and_run_tui};
use clap::Parser;
use log::{info, warn, error};
use std::path::PathBuf;
use std::process::{Child, Command};
use tokio::signal;
#[derive(Parser)]
#[command(name = "baobab-actor-tui")]
#[command(about = "Terminal UI for Baobab Actor - Monitor and dispatch jobs to a single actor")]
struct Args {
/// Redis URL for job queue
#[arg(short, long, default_value = "redis://localhost:6379")]
redis_url: String,
/// Enable verbose logging
#[arg(short, long)]
verbose: bool,
}
/// Initialize logging based on verbosity level
fn init_logging(verbose: bool) {
if verbose {
env_logger::Builder::from_default_env()
.filter_level(log::LevelFilter::Debug)
.init();
} else {
env_logger::Builder::from_default_env()
.filter_level(log::LevelFilter::Info)
.init();
}
}
/// Create and configure the TUI application
fn create_app(args: &Args) -> Result<App> {
let actor_id = "osis".to_string();
// Get the crate root directory
let crate_root = std::env::var("CARGO_MANIFEST_DIR")
.unwrap_or_else(|_| ".".to_string());
let crate_root = PathBuf::from(crate_root);
let actor_path = crate_root.join("target/debug/actor_osis");
let example_dir = Some(crate_root.join("examples/scripts"));
App::new(
actor_id,
actor_path,
args.redis_url.clone(),
example_dir,
)
}
/// Spawn the actor binary as a background process
fn spawn_actor_process(_args: &Args) -> Result<Child> {
// Get the crate root directory
let crate_root = std::env::var("CARGO_MANIFEST_DIR")
.unwrap_or_else(|_| ".".to_string());
let actor_path = PathBuf::from(crate_root).join("target/debug/actor_osis");
info!("🎬 Spawning actor process: {}", actor_path.display());
let mut cmd = Command::new(&actor_path);
// Redirect stdout and stderr to null to prevent logs from interfering with TUI
cmd.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null());
// Spawn the process
let child = cmd
.spawn()
.with_context(|| format!("Failed to spawn actor process: {}", actor_path.display()))?;
info!("✅ Actor process spawned with PID: {}", child.id());
Ok(child)
}
/// Cleanup function to terminate actor process
fn cleanup_actor_process(mut actor_process: Child) {
info!("🧹 Cleaning up actor process...");
match actor_process.try_wait() {
Ok(Some(status)) => {
info!("Actor process already exited with status: {}", status);
}
Ok(None) => {
info!("Terminating actor process...");
if let Err(e) = actor_process.kill() {
error!("Failed to kill actor process: {}", e);
} else {
match actor_process.wait() {
Ok(status) => info!("Actor process terminated with status: {}", status),
Err(e) => error!("Failed to wait for actor process: {}", e),
}
}
}
Err(e) => {
error!("Failed to check actor process status: {}", e);
}
}
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
// Initialize logging
init_logging(args.verbose);
let crate_root = std::env::var("CARGO_MANIFEST_DIR")
.unwrap_or_else(|_| ".".to_string());
info!("🚀 Starting Baobab Actor TUI...");
info!("Actor ID: osis");
info!("Actor Path: {}/target/debug/actor_osis", crate_root);
info!("Redis URL: {}", args.redis_url);
info!("Example Directory: {}/examples/scripts", crate_root);
// Spawn the actor process first
let actor_process = spawn_actor_process(&args)?;
// Give the actor a moment to start up
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
// Create app and run TUI
let app = create_app(&args)?;
// Set up signal handling for graceful shutdown
let result = tokio::select! {
tui_result = setup_and_run_tui(app) => {
info!("TUI exited");
tui_result
}
_ = signal::ctrl_c() => {
info!("Received Ctrl+C, shutting down...");
Ok(())
}
};
// Clean up the actor process
cleanup_actor_process(actor_process);
result
}

View File

@@ -110,9 +110,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Job::update_status(&mut redis_conn, &job.id, JobStatus::Dispatched).await?; Job::update_status(&mut redis_conn, &job.id, JobStatus::Dispatched).await?;
println!("Stored job in Redis with key: {} and status: Dispatched", job_key); println!("Stored job in Redis with key: {} and status: Dispatched", job_key);
// Add the job to the OSIS queue for processing // Add the job to the canonical type queue for processing
// Note: The supervisor uses "actor_queue:" prefix, so the correct queue is: // Canonical dispatch queue per type: hero:q:work:type:{script_type}
let queue_key = "hero:job:actor_queue:osis"; let queue_key = "hero:q:work:type:osis";
let _: () = redis_conn.lpush(&queue_key, &job.id).await?; let _: () = redis_conn.lpush(&queue_key, &job.id).await?;
println!("Dispatched job {} to OSIS queue: {}", job.id, queue_key); println!("Dispatched job {} to OSIS queue: {}", job.id, queue_key);

View File

@@ -0,0 +1,53 @@
// heroledger.rhai - Demonstration of HeroLedger models in Rhai
print("=== HeroLedger Models Demo ===");
// Create a new user
print("\n--- Creating User ---");
let new_user = new_user()
.name("Alice Johnson")
.email("alice@herocode.com")
.pubkey("0x1234567890abcdef")
.status("Active")
.save_user();
print("Created user: " + new_user.get_name());
print("User ID: " + new_user.get_id());
print("User email: " + new_user.get_email());
print("User pubkey: " + new_user.get_pubkey());
// Create a new group
print("\n--- Creating Group ---");
let new_group = new_group()
.name("HeroCode Developers")
.description("A group for HeroCode development team members")
.visibility("Public")
.save_group();
print("Created group: " + new_group.get_name());
print("Group ID: " + new_group.get_id());
print("Group description: " + new_group.get_description());
// Create a new account
print("\n--- Creating Account ---");
let new_account = new_account()
.name("Alice's Main Account")
.description("Primary account for Alice Johnson")
.currency("USD")
.save_account();
print("Created account: " + new_account.get_name());
print("Account ID: " + new_account.get_id());
print("Account currency: " + new_account.get_currency());
// Create a new DNS zone
print("\n--- Creating DNS Zone ---");
let new_dns_zone = new_dns_zone()
.name("herocode.com")
.description("Main domain for HeroCode")
.save_dns_zone();
print("Created DNS zone: " + new_dns_zone.get_name());
print("DNS zone ID: " + new_dns_zone.get_id());
print("\n=== Demo Complete ===");

View File

@@ -0,0 +1,13 @@
// test_logging.rhai - Simple test script for logging verification
print("=== LOGGING TEST SCRIPT ===");
print("This is a simple test to verify Rhai logging is working");
print("Line 1: Hello from Rhai!");
print("Line 2: Testing print statements");
print("Line 3: Numbers work too: " + 42);
print("Line 4: Boolean values: " + true);
print("Line 5: String concatenation: " + "works" + " " + "perfectly");
print("=== END OF TEST ===");
// Return a simple value
"Logging test completed successfully"

View File

@@ -1,42 +1,4 @@
//! # Rhailib Domain-Specific Language (DSL) Engine // use heromodels::models::heroledger::rhai::register_heroledger_rhai_modules;
//!
//! This module provides a comprehensive Domain-Specific Language implementation for the Rhai
//! scripting engine, exposing business domain models and operations through a fluent,
//! chainable API.
//!
//! ## Overview
//!
//! The DSL is organized into business domain modules, each providing Rhai-compatible
//! functions for creating, manipulating, and persisting domain entities. All operations
//! include proper authorization checks and type safety.
//!
//! ## Available Domains
//!
//! - **Business Operations** (`biz`): Companies, products, sales, shareholders
//! - **Financial Models** (`finance`): Accounts, assets, marketplace operations
//! - **Content Management** (`library`): Collections, images, PDFs, books, slideshows
//! - **Workflow Management** (`flow`): Flows, steps, signature requirements
//! - **Community Management** (`circle`): Circles, themes, membership
//! - **Contact Management** (`contact`): Contact information and relationships
//! - **Access Control** (`access`): Security and permissions
//! - **Time Management** (`calendar`): Calendar and scheduling
//! - **Core Utilities** (`core`): Comments and fundamental operations
//! - **Generic Objects** (`object`): Generic object manipulation
//!
//! ## Usage Example
//!
//! ```rust
//! use rhai::Engine;
//! use crate::engine::register_dsl_modules;
//!
//! let mut engine = Engine::new();
//! register_dsl_modules(&mut engine);
//!
//! // Now the engine can execute scripts like:
//! // let company = new_company().name("Acme Corp").email("contact@acme.com");
//! // let saved = save_company(company);
//! ```
use rhai::Engine; use rhai::Engine;
use rhailib_dsl; use rhailib_dsl;
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
@@ -161,6 +123,8 @@ pub fn register_dsl_modules(engine: &mut Engine) {
// Register basic object functionality directly // Register basic object functionality directly
register_object_functions(engine); register_object_functions(engine);
//heromodels::heroledger::rhai::register_heroledger_rhai_modules(&mut engine);
println!("Rhailib Domain Specific Language modules registered successfully."); println!("Rhailib Domain Specific Language modules registered successfully.");
} }

View File

@@ -264,8 +264,8 @@ impl Actor for OSISActor {
} }
fn actor_id(&self) -> &str { fn actor_id(&self) -> &str {
// Use actor_queue:osis to match supervisor's dispatch queue naming // Actor ID contains "osis" so the runtime derives ScriptType=OSIS and consumes the canonical type queue.
"actor_queue:osis" "osis"
} }
fn redis_url(&self) -> &str { fn redis_url(&self) -> &str {