mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
core: load AGENTS.md from foreign environments (#28958)
## Why Make it possible to load AGENTS.md from remote exec-servers whose OS is different than app-server. ## What - keep `AGENTS.md` discovery and provenance as `PathUri`, with root-aware parent and ancestor traversal - expose lifecycle instruction sources as legacy app-server path strings in events while retaining `PathUri` internally - preserve and test mixed POSIX and Windows paths in model context and TUI status output - cover remote Windows loading end to end by seeding the Wine prefix through host filesystem APIs - fix bug in `PathUri`'s parent() implementation that would erase Windows drive letters
This commit is contained in:
1 parent
406062c3af
commit
dce673905a
38 files changed
+550
-203
No files matched your search
@@ -53,16 +53,11 @@ pub(crate) async fn load_project_instructions(
|
||||
let mut loaded = LoadedAgentsMd::from_user_instructions(user_instructions);
|
||||
for turn_environment in &environments.turn_environments {
|
||||
let filesystem = turn_environment.environment.get_filesystem();
|
||||
// TODO(anp): Migrate AGENTS.md discovery to PathUri so instructions can be loaded from
|
||||
// environment-native foreign working directories.
|
||||
let Ok(cwd) = turn_environment.cwd().to_abs_path() else {
|
||||
continue;
|
||||
};
|
||||
match read_agents_md(
|
||||
config,
|
||||
filesystem.as_ref(),
|
||||
&turn_environment.environment_id,
|
||||
&cwd,
|
||||
turn_environment.cwd(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -97,7 +92,7 @@ async fn read_agents_md(
|
||||
config: &Config,
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
environment_id: &str,
|
||||
cwd: &AbsolutePathBuf,
|
||||
cwd: &PathUri,
|
||||
) -> io::Result<Option<LoadedAgentsMd>> {
|
||||
let max_total = config.project_doc_max_bytes;
|
||||
|
||||
@@ -118,15 +113,14 @@ async fn read_agents_md(
|
||||
break;
|
||||
}
|
||||
|
||||
let path_uri = PathUri::from_abs_path(&p);
|
||||
match fs.get_metadata(&path_uri, /*sandbox*/ None).await {
|
||||
match fs.get_metadata(&p, /*sandbox*/ None).await {
|
||||
Ok(metadata) if !metadata.is_file => continue,
|
||||
Ok(_) => {}
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => continue,
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
|
||||
let mut data = match fs.read_file(&path_uri, /*sandbox*/ None).await {
|
||||
let mut data = match fs.read_file(&p, /*sandbox*/ None).await {
|
||||
Ok(data) => data,
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => continue,
|
||||
Err(err) => return Err(err),
|
||||
@@ -139,7 +133,7 @@ async fn read_agents_md(
|
||||
if size > remaining {
|
||||
tracing::warn!(
|
||||
"Project doc `{}` exceeds remaining budget ({} bytes) - truncating.",
|
||||
p.display(),
|
||||
p.inferred_native_path_string(),
|
||||
remaining,
|
||||
);
|
||||
}
|
||||
@@ -169,9 +163,9 @@ async fn read_agents_md(
|
||||
/// directory, inclusive. Symlinks are allowed.
|
||||
async fn agents_md_paths(
|
||||
config: &Config,
|
||||
cwd: &AbsolutePathBuf,
|
||||
cwd: &PathUri,
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
) -> io::Result<Vec<AbsolutePathBuf>> {
|
||||
) -> io::Result<Vec<PathUri>> {
|
||||
let dir = cwd.clone();
|
||||
|
||||
let mut merged = TomlValue::Table(toml::map::Map::new());
|
||||
@@ -194,18 +188,18 @@ async fn agents_md_paths(
|
||||
};
|
||||
let mut project_root = None;
|
||||
if !project_root_markers.is_empty() {
|
||||
for ancestor in dir.ancestors() {
|
||||
for current in dir.ancestors() {
|
||||
for marker in &project_root_markers {
|
||||
let marker_path = ancestor.join(marker);
|
||||
let marker_path_uri = PathUri::from_abs_path(&marker_path);
|
||||
let marker_exists = match fs.get_metadata(&marker_path_uri, /*sandbox*/ None).await
|
||||
{
|
||||
let marker_path = current
|
||||
.join(marker)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
|
||||
let marker_exists = match fs.get_metadata(&marker_path, /*sandbox*/ None).await {
|
||||
Ok(_) => true,
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => false,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
if marker_exists {
|
||||
project_root = Some(ancestor.clone());
|
||||
project_root = Some(current.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -215,7 +209,7 @@ async fn agents_md_paths(
|
||||
}
|
||||
}
|
||||
|
||||
let search_dirs: Vec<AbsolutePathBuf> = if let Some(root) = project_root {
|
||||
let search_dirs: Vec<PathUri> = if let Some(root) = project_root {
|
||||
let mut dirs = Vec::new();
|
||||
let mut cursor = dir.clone();
|
||||
loop {
|
||||
@@ -234,13 +228,14 @@ async fn agents_md_paths(
|
||||
vec![dir]
|
||||
};
|
||||
|
||||
let mut found: Vec<AbsolutePathBuf> = Vec::new();
|
||||
let mut found: Vec<PathUri> = Vec::new();
|
||||
let candidate_filenames = candidate_filenames(config);
|
||||
for d in search_dirs {
|
||||
for name in &candidate_filenames {
|
||||
let candidate = d.join(name);
|
||||
let candidate_uri = PathUri::from_abs_path(&candidate);
|
||||
match fs.get_metadata(&candidate_uri, /*sandbox*/ None).await {
|
||||
let candidate = d
|
||||
.join(name)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
|
||||
match fs.get_metadata(&candidate, /*sandbox*/ None).await {
|
||||
Ok(md) if md.is_file => {
|
||||
found.push(candidate);
|
||||
break;
|
||||
@@ -371,7 +366,7 @@ impl LoadedAgentsMd {
|
||||
fn environment_labeled_text(&self) -> String {
|
||||
let mut output = String::new();
|
||||
let mut has_previous = false;
|
||||
let mut previous_environment: Option<(&str, &AbsolutePathBuf)> = None;
|
||||
let mut previous_environment: Option<(&str, &PathUri)> = None;
|
||||
if let Some(instructions) = &self.user_instructions {
|
||||
output.push_str(&instructions.text);
|
||||
has_previous = true;
|
||||
@@ -394,7 +389,7 @@ impl LoadedAgentsMd {
|
||||
output.push_str(&format!(
|
||||
"for `{}` with root {}\n\n",
|
||||
environment_id,
|
||||
cwd.display()
|
||||
cwd.inferred_native_path_string()
|
||||
));
|
||||
}
|
||||
output.push_str(&entry.contents);
|
||||
@@ -421,7 +416,7 @@ impl LoadedAgentsMd {
|
||||
None
|
||||
} else {
|
||||
self.single_project_cwd()
|
||||
.map(|cwd| cwd.to_string_lossy().into_owned())
|
||||
.map(PathUri::inferred_native_path_string)
|
||||
};
|
||||
ContextUserInstructions {
|
||||
directory,
|
||||
@@ -436,10 +431,10 @@ impl LoadedAgentsMd {
|
||||
}
|
||||
|
||||
/// Returns the AGENTS.md files that supplied instruction entries.
|
||||
pub fn sources(&self) -> impl Iterator<Item = &AbsolutePathBuf> {
|
||||
pub fn sources(&self) -> impl Iterator<Item = PathUri> + '_ {
|
||||
self.user_instructions
|
||||
.iter()
|
||||
.map(|instructions| &instructions.source)
|
||||
.map(|instructions| PathUri::from_abs_path(&instructions.source))
|
||||
.chain(
|
||||
self.entries
|
||||
.iter()
|
||||
@@ -463,7 +458,7 @@ impl LoadedAgentsMd {
|
||||
})
|
||||
}
|
||||
|
||||
fn single_project_cwd(&self) -> Option<&AbsolutePathBuf> {
|
||||
fn single_project_cwd(&self) -> Option<&PathUri> {
|
||||
self.entries
|
||||
.iter()
|
||||
.find_map(|entry| match &entry.provenance {
|
||||
@@ -488,9 +483,9 @@ enum InstructionProvenance {
|
||||
/// Workspace instructions discovered from project AGENTS.md files.
|
||||
Project {
|
||||
/// Exact AGENTS.md file, distinct from the environment's selected cwd.
|
||||
source_path: AbsolutePathBuf,
|
||||
source_path: PathUri,
|
||||
environment_id: String,
|
||||
cwd: AbsolutePathBuf,
|
||||
cwd: PathUri,
|
||||
},
|
||||
|
||||
/// Instructions without a file source, including internally defined guidance.
|
||||
@@ -498,9 +493,9 @@ enum InstructionProvenance {
|
||||
}
|
||||
|
||||
impl InstructionProvenance {
|
||||
fn path(&self) -> Option<&AbsolutePathBuf> {
|
||||
fn path(&self) -> Option<PathUri> {
|
||||
match self {
|
||||
Self::Project { source_path, .. } => Some(source_path),
|
||||
Self::Project { source_path, .. } => Some(source_path.clone()),
|
||||
Self::Internal => None,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user