use std::{path::Path, sync::Arc};
use basis::{AllowAll, CollectingSink, RunOutcome, Runtime, store};
use mentra::{
BuiltinProvider, ContentBlock, agent::AgentConfig, runtime::FileRuntimeStore, test::MockRuntime,
};
use crate::endpoint::ScriptedEndpoint;
use super::{CLOSED_PORT, offline, offline_runtime, offline_shared, write};
#[tokio::test]
async fn a_conversation_is_listed_for_the_workspace_that_minted_it() {
let dir = tempfile::tempdir().expect("tempdir");
write(&dir.path().join("AGENTS.md"), "house rules");
let store_dir = tempfile::tempdir().expect("tempdir");
let workspace = offline(dir.path())
.with_runtime_builder(offline_runtime().with_store_dir(store_dir.path()))
.open()
.await
.expect("opens");
let agent_id = workspace
.prepare("go")
.expect("mints")
.agent_id()
.to_string();
let listed = store::list_in(store_dir.path(), dir.path()).expect("lists");
assert_eq!(
listed
.iter()
.map(|session| session.agent_id.as_str())
.collect::<Vec<_>>(),
vec![agent_id.as_str()],
"a conversation this workspace minted must be one this workspace lists"
);
}
#[tokio::test]
async fn a_resumed_conversation_keeps_listing_under_its_own_workspace() {
let dir = tempfile::tempdir().expect("tempdir");
write(&dir.path().join("AGENTS.md"), "house rules");
let store_dir = tempfile::tempdir().expect("tempdir");
let shared = Arc::new(
Runtime::builder()
.with_base_url(CLOSED_PORT)
.with_api_key("test-key")
.with_store_dir(store_dir.path())
.build()
.expect("builds offline"),
);
let opened = offline_shared(dir.path(), shared.clone())
.open()
.await
.expect("opens");
let agent_id = {
opened.prepare("go").expect("mints").agent_id().to_string()
};
assert_eq!(
listed_ids(store_dir.path(), dir.path()),
vec![agent_id.clone()],
"a fresh mint lists under its own workspace, not the shared runtime's"
);
let mut resumed = opened.resume(&agent_id, "again").expect("resumes");
resumed
.set_name("touched after a resume")
.expect("renames, which persists");
assert_eq!(
listed_ids(store_dir.path(), dir.path()),
vec![agent_id],
"a resumed-then-persisted conversation must keep listing under its own \
workspace rather than falling back to the shared runtime's own tag"
);
}
#[tokio::test]
async fn the_conversation_touched_last_is_listed_first() {
let dir = tempfile::tempdir().expect("tempdir");
write(&dir.path().join("AGENTS.md"), "house rules");
let store_dir = tempfile::tempdir().expect("tempdir");
let workspace = offline(dir.path())
.with_runtime_builder(offline_runtime().with_store_dir(store_dir.path()))
.open()
.await
.expect("opens");
let mut first = workspace.prepare("first").expect("mints");
let first_id = first.agent_id().to_string();
tokio::time::sleep(std::time::Duration::from_millis(1_100)).await;
let second_id = workspace
.prepare("second")
.expect("mints")
.agent_id()
.to_string();
assert_eq!(
listed_ids(store_dir.path(), dir.path()),
vec![second_id.clone(), first_id.clone()],
"creation order is what mentra returns, and it is the reverse of this"
);
tokio::time::sleep(std::time::Duration::from_millis(1_100)).await;
first.set_name("came back to this one").expect("renames");
let listed = store::list_in(store_dir.path(), dir.path()).expect("lists");
assert_eq!(
listed
.iter()
.map(|session| session.agent_id.clone())
.collect::<Vec<_>>(),
vec![first_id, second_id],
"the conversation that was returned to is the one at the top"
);
let revisited = &listed[0];
let created_at = revisited.created_at.expect("a durable store records both");
let updated_at = revisited.updated_at.expect("a durable store records both");
assert!(
updated_at > created_at,
"a conversation that was written twice must not report one instant: \
created {created_at}, updated {updated_at}"
);
}
fn listed_ids(store_dir: &Path, workspace: &Path) -> Vec<String> {
store::list_in(store_dir, workspace)
.expect("lists")
.into_iter()
.map(|session| session.agent_id)
.collect()
}
#[tokio::test]
async fn one_workspace_does_not_list_anothers_conversations() {
let mine = tempfile::tempdir().expect("tempdir");
let theirs = tempfile::tempdir().expect("tempdir");
write(&mine.path().join("AGENTS.md"), "house rules");
write(&theirs.path().join("AGENTS.md"), "other rules");
let store_dir = tempfile::tempdir().expect("tempdir");
let workspace = offline(mine.path())
.with_runtime_builder(offline_runtime().with_store_dir(store_dir.path()))
.open()
.await
.expect("opens");
workspace.prepare("go").expect("mints");
assert!(
store::list_in(store_dir.path(), theirs.path())
.expect("lists")
.is_empty(),
"offering a person another repository's conversations is worse than offering none"
);
}
#[tokio::test]
async fn a_conversation_tagged_before_workspaces_were_is_resumable_and_files_itself_again() {
let dir = tempfile::tempdir().expect("tempdir");
write(&dir.path().join("AGENTS.md"), "house rules");
let store_dir = tempfile::tempdir().expect("tempdir");
let agent_id = {
let mock = MockRuntime::builder()
.model("test-model", BuiltinProvider::OpenAI)
.runtime_identifier("default")
.with_store(FileRuntimeStore::new(store_dir.path()))
.text("from before")
.build()
.expect("the mock runtime builds");
let mut session = mock
.runtime()
.create_session_with_config(
"old",
mock.model(),
AgentConfig {
workspace: mentra::agent::WorkspaceConfig {
base_dir: dir.path().to_path_buf(),
..Default::default()
},
..Default::default()
},
)
.expect("session");
session
.append_turn(vec![ContentBlock::text("hello")])
.await
.expect("a scripted turn completes");
session.agent_id().to_string()
};
assert!(
store::list_in(store_dir.path(), dir.path())
.expect("lists")
.is_empty(),
"an untagged conversation is not claimed by a workspace it never recorded"
);
let endpoint = ScriptedEndpoint::start();
let workspace = offline(dir.path())
.with_runtime_builder(
offline_runtime()
.with_base_url(&endpoint.base_url)
.with_store_dir(store_dir.path()),
)
.open()
.await
.expect("opens");
let report = workspace
.resume(&agent_id, "again")
.expect("an old conversation is still resumable")
.execute_with_approver(CollectingSink::default(), AllowAll)
.await
.expect("the resumed run completes");
assert!(matches!(report.outcome, RunOutcome::Ok));
let listed = store::list_in(store_dir.path(), dir.path()).expect("lists");
assert_eq!(
listed
.iter()
.map(|session| session.agent_id.as_str())
.collect::<Vec<_>>(),
vec![agent_id.as_str()],
"mentra#59's fix (`SessionResumeOptions::runtime_identifier`) means \
resuming an old `\"default\"`-tagged conversation through this \
workspace rehomes it, so using it again adopts it into this \
workspace's list just as it did before mentra 0.27"
);
}