26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
169 lines
5.1 KiB
Rust
169 lines
5.1 KiB
Rust
use std::sync::Arc;
|
|
|
|
use axum::body::Body;
|
|
use axum::http::{Request, StatusCode};
|
|
use rmcp::transport::{StreamableHttpServerConfig, StreamableHttpService};
|
|
use serde_json::json;
|
|
|
|
type LocalSessionManager =
|
|
rmcp::transport::streamable_http_server::session::local::LocalSessionManager;
|
|
|
|
fn jsonrpc_call(tool: &str, arguments: &serde_json::Value) -> String {
|
|
json!({
|
|
"jsonrpc": "2.0",
|
|
"id": 1,
|
|
"method": "tools/call",
|
|
"params": {
|
|
"name": tool,
|
|
"arguments": arguments
|
|
}
|
|
})
|
|
.to_string()
|
|
}
|
|
|
|
async fn call_tool(
|
|
svc: &StreamableHttpService<lean_ctx::tools::LeanCtxServer, LocalSessionManager>,
|
|
body: String,
|
|
) {
|
|
let req = Request::builder()
|
|
.method("POST")
|
|
.uri("/")
|
|
.header("Host", "localhost")
|
|
.header("Accept", "application/json, text/event-stream")
|
|
.header("Content-Type", "application/json")
|
|
.body(Body::from(body))
|
|
.expect("request");
|
|
|
|
let resp = svc.handle(req).await;
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
|
}
|
|
|
|
#[allow(clippy::await_holding_lock)]
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn anomaly_detector_is_persisted_from_tool_call_pipeline() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let data_dir = tempfile::tempdir().expect("data dir");
|
|
// TODO: Audit that the environment access only happens in single-threaded code.
|
|
unsafe { std::env::set_var("LEAN_CTX_DATA_DIR", data_dir.path()) };
|
|
|
|
let dir = tempfile::tempdir().expect("project dir");
|
|
let file_path = dir.path().join("a.txt");
|
|
std::fs::write(&file_path, "hello\n").expect("write file");
|
|
|
|
let root_str = dir.path().to_string_lossy().to_string();
|
|
let base = lean_ctx::tools::LeanCtxServer::new_with_project_root(Some(&root_str));
|
|
|
|
let service_factory = move || Ok(base.clone());
|
|
let cfg = StreamableHttpServerConfig::default()
|
|
.with_stateful_mode(false)
|
|
.with_json_response(true);
|
|
let svc = StreamableHttpService::new(
|
|
service_factory,
|
|
Arc::new(LocalSessionManager::default()),
|
|
cfg,
|
|
);
|
|
|
|
call_tool(
|
|
&svc,
|
|
jsonrpc_call(
|
|
"ctx_read",
|
|
&json!({
|
|
"path": file_path.to_string_lossy().to_string(),
|
|
"mode": "full"
|
|
}),
|
|
),
|
|
)
|
|
.await;
|
|
|
|
// Force save (save_debounced may skip if another test in this binary
|
|
// triggered a save within the 3s debounce window).
|
|
lean_ctx::core::anomaly::save();
|
|
|
|
assert!(
|
|
data_dir.path().join("anomaly_detector.json").exists(),
|
|
"expected anomaly_detector.json to be persisted"
|
|
);
|
|
|
|
// TODO: Audit that the environment access only happens in single-threaded code.
|
|
unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR") };
|
|
}
|
|
|
|
#[allow(clippy::await_holding_lock)]
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn episodic_and_procedural_memory_persist_via_ctx_session_actions() {
|
|
let _lock = lean_ctx::core::data_dir::test_env_lock();
|
|
let data_dir = tempfile::tempdir().expect("data dir");
|
|
// TODO: Audit that the environment access only happens in single-threaded code.
|
|
unsafe { std::env::set_var("LEAN_CTX_DATA_DIR", data_dir.path()) };
|
|
|
|
let dir = tempfile::tempdir().expect("project dir");
|
|
let file_path = dir.path().join("a.txt");
|
|
std::fs::write(&file_path, "hello\n").expect("write file");
|
|
|
|
let root_str = dir.path().to_string_lossy().to_string();
|
|
let base = lean_ctx::tools::LeanCtxServer::new_with_project_root(Some(&root_str));
|
|
|
|
let service_factory = move || Ok(base.clone());
|
|
let cfg = StreamableHttpServerConfig::default()
|
|
.with_stateful_mode(false)
|
|
.with_json_response(true);
|
|
let svc = StreamableHttpService::new(
|
|
service_factory,
|
|
Arc::new(LocalSessionManager::default()),
|
|
cfg,
|
|
);
|
|
|
|
call_tool(
|
|
&svc,
|
|
jsonrpc_call(
|
|
"ctx_read",
|
|
&json!({
|
|
"path": file_path.to_string_lossy().to_string(),
|
|
"mode": "full"
|
|
}),
|
|
),
|
|
)
|
|
.await;
|
|
|
|
call_tool(
|
|
&svc,
|
|
jsonrpc_call(
|
|
"ctx_session",
|
|
&json!({
|
|
"action": "episodes",
|
|
"value": "record"
|
|
}),
|
|
),
|
|
)
|
|
.await;
|
|
|
|
let episodes_dir = data_dir.path().join("memory").join("episodes");
|
|
let mut entries = std::fs::read_dir(&episodes_dir).expect("episodes dir exists");
|
|
assert!(
|
|
entries.next().is_some(),
|
|
"expected at least one episodic store file"
|
|
);
|
|
|
|
call_tool(
|
|
&svc,
|
|
jsonrpc_call(
|
|
"ctx_session",
|
|
&json!({
|
|
"action": "procedures",
|
|
"value": "detect"
|
|
}),
|
|
),
|
|
)
|
|
.await;
|
|
|
|
let procs_dir = data_dir.path().join("memory").join("procedures");
|
|
let mut entries = std::fs::read_dir(&procs_dir).expect("procedures dir exists");
|
|
assert!(
|
|
entries.next().is_some(),
|
|
"expected at least one procedural store file"
|
|
);
|
|
|
|
// TODO: Audit that the environment access only happens in single-threaded code.
|
|
unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR") };
|
|
}
|