merge: isolate agent cwd in .ideai/run/<id> (convention-file collision fix, §14.1)

This commit is contained in:
2026-06-06 12:26:02 +02:00
7 changed files with 242 additions and 31 deletions

View File

@ -80,16 +80,23 @@ impl CliAgentRuntime {
})
}
/// Resolves the profile's `cwd_template` against the project root.
/// Resolves the profile's `cwd_template` against the supplied `base` cwd.
///
/// The only recognised placeholder is `{projectRoot}` (CONTEXT §9). An empty
/// template defaults to the project root itself.
fn resolve_cwd(profile: &AgentProfile, root: &ProjectPath) -> Result<ProjectPath, RuntimeError> {
/// The base is the agent's **run directory** (`.ideai/run/<agent-id>/`),
/// already computed (and created) by `LaunchAgent` and passed as the `cwd`
/// argument of [`prepare_invocation`](AgentRuntime::prepare_invocation). The
/// recognised placeholder is `{agentRunDir}` (ARCHITECTURE §14.1); the legacy
/// `{projectRoot}` is still substituted with the same base for backwards
/// compatibility (the caller always passes the run dir now). An empty template
/// defaults to the base itself.
fn resolve_cwd(profile: &AgentProfile, base: &ProjectPath) -> Result<ProjectPath, RuntimeError> {
let template = profile.cwd_template.trim();
if template.is_empty() {
return Ok(root.clone());
return Ok(base.clone());
}
let resolved = template.replace("{projectRoot}", root.as_str());
let resolved = template
.replace("{agentRunDir}", base.as_str())
.replace("{projectRoot}", base.as_str());
ProjectPath::new(resolved).map_err(|e| RuntimeError::Invocation(e.to_string()))
}

View File

@ -217,15 +217,27 @@ fn prepare_substitutes_project_root_in_cwd_template() {
}
#[test]
fn prepare_empty_cwd_template_defaults_to_root() {
fn prepare_empty_cwd_template_defaults_to_base() {
let rt = pure_runtime();
let p = profile(ContextInjection::stdin(), "");
let root = ProjectPath::new("/home/me/proj").unwrap();
let base = ProjectPath::new("/home/me/proj").unwrap();
let spec = rt.prepare_invocation(&p, &ctx(), &root).unwrap();
let spec = rt.prepare_invocation(&p, &ctx(), &base).unwrap();
assert_eq!(spec.cwd.as_str(), "/home/me/proj");
}
#[test]
fn prepare_substitutes_agent_run_dir_in_cwd_template() {
// The canonical template (ARCHITECTURE §14.1): `{agentRunDir}` resolves to the
// base cwd the launcher passes — the agent's isolated run directory.
let rt = pure_runtime();
let p = profile(ContextInjection::convention_file("CLAUDE.md").unwrap(), "{agentRunDir}");
let run_dir = ProjectPath::new("/home/me/proj/.ideai/run/agent-1").unwrap();
let spec = rt.prepare_invocation(&p, &ctx(), &run_dir).unwrap();
assert_eq!(spec.cwd.as_str(), "/home/me/proj/.ideai/run/agent-1");
}
// ---------------------------------------------------------------------------
// detection_spec (pure)
// ---------------------------------------------------------------------------