app-server: preserve command item cwd

This commit is contained in:
Adam Perry
2026-06-16 17:39:01 +00:00
parent bd47c95c4c
commit 4a4c8fad12
36 changed files with 154 additions and 65 deletions
@@ -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."
@@ -17392,7 +17392,7 @@
"cwd": {
"allOf": [
{
"$ref": "#/definitions/v2/AbsolutePathBuf"
"$ref": "#/definitions/v2/ApiPathString"
}
],
"description": "The command's working directory."
@@ -15200,7 +15200,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": {
@@ -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."
@@ -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."
@@ -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."
@@ -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)]
+1
View File
@@ -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,
+2 -1
View File
@@ -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 {
+2 -2
View File
@@ -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 {