use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use serde_json::{json, Value};
use tokio::sync::oneshot;
use agent_block_core::bus::{AckResult, Handler};
use agent_block_core::host::{run, BlockConfig, PromptSource, ScriptSource};
struct CaptureHandler {
tx: tokio::sync::Mutex<Option<oneshot::Sender<Value>>>,
}
#[async_trait]
impl Handler for CaptureHandler {
async fn call(&self, _kind: String, _id: String, payload: Value, _meta: Value) -> AckResult {
if let Some(tx) = self.tx.lock().await.take() {
let _ = tx.send(payload);
}
Ok(Value::Null)
}
}
#[tokio::test]
async fn inline_script_emits_agent_result_with_prompt_and_context() {
let dir = tempfile::tempdir().expect("tempdir");
let (tx, rx) = oneshot::channel::<Value>();
let handler: Arc<dyn Handler> = Arc::new(CaptureHandler {
tx: tokio::sync::Mutex::new(Some(tx)),
});
let mut host_handlers: HashMap<String, Arc<dyn Handler>> = HashMap::new();
host_handlers.insert("agent_result".to_string(), handler);
let stub_script = r#"
bus.emit("agent_result", {
ok = true,
prompt = _PROMPT,
system = _CONTEXT,
})
"#;
let config = BlockConfig {
script: ScriptSource::Inline {
source: stub_script.to_string(),
name: "stub_agent_invoker.lua".to_string(),
},
project_root: dir.path().to_path_buf(),
relay_url: None,
secret_key: None,
mcp_rpc_timeout: Duration::from_secs(30),
prompt: Some(PromptSource::Inline("solve this".to_string())),
context: Some(PromptSource::Inline("you are an agent".to_string())),
host_handlers,
auto_serve_bus: true,
shutdown_token: None,
};
run(config).await.expect("run ok");
let payload = tokio::time::timeout(Duration::from_secs(2), rx)
.await
.expect("oneshot did not receive within 2s")
.expect("oneshot canceled");
assert_eq!(
payload,
json!({
"ok": true,
"prompt": "solve this",
"system": "you are an agent",
})
);
}
#[tokio::test]
async fn prompt_source_file_is_read_at_run_start() {
let dir = tempfile::tempdir().expect("tempdir");
let prompt_path = dir.path().join("prompt.txt");
std::fs::write(&prompt_path, "from a file").expect("write prompt file");
let (tx, rx) = oneshot::channel::<Value>();
let handler: Arc<dyn Handler> = Arc::new(CaptureHandler {
tx: tokio::sync::Mutex::new(Some(tx)),
});
let mut host_handlers: HashMap<String, Arc<dyn Handler>> = HashMap::new();
host_handlers.insert("agent_result".to_string(), handler);
let config = BlockConfig {
script: ScriptSource::Inline {
source: r#"bus.emit("agent_result", { prompt = _PROMPT })"#.to_string(),
name: "echo.lua".to_string(),
},
project_root: dir.path().to_path_buf(),
relay_url: None,
secret_key: None,
mcp_rpc_timeout: Duration::from_secs(30),
prompt: Some(PromptSource::File(prompt_path)),
context: None,
host_handlers,
auto_serve_bus: true,
shutdown_token: None,
};
run(config).await.expect("run ok");
let payload = tokio::time::timeout(Duration::from_secs(2), rx)
.await
.expect("oneshot did not receive within 2s")
.expect("oneshot canceled");
assert_eq!(payload, json!({ "prompt": "from a file" }));
}