fix(codex): preserve project root on structured resume
Recalcule la policy structured Codex lors des reprises custom/headless afin que le project root et le managed tmp soient transmis en writable_roots. Fail-closed si une session Codex structured est lancée sans policy explicite.
This commit is contained in:
@ -189,7 +189,11 @@ impl AgentSessionFactory for StructuredSessionFactory {
|
||||
writable_roots.clone(),
|
||||
Some(*network_access),
|
||||
),
|
||||
None => ("workspace-write".to_owned(), Vec::new(), None),
|
||||
None => {
|
||||
return Err(AgentSessionError::Start(
|
||||
"structured Codex launch requires an explicit launch policy".to_owned(),
|
||||
));
|
||||
}
|
||||
};
|
||||
Arc::new(CodexExecSession::new_with_policy_and_overrides(
|
||||
id,
|
||||
|
||||
@ -51,7 +51,7 @@ mod tests {
|
||||
use domain::ports::{
|
||||
AgentSession, AgentSessionError, AgentSessionFactory, ContextInjectionPlan,
|
||||
PreparedContext, ReplyEvent, ReplyProgressKind, ReplyProgressSource, ReplyProgressStage,
|
||||
SessionPlan,
|
||||
SessionPlan, StructuredProviderLaunchPolicy,
|
||||
};
|
||||
use domain::profile::{AgentProfile, ContextInjection, HttpChatConfig, StructuredAdapter};
|
||||
use domain::project::ProjectPath;
|
||||
@ -589,6 +589,11 @@ mod tests {
|
||||
// Codex : routé vers l'adapter Codex (id de session distinct, démarrage ok).
|
||||
let fake_cx = FakeCli::printing(&codex_script());
|
||||
let codex = structured_profile(StructuredAdapter::Codex, &fake_cx.command());
|
||||
let codex_policy = StructuredProviderLaunchPolicy::Codex {
|
||||
sandbox_mode: "read-only".to_owned(),
|
||||
writable_roots: Vec::new(),
|
||||
network_access: false,
|
||||
};
|
||||
let session_cx = factory
|
||||
.start(
|
||||
&codex,
|
||||
@ -598,7 +603,7 @@ mod tests {
|
||||
None,
|
||||
&[],
|
||||
None,
|
||||
None,
|
||||
Some(&codex_policy),
|
||||
)
|
||||
.await
|
||||
.expect("start Codex ok");
|
||||
@ -647,7 +652,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn factory_does_not_pass_project_root_to_codex_add_dir_without_policy() {
|
||||
async fn factory_rejects_codex_launch_without_explicit_policy() {
|
||||
let factory = StructuredSessionFactory::new();
|
||||
let (cmd, argv) = make_recording_fake(&[
|
||||
r#"{"type":"thread.started","thread_id":"cx-new"}"#,
|
||||
@ -660,7 +665,7 @@ mod tests {
|
||||
project_root: "/project/root".to_owned(),
|
||||
};
|
||||
|
||||
let session = factory
|
||||
let err = match factory
|
||||
.start(
|
||||
&codex,
|
||||
&ctx,
|
||||
@ -672,23 +677,18 @@ mod tests {
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("start Codex ok");
|
||||
let content = drain_final(session.as_ref()).await;
|
||||
assert_eq!(content, "ok");
|
||||
|
||||
let recorded = std::fs::read_to_string(&argv).expect("argv");
|
||||
let args: Vec<&str> = recorded.lines().collect();
|
||||
{
|
||||
Ok(_) => panic!("Codex launch without a policy must fail closed"),
|
||||
Err(err) => err,
|
||||
};
|
||||
assert!(
|
||||
!args.windows(2).any(|w| w == ["--add-dir", "/project/root"]),
|
||||
"factory must not invent --add-dir project root without an explicit policy, got: {args:?}"
|
||||
err.to_string()
|
||||
.contains("requires an explicit launch policy"),
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
assert!(
|
||||
!args.contains(&"--add-dir"),
|
||||
"default Codex adapter launch must not carry any implicit --add-dir, got: {args:?}"
|
||||
);
|
||||
assert!(
|
||||
!args.contains(&"--ask-for-approval"),
|
||||
"codex exec must not receive unsupported approval flags, got: {args:?}"
|
||||
!argv.exists(),
|
||||
"factory must reject the launch before spawning Codex"
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_file(&cmd);
|
||||
@ -710,6 +710,11 @@ mod tests {
|
||||
relative_path: "AGENTS.md".to_owned(),
|
||||
project_root: "/project/root".to_owned(),
|
||||
};
|
||||
let policy = StructuredProviderLaunchPolicy::Codex {
|
||||
sandbox_mode: "workspace-write".to_owned(),
|
||||
writable_roots: vec!["/project/root".to_owned()],
|
||||
network_access: false,
|
||||
};
|
||||
|
||||
let session = factory
|
||||
.start(
|
||||
@ -720,7 +725,7 @@ mod tests {
|
||||
None,
|
||||
&[],
|
||||
None,
|
||||
None,
|
||||
Some(&policy),
|
||||
)
|
||||
.await
|
||||
.expect("start Codex ok");
|
||||
@ -1803,6 +1808,11 @@ mod tests {
|
||||
r#"{"type":"item.completed","item":{"id":"i0","type":"agent_message","text":"ok"}}"#,
|
||||
]);
|
||||
let codex = structured_profile(StructuredAdapter::Codex, &fake.command());
|
||||
let policy = StructuredProviderLaunchPolicy::Codex {
|
||||
sandbox_mode: "read-only".to_owned(),
|
||||
writable_roots: Vec::new(),
|
||||
network_access: false,
|
||||
};
|
||||
let session = factory
|
||||
.start(
|
||||
&codex,
|
||||
@ -1814,7 +1824,7 @@ mod tests {
|
||||
None,
|
||||
&[],
|
||||
None,
|
||||
None,
|
||||
Some(&policy),
|
||||
)
|
||||
.await
|
||||
.expect("start resume codex");
|
||||
@ -2102,7 +2112,7 @@ mod tests {
|
||||
cmd.clone(),
|
||||
"/",
|
||||
Some("cx-id".to_owned()),
|
||||
vec!["/project/root".to_owned()],
|
||||
vec!["/project/root".to_owned(), "/managed/tmp".to_owned()],
|
||||
Vec::new(),
|
||||
None,
|
||||
None,
|
||||
@ -2121,6 +2131,8 @@ mod tests {
|
||||
"workspace-write",
|
||||
"--add-dir",
|
||||
"/project/root",
|
||||
"--add-dir",
|
||||
"/managed/tmp",
|
||||
"resume",
|
||||
"cx-id",
|
||||
"vas-y",
|
||||
|
||||
Reference in New Issue
Block a user