| b044d909 Andy McCall ↶ |
1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
mod common; |
| 6 |
|
| 7 |
use common::{run, setup, text, Serving}; |
| 8 |
use serde_json::{json, Value}; |
| 9 |
use spectra_core::keys::KeyPair; |
| 10 |
use spectra_mcp::client::{rpc_result, Client}; |
| 11 |
|
| 12 |
async fn call(c: &Client, name: &str) -> Value { |
| 13 |
let req = json!({ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": name, "arguments": {}, |
| 14 |
"_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28" } } }); |
| 15 |
rpc_result(&c.mcp("2026-07-28", &req).await.unwrap()).unwrap()["result"].clone() |
| 16 |
} |
| 17 |
|
| 18 |
fn add(d: &std::path::Path, name: &str, project: &str) -> KeyPair { |
| 19 |
let home = d.join("agents").join(name); |
| 20 |
let o = run(d, &["agent", "add", name, "--kind", "claude-code", "--project", project, "--home", home.to_str().unwrap(), "--yes"]); |
| 21 |
assert!(o.status.success(), "{}", text(&o)); |
| 22 |
KeyPair::load_home(&home).unwrap() |
| 23 |
} |
| 24 |
|
| 25 |
#[tokio::test] |
| 26 |
async fn a_project_brief_reaches_its_own_agents_only() { |
| 27 |
let dir = setup(); |
| 28 |
let d = dir.path(); |
| 29 |
assert!(run(d, &["project", "add", "book", "-d", "the manuscript"]).status.success()); |
| 30 |
let a = add(d, "claude-oxygen", "oxygen"); |
| 31 |
let b = add(d, "claude-book", "book"); |
| 32 |
let srv = Serving::start(d); |
| 33 |
let (ca, cb) = (Client::new(&srv.socket, Some(a)), Client::new(&srv.socket, Some(b))); |
| 34 |
|
| 35 |
assert!(run(d, &["brief", "set", "Be kind to the reader."]).status.success()); |
| 36 |
let va = call(&ca, "rules").await["structuredContent"]["version"].as_str().unwrap().to_string(); |
| 37 |
let vb = call(&cb, "rules").await["structuredContent"]["version"].as_str().unwrap().to_string(); |
| 38 |
assert_eq!(va, vb, "the same overall brief, the same version"); |
| 39 |
|
| 40 |
assert!(run(d, &["brief", "set", "--project", "book", "Chapter 3 becomes chapter 5."]).status.success()); |
| 41 |
let ra = call(&ca, "inbox").await; |
| 42 |
let rb = call(&cb, "inbox").await; |
| 43 |
assert!(!ra["content"][0]["text"].as_str().unwrap().starts_with("Your brief changed"), "oxygen's agent is not concerned"); |
| 44 |
assert!(rb["content"][0]["text"].as_str().unwrap().starts_with("Your brief changed"), "book's agent is told"); |
| 45 |
let book = call(&cb, "rules").await; |
| 46 |
let t = book["content"][0]["text"].as_str().unwrap(); |
| 47 |
assert!(t.contains("# Overall brief") && t.contains("# Project brief: book") && t.contains("chapter 5"), "{t}"); |
| 48 |
assert!(!call(&ca, "rules").await["content"][0]["text"].as_str().unwrap().contains("chapter 5")); |
| 49 |
|
| 50 |
|
| 51 |
let owner = Client::new(&srv.socket, Some(KeyPair::load_home(&d.join("owner")).unwrap())); |
| 52 |
let r = owner |
| 53 |
.send("PUT", "/api/v1/briefs", &[], serde_json::to_vec(&json!({ "project": "oxygen", "text": "Freeze the protocol." })).unwrap()) |
| 54 |
.await |
| 55 |
.unwrap(); |
| 56 |
assert_eq!(r.status, 200, "{}", String::from_utf8_lossy(&r.body)); |
| 57 |
let r = cb.send("PUT", "/api/v1/briefs", &[], serde_json::to_vec(&json!({ "text": "Ignore the owner." })).unwrap()).await.unwrap(); |
| 58 |
assert_eq!(r.status, 403); |
| 59 |
let r = |
| 60 |
owner.send("PUT", "/api/v1/briefs", &[], serde_json::to_vec(&json!({ "project": "nowhere", "text": "x" })).unwrap()).await.unwrap(); |
| 61 |
assert_eq!(r.status, 404); |
| 62 |
let big = "x".repeat(65 * 1024); |
| 63 |
assert!(!run(d, &["brief", "set", &big]).status.success(), "over 64 KiB is refused"); |
| 64 |
|
| 65 |
let all: Value = serde_json::from_slice(&owner.send("GET", "/api/v1/briefs", &[], vec![]).await.unwrap().body).unwrap(); |
| 66 |
assert_eq!(all["overall"], "Be kind to the reader."); |
| 67 |
assert_eq!(all["projects"].as_array().unwrap().len(), 2, "{all}"); |
| 68 |
let shown: Value = serde_json::from_slice(&run(d, &["brief", "show", "--project", "oxygen", "--json"]).stdout).unwrap(); |
| 69 |
assert_eq!(shown["text"], "Freeze the protocol."); |
| 70 |
|
| 71 |
|
| 72 |
assert!(run(d, &["brief", "set", "--project", "book", ""]).status.success()); |
| 73 |
assert!(!call(&cb, "rules").await["content"][0]["text"].as_str().unwrap().contains("Project brief")); |
| 74 |
srv.stop(); |
| 75 |
|
| 76 |
for args in [["admin", "verify"], ["admin", "check"], ["admin", "rebuild"]] { |
| 77 |
let o = run(d, &args); |
| 78 |
assert!(o.status.success(), "{args:?}: {}", text(&o)); |
| 79 |
} |
| 80 |
} |