mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08: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:
@@ -2573,7 +2573,11 @@ mod tests {
|
||||
service_tier: None,
|
||||
cwd,
|
||||
runtime_workspace_roots: Vec::new(),
|
||||
instruction_sources: vec![absolute_path("/tmp/AGENTS.md")],
|
||||
instruction_sources: vec![
|
||||
codex_utils_path_uri::LegacyAppPathString::from_abs_path(&absolute_path(
|
||||
"/tmp/AGENTS.md",
|
||||
)),
|
||||
],
|
||||
approval_policy: v2::AskForApproval::OnFailure,
|
||||
approvals_reviewer: v2::ApprovalsReviewer::User,
|
||||
sandbox: v2::SandboxPolicy::DangerFullAccess,
|
||||
|
||||
@@ -36,6 +36,7 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||
use codex_utils_absolute_path::test_support::test_path_buf;
|
||||
use codex_utils_path_uri::LegacyAppPathString;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value as JsonValue;
|
||||
use serde_json::json;
|
||||
@@ -3685,17 +3686,49 @@ fn thread_lifecycle_responses_default_missing_optional_fields() {
|
||||
serde_json::from_value(response.clone()).expect("thread/start response");
|
||||
let resume: ThreadResumeResponse =
|
||||
serde_json::from_value(response.clone()).expect("thread/resume response");
|
||||
let fork: ThreadForkResponse = serde_json::from_value(response).expect("thread/fork response");
|
||||
let fork: ThreadForkResponse =
|
||||
serde_json::from_value(response.clone()).expect("thread/fork response");
|
||||
|
||||
assert_eq!(start.instruction_sources, Vec::<AbsolutePathBuf>::new());
|
||||
assert_eq!(start.instruction_sources, Vec::<LegacyAppPathString>::new());
|
||||
assert_eq!(start.thread.parent_thread_id, None);
|
||||
assert_eq!(start.thread.recency_at, None);
|
||||
assert_eq!(resume.instruction_sources, Vec::<AbsolutePathBuf>::new());
|
||||
assert_eq!(fork.instruction_sources, Vec::<AbsolutePathBuf>::new());
|
||||
assert_eq!(
|
||||
resume.instruction_sources,
|
||||
Vec::<LegacyAppPathString>::new()
|
||||
);
|
||||
assert_eq!(fork.instruction_sources, Vec::<LegacyAppPathString>::new());
|
||||
assert_eq!(start.active_permission_profile, None);
|
||||
assert_eq!(resume.active_permission_profile, None);
|
||||
assert_eq!(resume.initial_turns_page, None);
|
||||
assert_eq!(fork.active_permission_profile, None);
|
||||
|
||||
let foreign_source: LegacyAppPathString =
|
||||
serde_json::from_value(json!(r"C:\workspace\AGENTS.md")).expect("foreign source");
|
||||
let mut response_with_foreign_source = response;
|
||||
response_with_foreign_source["instructionSources"] = json!([foreign_source.as_str()]);
|
||||
let start: ThreadStartResponse = serde_json::from_value(response_with_foreign_source.clone())
|
||||
.expect("thread/start response with foreign source");
|
||||
let resume: ThreadResumeResponse = serde_json::from_value(response_with_foreign_source.clone())
|
||||
.expect("thread/resume response with foreign source");
|
||||
let fork: ThreadForkResponse = serde_json::from_value(response_with_foreign_source)
|
||||
.expect("thread/fork response with foreign source");
|
||||
assert_eq!(start.instruction_sources, vec![foreign_source.clone()]);
|
||||
assert_eq!(resume.instruction_sources, vec![foreign_source.clone()]);
|
||||
assert_eq!(fork.instruction_sources, vec![foreign_source]);
|
||||
let foreign_source_uri =
|
||||
PathUri::parse("file:///C:/workspace/AGENTS.md").expect("foreign source URI");
|
||||
assert_eq!(
|
||||
start.instruction_source_path_uris(),
|
||||
vec![foreign_source_uri.clone()]
|
||||
);
|
||||
assert_eq!(
|
||||
resume.instruction_source_path_uris(),
|
||||
vec![foreign_source_uri.clone()]
|
||||
);
|
||||
assert_eq!(
|
||||
fork.instruction_source_path_uris(),
|
||||
vec![foreign_source_uri]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -26,6 +26,8 @@ use codex_protocol::protocol::ThreadGoalStatus as CoreThreadGoalStatus;
|
||||
use codex_protocol::protocol::TokenUsage as CoreTokenUsage;
|
||||
use codex_protocol::protocol::TokenUsageInfo as CoreTokenUsageInfo;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::LegacyAppPathString;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -161,9 +163,9 @@ pub struct ThreadStartResponse {
|
||||
#[experimental("thread/start.runtimeWorkspaceRoots")]
|
||||
#[serde(default)]
|
||||
pub runtime_workspace_roots: Vec<AbsolutePathBuf>,
|
||||
/// Instruction source files currently loaded for this thread.
|
||||
/// Environment-native paths to instruction source files currently loaded for this thread.
|
||||
#[serde(default)]
|
||||
pub instruction_sources: Vec<AbsolutePathBuf>,
|
||||
pub instruction_sources: Vec<LegacyAppPathString>,
|
||||
#[experimental(nested)]
|
||||
pub approval_policy: AskForApproval,
|
||||
/// Reviewer currently used for approval requests on this thread.
|
||||
@@ -179,6 +181,13 @@ pub struct ThreadStartResponse {
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
}
|
||||
|
||||
impl ThreadStartResponse {
|
||||
/// Parses valid absolute instruction source paths and omits malformed legacy values.
|
||||
pub fn instruction_source_path_uris(&self) -> Vec<PathUri> {
|
||||
instruction_source_path_uris(&self.instruction_sources)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS, ExperimentalApi,
|
||||
)]
|
||||
@@ -375,9 +384,9 @@ pub struct ThreadResumeResponse {
|
||||
#[experimental("thread/resume.runtimeWorkspaceRoots")]
|
||||
#[serde(default)]
|
||||
pub runtime_workspace_roots: Vec<AbsolutePathBuf>,
|
||||
/// Instruction source files currently loaded for this thread.
|
||||
/// Environment-native paths to instruction source files currently loaded for this thread.
|
||||
#[serde(default)]
|
||||
pub instruction_sources: Vec<AbsolutePathBuf>,
|
||||
pub instruction_sources: Vec<LegacyAppPathString>,
|
||||
#[experimental(nested)]
|
||||
pub approval_policy: AskForApproval,
|
||||
/// Reviewer currently used for approval requests on this thread.
|
||||
@@ -397,6 +406,13 @@ pub struct ThreadResumeResponse {
|
||||
pub initial_turns_page: Option<TurnsPage>,
|
||||
}
|
||||
|
||||
impl ThreadResumeResponse {
|
||||
/// Parses valid absolute instruction source paths and omits malformed legacy values.
|
||||
pub fn instruction_source_path_uris(&self) -> Vec<PathUri> {
|
||||
instruction_source_path_uris(&self.instruction_sources)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -523,9 +539,9 @@ pub struct ThreadForkResponse {
|
||||
#[experimental("thread/fork.runtimeWorkspaceRoots")]
|
||||
#[serde(default)]
|
||||
pub runtime_workspace_roots: Vec<AbsolutePathBuf>,
|
||||
/// Instruction source files currently loaded for this thread.
|
||||
/// Environment-native paths to instruction source files currently loaded for this thread.
|
||||
#[serde(default)]
|
||||
pub instruction_sources: Vec<AbsolutePathBuf>,
|
||||
pub instruction_sources: Vec<LegacyAppPathString>,
|
||||
#[experimental(nested)]
|
||||
pub approval_policy: AskForApproval,
|
||||
/// Reviewer currently used for approval requests on this thread.
|
||||
@@ -541,6 +557,30 @@ pub struct ThreadForkResponse {
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
}
|
||||
|
||||
impl ThreadForkResponse {
|
||||
/// Parses valid absolute instruction source paths and omits malformed legacy values.
|
||||
pub fn instruction_source_path_uris(&self) -> Vec<PathUri> {
|
||||
instruction_source_path_uris(&self.instruction_sources)
|
||||
}
|
||||
}
|
||||
|
||||
fn instruction_source_path_uris(sources: &[LegacyAppPathString]) -> Vec<PathUri> {
|
||||
// Instruction sources are advisory diagnostics. Warn and fail open so a malformed legacy
|
||||
// path cannot fail thread start, resume, or fork.
|
||||
sources
|
||||
.iter()
|
||||
.filter_map(|source| {
|
||||
source.to_inferred_path_uri().or_else(|| {
|
||||
tracing::warn!(
|
||||
path = source.as_str(),
|
||||
"ignoring invalid instruction source path from app-server"
|
||||
);
|
||||
None
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
|
||||
Reference in New Issue
Block a user