mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
app-server: preserve command item cwd
This commit is contained in:
@@ -823,7 +823,7 @@ fn sample_command_execution_item_with_id(
|
||||
ThreadItem::CommandExecution {
|
||||
id: id.to_string(),
|
||||
command: "echo hi".to_string(),
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cwd: serde_json::from_value(json!("/tmp")).expect("API cwd should deserialize"),
|
||||
process_id: Some("pid-1".to_string()),
|
||||
source: CommandExecutionSource::Agent,
|
||||
status,
|
||||
|
||||
@@ -3964,7 +3964,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
+1
-1
@@ -17392,7 +17392,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/v2/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
+1
-1
@@ -15200,7 +15200,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
+4
-1
@@ -5,6 +5,9 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -687,7 +690,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -687,7 +690,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -831,7 +834,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApprovalsReviewer": {
|
||||
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
|
||||
"enum": [
|
||||
@@ -1315,7 +1318,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -1130,7 +1133,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
+4
-1
@@ -8,6 +8,9 @@
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -1130,7 +1133,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -1130,7 +1133,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApprovalsReviewer": {
|
||||
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
|
||||
"enum": [
|
||||
@@ -1315,7 +1318,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -1130,7 +1133,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
@@ -1318,7 +1318,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
+4
-1
@@ -8,6 +8,9 @@
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -1130,7 +1133,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -1130,7 +1133,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
+4
-1
@@ -5,6 +5,9 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -831,7 +834,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -831,7 +834,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ByteRange": {
|
||||
"properties": {
|
||||
"end": {
|
||||
@@ -831,7 +834,7 @@
|
||||
"cwd": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
}
|
||||
],
|
||||
"description": "The command's working directory."
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
import type { ApiPathString } from "../ApiPathString";
|
||||
import type { MessagePhase } from "../MessagePhase";
|
||||
import type { ReasoningEffort } from "../ReasoningEffort";
|
||||
import type { JsonValue } from "../serde_json/JsonValue";
|
||||
@@ -32,7 +33,7 @@ command: string,
|
||||
/**
|
||||
* The command's working directory.
|
||||
*/
|
||||
cwd: AbsolutePathBuf,
|
||||
cwd: ApiPathString,
|
||||
/**
|
||||
* Identifier for the underlying PTY process (when available).
|
||||
*/
|
||||
|
||||
@@ -43,13 +43,28 @@ use std::path::PathBuf;
|
||||
|
||||
#[expect(
|
||||
clippy::expect_used,
|
||||
reason = "legacy app-server command items still require a host-native cwd"
|
||||
reason = "command event paths are absolute and have an inferred convention"
|
||||
)]
|
||||
fn project_command_cwd_to_host(cwd: &PathUri) -> AbsolutePathBuf {
|
||||
let cwd = ApiPathString::from_path_uri(cwd, PathConvention::native())
|
||||
fn command_cwd_for_api_and_actions(cwd: &PathUri) -> (ApiPathString, AbsolutePathBuf) {
|
||||
let convention = cwd
|
||||
.infer_path_convention()
|
||||
.expect("command cwd should have a native path convention");
|
||||
let api_cwd = ApiPathString::from_path_uri(cwd, convention)
|
||||
.expect("command cwd should render using its native path convention");
|
||||
let host_cwd = ApiPathString::from_path_uri(cwd, PathConvention::native())
|
||||
.expect("command cwd should render using the app-server host path convention");
|
||||
AbsolutePathBuf::try_from(cwd.into_string())
|
||||
.expect("projected command cwd should be absolute on the app-server host")
|
||||
let host_cwd = AbsolutePathBuf::try_from(host_cwd.into_string())
|
||||
.expect("projected command cwd should be absolute on the app-server host");
|
||||
(api_cwd, host_cwd)
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::expect_used,
|
||||
reason = "guardian command paths are native to the app-server host"
|
||||
)]
|
||||
fn api_path_from_host(cwd: &AbsolutePathBuf) -> ApiPathString {
|
||||
ApiPathString::from_abs_path(cwd, PathConvention::native())
|
||||
.expect("guardian command cwd should render using the host path convention")
|
||||
}
|
||||
|
||||
pub fn build_file_change_approval_request_item(
|
||||
@@ -81,11 +96,11 @@ pub fn build_file_change_end_item(payload: &PatchApplyEndEvent) -> ThreadItem {
|
||||
pub fn build_command_execution_approval_request_item(
|
||||
payload: &ExecApprovalRequestEvent,
|
||||
) -> ThreadItem {
|
||||
let cwd = project_command_cwd_to_host(&payload.cwd);
|
||||
let (cwd, host_cwd) = command_cwd_for_api_and_actions(&payload.cwd);
|
||||
ThreadItem::CommandExecution {
|
||||
id: payload.call_id.clone(),
|
||||
command: shlex_join(&payload.command),
|
||||
cwd: cwd.clone(),
|
||||
cwd,
|
||||
process_id: None,
|
||||
source: CommandExecutionSource::Agent,
|
||||
status: CommandExecutionStatus::InProgress,
|
||||
@@ -93,7 +108,7 @@ pub fn build_command_execution_approval_request_item(
|
||||
.parsed_cmd
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|parsed| CommandAction::from_core_with_cwd(parsed, &cwd))
|
||||
.map(|parsed| CommandAction::from_core_with_cwd(parsed, &host_cwd))
|
||||
.collect(),
|
||||
aggregated_output: None,
|
||||
exit_code: None,
|
||||
@@ -102,11 +117,11 @@ pub fn build_command_execution_approval_request_item(
|
||||
}
|
||||
|
||||
pub fn build_command_execution_begin_item(payload: &ExecCommandBeginEvent) -> ThreadItem {
|
||||
let cwd = project_command_cwd_to_host(&payload.cwd);
|
||||
let (cwd, host_cwd) = command_cwd_for_api_and_actions(&payload.cwd);
|
||||
ThreadItem::CommandExecution {
|
||||
id: payload.call_id.clone(),
|
||||
command: shlex_join(&payload.command),
|
||||
cwd: cwd.clone(),
|
||||
cwd,
|
||||
process_id: payload.process_id.clone(),
|
||||
source: payload.source.into(),
|
||||
status: CommandExecutionStatus::InProgress,
|
||||
@@ -114,7 +129,7 @@ pub fn build_command_execution_begin_item(payload: &ExecCommandBeginEvent) -> Th
|
||||
.parsed_cmd
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|parsed| CommandAction::from_core_with_cwd(parsed, &cwd))
|
||||
.map(|parsed| CommandAction::from_core_with_cwd(parsed, &host_cwd))
|
||||
.collect(),
|
||||
aggregated_output: None,
|
||||
exit_code: None,
|
||||
@@ -129,12 +144,12 @@ pub fn build_command_execution_end_item(payload: &ExecCommandEndEvent) -> Thread
|
||||
Some(payload.aggregated_output.clone())
|
||||
};
|
||||
let duration_ms = i64::try_from(payload.duration.as_millis()).unwrap_or(i64::MAX);
|
||||
let cwd = project_command_cwd_to_host(&payload.cwd);
|
||||
let (cwd, host_cwd) = command_cwd_for_api_and_actions(&payload.cwd);
|
||||
|
||||
ThreadItem::CommandExecution {
|
||||
id: payload.call_id.clone(),
|
||||
command: shlex_join(&payload.command),
|
||||
cwd: cwd.clone(),
|
||||
cwd,
|
||||
process_id: payload.process_id.clone(),
|
||||
source: payload.source.into(),
|
||||
status: (&payload.status).into(),
|
||||
@@ -142,7 +157,7 @@ pub fn build_command_execution_end_item(payload: &ExecCommandEndEvent) -> Thread
|
||||
.parsed_cmd
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|parsed| CommandAction::from_core_with_cwd(parsed, &cwd))
|
||||
.map(|parsed| CommandAction::from_core_with_cwd(parsed, &host_cwd))
|
||||
.collect(),
|
||||
aggregated_output,
|
||||
exit_code: Some(payload.exit_code),
|
||||
@@ -168,7 +183,7 @@ pub fn build_item_from_guardian_event(
|
||||
Some(ThreadItem::CommandExecution {
|
||||
id: id.clone(),
|
||||
command,
|
||||
cwd: cwd.clone(),
|
||||
cwd: api_path_from_host(cwd),
|
||||
process_id: None,
|
||||
source: CommandExecutionSource::Agent,
|
||||
status,
|
||||
@@ -204,7 +219,7 @@ pub fn build_item_from_guardian_event(
|
||||
Some(ThreadItem::CommandExecution {
|
||||
id: id.clone(),
|
||||
command,
|
||||
cwd: cwd.clone(),
|
||||
cwd: api_path_from_host(cwd),
|
||||
process_id: None,
|
||||
source: CommandExecutionSource::Agent,
|
||||
status,
|
||||
|
||||
@@ -1278,12 +1278,19 @@ mod tests {
|
||||
use codex_protocol::protocol::WebSearchEndEvent;
|
||||
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||
use codex_utils_absolute_path::test_support::test_path_buf;
|
||||
use codex_utils_path_uri::ApiPathString;
|
||||
use codex_utils_path_uri::PathConvention;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn test_api_path(path: &str) -> ApiPathString {
|
||||
ApiPathString::from_abs_path(&test_path_buf(path).abs(), PathConvention::native())
|
||||
.expect("API path")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_multiple_turns_with_reasoning_items() {
|
||||
let events = vec![
|
||||
@@ -2124,7 +2131,7 @@ mod tests {
|
||||
ThreadItem::CommandExecution {
|
||||
id: "exec-1".into(),
|
||||
command: "echo 'hello world'".into(),
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cwd: test_api_path("/tmp"),
|
||||
process_id: Some("pid-1".into()),
|
||||
source: CommandExecutionSource::Agent,
|
||||
status: CommandExecutionStatus::Completed,
|
||||
@@ -2355,7 +2362,7 @@ mod tests {
|
||||
ThreadItem::CommandExecution {
|
||||
id: "exec-declined".into(),
|
||||
command: "ls".into(),
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cwd: test_api_path("/tmp"),
|
||||
process_id: Some("pid-2".into()),
|
||||
source: CommandExecutionSource::Agent,
|
||||
status: CommandExecutionStatus::Declined,
|
||||
@@ -2453,7 +2460,7 @@ mod tests {
|
||||
ThreadItem::CommandExecution {
|
||||
id: "guardian-exec".into(),
|
||||
command: "rm -rf /tmp/guardian".into(),
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cwd: test_api_path("/tmp"),
|
||||
process_id: None,
|
||||
source: CommandExecutionSource::Agent,
|
||||
status: CommandExecutionStatus::Declined,
|
||||
@@ -2519,7 +2526,7 @@ mod tests {
|
||||
ThreadItem::CommandExecution {
|
||||
id: "guardian-execve".into(),
|
||||
command: "/bin/rm -f /tmp/file.sqlite".into(),
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cwd: test_api_path("/tmp"),
|
||||
process_id: None,
|
||||
source: CommandExecutionSource::Agent,
|
||||
status: CommandExecutionStatus::InProgress,
|
||||
@@ -2617,7 +2624,7 @@ mod tests {
|
||||
ThreadItem::CommandExecution {
|
||||
id: "exec-late".into(),
|
||||
command: "echo done".into(),
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cwd: test_api_path("/tmp"),
|
||||
process_id: Some("pid-42".into()),
|
||||
source: CommandExecutionSource::Agent,
|
||||
status: CommandExecutionStatus::Completed,
|
||||
|
||||
@@ -257,7 +257,7 @@ pub enum ThreadItem {
|
||||
/// The command to be executed.
|
||||
command: String,
|
||||
/// The command's working directory.
|
||||
cwd: AbsolutePathBuf,
|
||||
cwd: ApiPathString,
|
||||
/// Identifier for the underlying PTY process (when available).
|
||||
process_id: Option<String>,
|
||||
#[serde(default)]
|
||||
|
||||
@@ -1389,6 +1389,7 @@ There are additional item-specific events:
|
||||
|
||||
- `item/commandExecution/outputDelta` — streams stdout/stderr for the command; append deltas in order to render live output alongside `aggregatedOutput` in the final item.
|
||||
Final `commandExecution` items include parsed `commandActions`, `status`, `exitCode`, and `durationMs` so the UI can summarize what ran and whether it succeeded.
|
||||
- `cwd` uses the selected execution environment's native path syntax.
|
||||
|
||||
#### fileChange
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ enum CommandExecutionApprovalPresentation {
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct CommandExecutionCompletionItem {
|
||||
command: String,
|
||||
cwd: AbsolutePathBuf,
|
||||
cwd: ApiPathString,
|
||||
command_actions: Vec<V2ParsedCommand>,
|
||||
}
|
||||
|
||||
@@ -592,7 +592,7 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
let command_string = shlex_join(&command);
|
||||
let completion_item = CommandExecutionCompletionItem {
|
||||
command: command_string,
|
||||
cwd: cwd.clone(),
|
||||
cwd: api_cwd.clone(),
|
||||
command_actions: command_actions.clone(),
|
||||
};
|
||||
CommandExecutionApprovalPresentation::Command(completion_item)
|
||||
@@ -1370,7 +1370,7 @@ async fn start_command_execution_item(
|
||||
turn_id: String,
|
||||
item_id: String,
|
||||
command: String,
|
||||
cwd: AbsolutePathBuf,
|
||||
cwd: ApiPathString,
|
||||
command_actions: Vec<V2ParsedCommand>,
|
||||
source: CommandExecutionSource,
|
||||
outgoing: &ThreadScopedOutgoingMessageSender,
|
||||
@@ -1414,7 +1414,7 @@ async fn complete_command_execution_item(
|
||||
turn_id: String,
|
||||
item_id: String,
|
||||
command: String,
|
||||
cwd: AbsolutePathBuf,
|
||||
cwd: ApiPathString,
|
||||
process_id: Option<String>,
|
||||
source: CommandExecutionSource,
|
||||
command_actions: Vec<V2ParsedCommand>,
|
||||
@@ -2338,7 +2338,11 @@ mod tests {
|
||||
fn command_execution_completion_item(command: &str) -> CommandExecutionCompletionItem {
|
||||
CommandExecutionCompletionItem {
|
||||
command: command.to_string(),
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cwd: ApiPathString::from_abs_path(
|
||||
&test_path_buf("/tmp").abs(),
|
||||
PathConvention::native(),
|
||||
)
|
||||
.expect("API cwd"),
|
||||
command_actions: vec![V2ParsedCommand::Unknown {
|
||||
command: command.to_string(),
|
||||
}],
|
||||
|
||||
@@ -2439,7 +2439,11 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
|
||||
else {
|
||||
unreachable!("loop ensures we break on command execution items");
|
||||
};
|
||||
assert_eq!(cwd.as_path(), second_cwd.as_path());
|
||||
let second_cwd = codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(second_cwd)?;
|
||||
assert_eq!(
|
||||
cwd,
|
||||
ApiPathString::from_abs_path(&second_cwd, PathConvention::native())?
|
||||
);
|
||||
let expected_command = format_with_current_shell_display("echo second turn");
|
||||
assert_eq!(command, expected_command);
|
||||
assert_eq!(status, CommandExecutionStatus::InProgress);
|
||||
|
||||
@@ -32,6 +32,9 @@ use codex_app_server_protocol::TurnStatus;
|
||||
use codex_app_server_protocol::UserInput as V2UserInput;
|
||||
use codex_features::FEATURES;
|
||||
use codex_features::Feature;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::ApiPathString;
|
||||
use codex_utils_path_uri::PathConvention;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -167,7 +170,11 @@ async fn turn_start_shell_zsh_fork_executes_command_v2() -> Result<()> {
|
||||
assert!(command.contains("/bin/sh -c"));
|
||||
assert!(command.contains("sleep 0.01"));
|
||||
assert!(command.contains(&release_marker.display().to_string()));
|
||||
assert_eq!(cwd.as_path(), workspace.as_path());
|
||||
let workspace = AbsolutePathBuf::try_from(workspace)?;
|
||||
assert_eq!(
|
||||
cwd,
|
||||
ApiPathString::from_abs_path(&workspace, PathConvention::native())?
|
||||
);
|
||||
|
||||
mcp.interrupt_turn_and_wait_for_aborted(thread.id, turn.id, DEFAULT_READ_TIMEOUT)
|
||||
.await?;
|
||||
|
||||
@@ -332,8 +332,7 @@ async fn app_server_starts_thread_with_windows_environment_native_cwd() -> Resul
|
||||
unreachable!("loop returns only command execution items");
|
||||
};
|
||||
assert_eq!(id, CALL_ID);
|
||||
// TODO(anp): Make command execution cwd a PathUri so this stays `C:\windows`.
|
||||
assert_eq!(cwd, std::path::PathBuf::from("/C:/windows").abs());
|
||||
assert_eq!(cwd.as_str(), r"C:\windows");
|
||||
// TODO(anp): Parse command actions using the selected environment's path convention so
|
||||
// their paths remain Windows-native instead of degrading the action to Unknown.
|
||||
assert_eq!(command_actions.len(), 1);
|
||||
|
||||
@@ -71,7 +71,7 @@ impl EventProcessorWithHumanOutput {
|
||||
"{}\n{} in {}",
|
||||
"exec".style(self.italic).style(self.magenta),
|
||||
command.style(self.bold),
|
||||
cwd.display()
|
||||
cwd.as_str()
|
||||
);
|
||||
}
|
||||
ThreadItem::McpToolCall { server, tool, .. } => {
|
||||
|
||||
@@ -35,6 +35,8 @@ use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::SessionConfiguredEvent;
|
||||
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||
use codex_utils_absolute_path::test_support::test_path_buf;
|
||||
use codex_utils_path_uri::ApiPathString;
|
||||
use codex_utils_path_uri::PathConvention;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
@@ -75,6 +77,12 @@ use codex_exec::TurnStartedEvent;
|
||||
use codex_exec::Usage;
|
||||
use codex_exec::WebSearchItem;
|
||||
|
||||
#[expect(clippy::expect_used, reason = "test paths are absolute")]
|
||||
fn api_path(path: &str) -> ApiPathString {
|
||||
ApiPathString::from_abs_path(&test_path_buf(path).abs(), PathConvention::native())
|
||||
.expect("API path")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_todo_items_preserves_text_and_completion_state() {
|
||||
let items = EventProcessorWithJsonOutput::map_todo_items(&[
|
||||
@@ -170,7 +178,7 @@ fn command_execution_started_and_completed_translate_to_thread_events() {
|
||||
let command_item = ThreadItem::CommandExecution {
|
||||
id: "cmd-1".to_string(),
|
||||
command: "ls".to_string(),
|
||||
cwd: test_path_buf("/tmp/project").abs(),
|
||||
cwd: api_path("/tmp/project"),
|
||||
process_id: Some("123".to_string()),
|
||||
source: CommandExecutionSource::UserShell,
|
||||
status: ApiCommandExecutionStatus::InProgress,
|
||||
@@ -210,7 +218,7 @@ fn command_execution_started_and_completed_translate_to_thread_events() {
|
||||
item: ThreadItem::CommandExecution {
|
||||
id: "cmd-1".to_string(),
|
||||
command: "ls".to_string(),
|
||||
cwd: test_path_buf("/tmp/project").abs(),
|
||||
cwd: api_path("/tmp/project"),
|
||||
process_id: Some("123".to_string()),
|
||||
source: CommandExecutionSource::UserShell,
|
||||
status: ApiCommandExecutionStatus::Completed,
|
||||
@@ -1321,7 +1329,7 @@ fn turn_completion_reconciles_started_items_from_turn_items() {
|
||||
item: ThreadItem::CommandExecution {
|
||||
id: "cmd-1".to_string(),
|
||||
command: "ls".to_string(),
|
||||
cwd: test_path_buf("/tmp/project").abs(),
|
||||
cwd: api_path("/tmp/project"),
|
||||
process_id: Some("123".to_string()),
|
||||
source: CommandExecutionSource::UserShell,
|
||||
status: ApiCommandExecutionStatus::InProgress,
|
||||
@@ -1361,7 +1369,7 @@ fn turn_completion_reconciles_started_items_from_turn_items() {
|
||||
items: vec![ThreadItem::CommandExecution {
|
||||
id: "cmd-1".to_string(),
|
||||
command: "ls".to_string(),
|
||||
cwd: test_path_buf("/tmp/project").abs(),
|
||||
cwd: api_path("/tmp/project"),
|
||||
process_id: Some("123".to_string()),
|
||||
source: CommandExecutionSource::UserShell,
|
||||
status: ApiCommandExecutionStatus::Completed,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::*;
|
||||
use crate::test_support::test_api_path;
|
||||
use codex_app_server_protocol::CommandExecutionSource;
|
||||
use codex_app_server_protocol::CommandExecutionStatus;
|
||||
use codex_app_server_protocol::ItemCompletedNotification;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
||||
#[test]
|
||||
fn agent_status_uses_bounded_buffered_activity() {
|
||||
@@ -12,7 +12,7 @@ fn agent_status_uses_bounded_buffered_activity() {
|
||||
item: ThreadItem::CommandExecution {
|
||||
id: "command-1".to_string(),
|
||||
command: "cargo test -p codex-tui".to_string(),
|
||||
cwd: AbsolutePathBuf::try_from("/workspace").expect("absolute path"),
|
||||
cwd: test_api_path("/workspace"),
|
||||
process_id: None,
|
||||
source: CommandExecutionSource::Agent,
|
||||
status: CommandExecutionStatus::Completed,
|
||||
|
||||
@@ -138,6 +138,7 @@ impl QueuedInterrupt {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::approval_events::ExecApprovalRequestEvent;
|
||||
use crate::test_support::test_api_path;
|
||||
use codex_app_server_protocol::CommandExecutionSource;
|
||||
use codex_app_server_protocol::CommandExecutionStatus;
|
||||
use codex_app_server_protocol::ThreadItem;
|
||||
@@ -176,7 +177,7 @@ mod tests {
|
||||
ThreadItem::CommandExecution {
|
||||
id: call_id.to_string(),
|
||||
command: "true".to_string(),
|
||||
cwd: AbsolutePathBuf::current_dir().expect("current dir"),
|
||||
cwd: test_api_path("/tmp"),
|
||||
process_id: None,
|
||||
source: CommandExecutionSource::Agent,
|
||||
status: CommandExecutionStatus::InProgress,
|
||||
|
||||
@@ -521,7 +521,7 @@ async fn live_app_server_command_execution_strips_shell_wrapper() {
|
||||
item: AppServerThreadItem::CommandExecution {
|
||||
id: "cmd-1".to_string(),
|
||||
command: command.clone(),
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cwd: test_api_path("/tmp"),
|
||||
process_id: None,
|
||||
source: AppServerCommandExecutionSource::UserShell,
|
||||
status: AppServerCommandExecutionStatus::InProgress,
|
||||
@@ -543,7 +543,7 @@ async fn live_app_server_command_execution_strips_shell_wrapper() {
|
||||
item: AppServerThreadItem::CommandExecution {
|
||||
id: "cmd-1".to_string(),
|
||||
command,
|
||||
cwd: test_path_buf("/tmp").abs(),
|
||||
cwd: test_api_path("/tmp"),
|
||||
process_id: None,
|
||||
source: AppServerCommandExecutionSource::UserShell,
|
||||
status: AppServerCommandExecutionStatus::Completed,
|
||||
|
||||
@@ -352,7 +352,7 @@ async fn exec_end_without_begin_uses_event_command() {
|
||||
.into_iter()
|
||||
.map(|parsed| AppServerCommandAction::from_core_with_cwd(parsed, &chat.config.cwd))
|
||||
.collect();
|
||||
let cwd = chat.config.cwd.clone();
|
||||
let cwd = test_api_path("/tmp/project");
|
||||
handle_exec_end(
|
||||
&mut chat,
|
||||
AppServerThreadItem::CommandExecution {
|
||||
|
||||
@@ -821,7 +821,7 @@ pub(super) fn begin_exec_with_source(
|
||||
let item = AppServerThreadItem::CommandExecution {
|
||||
id: call_id.to_string(),
|
||||
command: codex_shell_command::parse_command::shlex_join(&command),
|
||||
cwd: chat.config.cwd.clone(),
|
||||
cwd: test_api_path("/tmp/project"),
|
||||
process_id: None,
|
||||
source,
|
||||
status: AppServerCommandExecutionStatus::InProgress,
|
||||
@@ -844,7 +844,7 @@ pub(super) fn begin_unified_exec_startup(
|
||||
let item = AppServerThreadItem::CommandExecution {
|
||||
id: call_id.to_string(),
|
||||
command: codex_shell_command::parse_command::shlex_join(&command),
|
||||
cwd: chat.config.cwd.clone(),
|
||||
cwd: test_api_path("/tmp/project"),
|
||||
process_id: Some(process_id.to_string()),
|
||||
source: ExecCommandSource::UnifiedExecStartup,
|
||||
status: AppServerCommandExecutionStatus::InProgress,
|
||||
|
||||
@@ -3607,7 +3607,7 @@ async fn chatwidget_exec_and_status_layout_vt100_snapshot() {
|
||||
.cloned()
|
||||
.map(|parsed| AppServerCommandAction::from_core_with_cwd(parsed, &chat.config.cwd))
|
||||
.collect::<Vec<_>>();
|
||||
let cwd = chat.config.cwd.clone();
|
||||
let cwd = test_api_path("/tmp/project");
|
||||
handle_exec_begin(
|
||||
&mut chat,
|
||||
AppServerThreadItem::CommandExecution {
|
||||
|
||||
Reference in New Issue
Block a user