Expose instruction sources (AGENTS.md) via app server (#17506)

Addresses #17498

Problem: The TUI derived /status instruction source paths from the local
client environment, which could show stale <none> output or incorrect
paths when connected to a remote app server.

Solution: Add an app-server v2 instructionSources snapshot to thread
start/resume/fork responses, default it to an empty list when older
servers omit it, and render TUI /status from that server-provided
session data.

Additional context: The app-server field is intentionally named
instructionSources rather than AGENTS.md-specific terminology because
the loaded instruction sources can include global instructions, project
AGENTS.md files, AGENTS.override.md, user-defined instruction files, and
future dynamic sources.
This commit is contained in:
Eric Traut
2026-04-12 15:50:12 -07:00
committed by GitHub
parent 470510174b
commit 46ab9974dc
23 changed files with 302 additions and 69 deletions

View File

@@ -2719,6 +2719,9 @@ pub struct ThreadStartResponse {
pub model_provider: String,
pub service_tier: Option<ServiceTier>,
pub cwd: PathBuf,
/// Instruction source files currently loaded for this thread.
#[serde(default)]
pub instruction_sources: Vec<PathBuf>,
#[experimental(nested)]
pub approval_policy: AskForApproval,
/// Reviewer currently used for approval requests on this thread.
@@ -2805,6 +2808,9 @@ pub struct ThreadResumeResponse {
pub model_provider: String,
pub service_tier: Option<ServiceTier>,
pub cwd: PathBuf,
/// Instruction source files currently loaded for this thread.
#[serde(default)]
pub instruction_sources: Vec<PathBuf>,
#[experimental(nested)]
pub approval_policy: AskForApproval,
/// Reviewer currently used for approval requests on this thread.
@@ -2882,6 +2888,9 @@ pub struct ThreadForkResponse {
pub model_provider: String,
pub service_tier: Option<ServiceTier>,
pub cwd: PathBuf,
/// Instruction source files currently loaded for this thread.
#[serde(default)]
pub instruction_sources: Vec<PathBuf>,
#[experimental(nested)]
pub approval_policy: AskForApproval,
/// Reviewer currently used for approval requests on this thread.
@@ -8506,6 +8515,50 @@ mod tests {
assert_eq!(serialized_without_override.get("serviceTier"), None);
}
#[test]
fn thread_lifecycle_responses_default_missing_instruction_sources() {
let response = json!({
"thread": {
"id": "thread-id",
"forkedFromId": null,
"preview": "",
"ephemeral": false,
"modelProvider": "openai",
"createdAt": 1,
"updatedAt": 1,
"status": { "type": "idle" },
"path": null,
"cwd": "/tmp",
"cliVersion": "0.0.0",
"source": "exec",
"agentNickname": null,
"agentRole": null,
"gitInfo": null,
"name": null,
"turns": []
},
"model": "gpt-5",
"modelProvider": "openai",
"serviceTier": null,
"cwd": "/tmp",
"approvalPolicy": "on-failure",
"approvalsReviewer": "user",
"sandbox": { "type": "dangerFullAccess" },
"reasoningEffort": null
});
let start: ThreadStartResponse =
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");
assert_eq!(start.instruction_sources, Vec::<PathBuf>::new());
assert_eq!(resume.instruction_sources, Vec::<PathBuf>::new());
assert_eq!(fork.instruction_sources, Vec::<PathBuf>::new());
}
#[test]
fn turn_start_params_preserve_explicit_null_service_tier() {
let params: TurnStartParams = serde_json::from_value(json!({