Skip to content

Commit 957aac9

Browse files
authored
Merge branch 'main' into canvrno/plugins_tui_no_filter
2 parents 1a45308 + 9deb8ce commit 957aac9

File tree

76 files changed

+5519
-1914
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+5519
-1914
lines changed

.github/workflows/rust-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ jobs:
547547
tests:
548548
name: Tests — ${{ matrix.runner }} - ${{ matrix.target }}${{ matrix.remote_env == 'true' && ' (remote)' || '' }}
549549
runs-on: ${{ matrix.runs_on || matrix.runner }}
550-
timeout-minutes: 30
550+
timeout-minutes: ${{ matrix.runner == 'windows-arm64' && 35 || 30 }}
551551
needs: changed
552552
if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }}
553553
defaults:

codex-rs/Cargo.lock

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ members = [
4242
"protocol",
4343
"rmcp-client",
4444
"responses-api-proxy",
45+
"sandboxing",
4546
"stdio-to-uds",
4647
"otel",
4748
"tui",
@@ -130,6 +131,7 @@ codex-process-hardening = { path = "process-hardening" }
130131
codex-protocol = { path = "protocol" }
131132
codex-responses-api-proxy = { path = "responses-api-proxy" }
132133
codex-rmcp-client = { path = "rmcp-client" }
134+
codex-sandboxing = { path = "sandboxing" }
133135
codex-secrets = { path = "secrets" }
134136
codex-shell-command = { path = "shell-command" }
135137
codex-shell-escalation = { path = "shell-escalation" }
@@ -288,7 +290,7 @@ supports-color = "3.0.2"
288290
syntect = "5"
289291
sys-locale = "0.3.2"
290292
tempfile = "3.23.0"
291-
tar = "0.4.44"
293+
tar = "0.4.45"
292294
test-log = "0.2.19"
293295
textwrap = "0.16.2"
294296
thiserror = "2.0.17"

codex-rs/app-server-protocol/src/protocol/thread_history.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ pub struct ThreadHistoryBuilder {
7474
turns: Vec<Turn>,
7575
current_turn: Option<PendingTurn>,
7676
next_item_index: i64,
77+
current_rollout_index: usize,
78+
next_rollout_index: usize,
7779
}
7880

7981
impl Default for ThreadHistoryBuilder {
@@ -88,6 +90,8 @@ impl ThreadHistoryBuilder {
8890
turns: Vec::new(),
8991
current_turn: None,
9092
next_item_index: 1,
93+
current_rollout_index: 0,
94+
next_rollout_index: 0,
9195
}
9296
}
9397

@@ -111,6 +115,19 @@ impl ThreadHistoryBuilder {
111115
self.current_turn.is_some()
112116
}
113117

118+
pub fn active_turn_id_if_explicit(&self) -> Option<String> {
119+
self.current_turn
120+
.as_ref()
121+
.filter(|turn| turn.opened_explicitly)
122+
.map(|turn| turn.id.clone())
123+
}
124+
125+
pub fn active_turn_start_index(&self) -> Option<usize> {
126+
self.current_turn
127+
.as_ref()
128+
.map(|turn| turn.rollout_start_index)
129+
}
130+
114131
/// Shared reducer for persisted rollout replay and in-memory current-turn
115132
/// tracking used by running thread resume/rejoin.
116133
///
@@ -182,6 +199,8 @@ impl ThreadHistoryBuilder {
182199
}
183200

184201
pub fn handle_rollout_item(&mut self, item: &RolloutItem) {
202+
self.current_rollout_index = self.next_rollout_index;
203+
self.next_rollout_index += 1;
185204
match item {
186205
RolloutItem::EventMsg(event) => self.handle_event(event),
187206
RolloutItem::Compacted(payload) => self.handle_compacted(payload),
@@ -974,6 +993,7 @@ impl ThreadHistoryBuilder {
974993
status: TurnStatus::Completed,
975994
opened_explicitly: false,
976995
saw_compaction: false,
996+
rollout_start_index: self.current_rollout_index,
977997
}
978998
}
979999

@@ -1137,6 +1157,8 @@ struct PendingTurn {
11371157
/// True when this turn includes a persisted `RolloutItem::Compacted`, which
11381158
/// should keep the turn from being dropped even without normal items.
11391159
saw_compaction: bool,
1160+
/// Index of the rollout item that opened this turn during replay.
1161+
rollout_start_index: usize,
11401162
}
11411163

11421164
impl PendingTurn {

codex-rs/app-server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ codex-protocol = { workspace = true }
4646
codex-app-server-protocol = { workspace = true }
4747
codex-feedback = { workspace = true }
4848
codex-rmcp-client = { workspace = true }
49+
codex-sandboxing = { workspace = true }
4950
codex-state = { workspace = true }
5051
codex-utils-absolute-path = { workspace = true }
5152
codex-utils-json-to-toml = { workspace = true }

codex-rs/app-server/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ Example with notification opt-out:
125125

126126
- `thread/start` — create a new thread; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for that thread.
127127
- `thread/resume` — reopen an existing thread by id so subsequent `turn/start` calls append to it.
128-
- `thread/fork` — fork an existing thread into a new thread id by copying the stored history; accepts `ephemeral: true` for an in-memory temporary fork, emits `thread/started` (including the current `thread.status`), and auto-subscribes you to turn/item events for the new thread.
128+
- `thread/fork` — fork an existing thread into a new thread id by copying the stored history; if the source thread is currently mid-turn, the fork records the same interruption marker as `turn/interrupt` instead of inheriting an unmarked partial turn suffix. Accepts `ephemeral: true` for an in-memory temporary fork, emits `thread/started` (including the current `thread.status`), and auto-subscribes you to turn/item events for the new thread.
129129
- `thread/list` — page through stored rollouts; supports cursor-based pagination and optional `modelProviders`, `sourceKinds`, `archived`, `cwd`, and `searchTerm` filters. Each returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded.
130130
- `thread/loaded/list` — list the thread ids currently loaded in memory.
131131
- `thread/read` — read a stored thread by id without resuming it; optionally include turns via `includeTurns`. The returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded.
@@ -240,7 +240,7 @@ Example:
240240
{ "id": 11, "result": { "thread": { "id": "thr_123", } } }
241241
```
242242

243-
To branch from a stored session, call `thread/fork` with the `thread.id`. This creates a new thread id and emits a `thread/started` notification for it. Pass `ephemeral: true` when the fork should stay in-memory only:
243+
To branch from a stored session, call `thread/fork` with the `thread.id`. This creates a new thread id and emits a `thread/started` notification for it. If the source thread is actively running, the fork snapshots it as if the current turn had been interrupted first. Pass `ephemeral: true` when the fork should stay in-memory only:
244244

245245
```json
246246
{ "method": "thread/fork", "id": 12, "params": { "threadId": "thr_123", "ephemeral": true } }

codex-rs/app-server/src/bespoke_event_handling.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ use codex_core::ThreadManager;
110110
use codex_core::find_thread_name_by_id;
111111
use codex_core::review_format::format_review_findings_block;
112112
use codex_core::review_prompts;
113-
use codex_core::sandboxing::intersect_permission_profiles;
114113
use codex_protocol::ThreadId;
115114
use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem;
116115
use codex_protocol::dynamic_tools::DynamicToolResponse as CoreDynamicToolResponse;
@@ -136,6 +135,7 @@ use codex_protocol::request_permissions::RequestPermissionProfile as CoreRequest
136135
use codex_protocol::request_permissions::RequestPermissionsResponse as CoreRequestPermissionsResponse;
137136
use codex_protocol::request_user_input::RequestUserInputAnswer as CoreRequestUserInputAnswer;
138137
use codex_protocol::request_user_input::RequestUserInputResponse as CoreRequestUserInputResponse;
138+
use codex_sandboxing::policy_transforms::intersect_permission_profiles;
139139
use codex_shell_command::parse_command::shlex_join;
140140
use std::collections::HashMap;
141141
use std::convert::TryFrom;

codex-rs/app-server/src/codex_message_processor.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ use codex_core::AuthManager;
183183
use codex_core::CodexAuth;
184184
use codex_core::CodexThread;
185185
use codex_core::Cursor as RolloutCursor;
186+
use codex_core::ForkSnapshot;
186187
use codex_core::NewThread;
187188
use codex_core::RolloutRecorder;
188189
use codex_core::SessionMeta;
@@ -4039,7 +4040,7 @@ impl CodexMessageProcessor {
40394040
} = match self
40404041
.thread_manager
40414042
.fork_thread(
4042-
usize::MAX,
4043+
ForkSnapshot::Interrupted,
40434044
config,
40444045
rollout_path.clone(),
40454046
persist_extended_history,
@@ -6508,7 +6509,7 @@ impl CodexMessageProcessor {
65086509
} = self
65096510
.thread_manager
65106511
.fork_thread(
6511-
usize::MAX,
6512+
ForkSnapshot::Interrupted,
65126513
config,
65136514
rollout_path,
65146515
/*persist_extended_history*/ false,

codex-rs/app-server/tests/suite/v2/thread_fork.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> {
123123
"expected forked thread to include one turn"
124124
);
125125
let turn = &thread.turns[0];
126-
assert_eq!(turn.status, TurnStatus::Completed);
126+
assert_eq!(turn.status, TurnStatus::Interrupted);
127127
assert_eq!(turn.items.len(), 1, "expected user message item");
128128
match &turn.items[0] {
129129
ThreadItem::UserMessage { content, .. } => {

codex-rs/app-server/tests/suite/v2/thread_start.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -585,9 +585,10 @@ request_max_retries = 0
585585
stream_max_retries = 0
586586
587587
[mcp_servers.required_broken]
588-
command = "codex-definitely-not-a-real-binary"
588+
{required_broken_transport}
589589
required = true
590-
"#
590+
"#,
591+
required_broken_transport = broken_mcp_transport_toml()
591592
),
592593
)
593594
}
@@ -615,8 +616,21 @@ request_max_retries = 0
615616
stream_max_retries = 0
616617
617618
[mcp_servers.optional_broken]
618-
command = "codex-definitely-not-a-real-binary"
619-
"#
619+
{optional_broken_transport}
620+
"#,
621+
optional_broken_transport = broken_mcp_transport_toml()
620622
),
621623
)
622624
}
625+
626+
#[cfg(target_os = "windows")]
627+
fn broken_mcp_transport_toml() -> &'static str {
628+
r#"command = "cmd"
629+
args = ["/C", "exit 1"]"#
630+
}
631+
632+
#[cfg(not(target_os = "windows"))]
633+
fn broken_mcp_transport_toml() -> &'static str {
634+
r#"command = "/bin/sh"
635+
args = ["-c", "exit 1"]"#
636+
}

0 commit comments

Comments
 (0)