Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b1c93e1
chore(apply-patch) additional scenarios (#8230)
dylan-hurd-oai Jan 5, 2026
771f1ca
fix: accept whitespace-padded patch markers (#8746)
tibo-openai Jan 6, 2026
c92dbea
tui2: stop baking streaming wraps; reflow agent markdown (#8761)
joshka-oai Jan 6, 2026
4c3d2a5
fix: render cwd-relative paths in tui (#8771)
joshka-oai Jan 6, 2026
6346e4f
fix: fix readiness subscribe token wrap-around (#8770)
tibo-openai Jan 6, 2026
8858012
chore: emit unified exec begin only when PTY exist (#8780)
jif-oai Jan 6, 2026
7ecd0dc
fix: stop honoring CODEX_MANAGED_CONFIG_PATH environment variable in …
bolinfest Jan 6, 2026
06e21c7
fix: update model examples to gpt-5.2 (#8566)
seuros Jan 6, 2026
32db8ea
feat: add head-tail buffer for `unified_exec` (#8735)
jif-oai Jan 6, 2026
cab7136
chore: add model/list call to app-server-test-client (#8331)
owenlin0 Jan 6, 2026
d1c6329
feat: forced tool tips (#8752)
jif-oai Jan 6, 2026
740bf0e
chore: clear background terminals on interrupt (#8786)
jif-oai Jan 6, 2026
915352b
feat: add analytics config setting (#8350)
JaviSoto Jan 6, 2026
1dd1355
feat: agent controller (#8783)
jif-oai Jan 6, 2026
4ce9d0a
suppress popups while browsing input history (#8772)
xl-openai Jan 6, 2026
a0b2d03
Clear copy pill background and add snapshot test (#8777)
joshka-oai Jan 6, 2026
188f79a
feat: drop agent bus and store the agent status in codex directly (#8…
jif-oai Jan 6, 2026
8b7ec31
feat(app-server): thread/rollback API (#8454)
owenlin0 Jan 6, 2026
11d4f3f
[app-server] fix config loading for conversations (#8765)
celia-oai Jan 6, 2026
54ded1a
add web_search_cached flag (#8795)
sayan-oai Jan 6, 2026
dc1a568
fix: populate the release notes when the release is created (#8799)
bolinfest Jan 6, 2026
8b4d27d
fix: truncate long approval prefixes when rendering (#8734)
tibo-openai Jan 6, 2026
3389465
Enable model upgrade popup even when selected model is no longer in p…
charley-oai Jan 7, 2026
e758c19
Merge upstream/main
ScriptedAlchemy Jan 7, 2026
14ece88
Fix exec-server nextest timeouts by avoiding per-test DotSlash cache
ScriptedAlchemy Jan 7, 2026
9bfe417
Avoid nextest timeouts by using DotSlash default cache in exec-server…
ScriptedAlchemy Jan 7, 2026
00de44e
Remove CLA Assistant GitHub Action
ScriptedAlchemy Jan 7, 2026
a2b483d
cargo-deny: bump lru and ignore ratatui unsound advisory
ScriptedAlchemy Jan 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 0 additions & 49 deletions .github/workflows/cla.yml

This file was deleted.

21 changes: 21 additions & 0 deletions .github/workflows/rust-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,26 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v6

- name: Generate release notes from tag commit message
id: release_notes
shell: bash
run: |
set -euo pipefail

# On tag pushes, GITHUB_SHA may be a tag object for annotated tags;
# peel it to the underlying commit.
commit="$(git rev-parse "${GITHUB_SHA}^{commit}")"
notes_path="${RUNNER_TEMP}/release-notes.md"

# Use the commit message for the commit the tag points at (not the
# annotated tag message).
git log -1 --format=%B "${commit}" > "${notes_path}"
# Ensure trailing newline so GitHub's markdown renderer doesn't
# occasionally run the last line into subsequent content.
echo >> "${notes_path}"

echo "path=${notes_path}" >> "${GITHUB_OUTPUT}"

- uses: actions/download-artifact@v7
with:
path: dist
Expand Down Expand Up @@ -395,6 +415,7 @@ jobs:
with:
name: ${{ steps.release_name.outputs.name }}
tag_name: ${{ github.ref_name }}
body_path: ${{ steps.release_notes.outputs.path }}
files: dist/**
# Mark as prerelease only when the version has a suffix after x.y.z
# (e.g. -alpha, -beta). Otherwise publish a normal release.
Expand Down
16 changes: 16 additions & 0 deletions announcement_tip.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Example announcement tips for Codex TUI.
# Each [[announcements]] entry is evaluated in order; the last matching one is shown.
# Dates are UTC, formatted as YYYY-MM-DD. The from_date is inclusive and the to_date is exclusive.
# version_regex matches against the CLI version (env!("CARGO_PKG_VERSION")); omit to apply to all versions.
# target_app specify which app should display the announcement (cli, vsce, ...).

[[announcements]]
content = "Welcome to Codex! Check out the new onboarding flow."
from_date = "2024-10-01"
to_date = "2024-10-15"
target_app = "cli"

[[announcements]]
content = "This is a test announcement"
version_regex = "^0\\.0\\.0$"
to_date = "2026-01-10"
24 changes: 12 additions & 12 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion codex-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ landlock = "0.4.4"
lazy_static = "1"
libc = "0.2.177"
log = "0.4"
lru = "0.16.2"
lru = "0.16.3"
maplit = "1.0.2"
mime_guess = "2.0.5"
multimap = "0.10.0"
Expand Down
4 changes: 4 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ client_request_definitions! {
params: v2::ThreadArchiveParams,
response: v2::ThreadArchiveResponse,
},
ThreadRollback => "thread/rollback" {
params: v2::ThreadRollbackParams,
response: v2::ThreadRollbackResponse,
},
ThreadList => "thread/list" {
params: v2::ThreadListParams,
response: v2::ThreadListResponse,
Expand Down
111 changes: 111 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/thread_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::protocol::v2::UserInput;
use codex_protocol::protocol::AgentReasoningEvent;
use codex_protocol::protocol::AgentReasoningRawContentEvent;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ThreadRolledBackEvent;
use codex_protocol::protocol::TurnAbortedEvent;
use codex_protocol::protocol::UserMessageEvent;

Expand Down Expand Up @@ -57,6 +58,7 @@ impl ThreadHistoryBuilder {
EventMsg::TokenCount(_) => {}
EventMsg::EnteredReviewMode(_) => {}
EventMsg::ExitedReviewMode(_) => {}
EventMsg::ThreadRolledBack(payload) => self.handle_thread_rollback(payload),
EventMsg::UndoCompleted(_) => {}
EventMsg::TurnAborted(payload) => self.handle_turn_aborted(payload),
_ => {}
Expand Down Expand Up @@ -130,6 +132,23 @@ impl ThreadHistoryBuilder {
turn.status = TurnStatus::Interrupted;
}

fn handle_thread_rollback(&mut self, payload: &ThreadRolledBackEvent) {
self.finish_current_turn();

let n = usize::try_from(payload.num_turns).unwrap_or(usize::MAX);
if n >= self.turns.len() {
self.turns.clear();
} else {
self.turns.truncate(self.turns.len().saturating_sub(n));
}

// Re-number subsequent synthetic ids so the pruned history is consistent.
self.next_turn_index =
i64::try_from(self.turns.len().saturating_add(1)).unwrap_or(i64::MAX);
let item_count: usize = self.turns.iter().map(|t| t.items.len()).sum();
self.next_item_index = i64::try_from(item_count.saturating_add(1)).unwrap_or(i64::MAX);
}

fn finish_current_turn(&mut self) {
if let Some(turn) = self.current_turn.take() {
if turn.items.is_empty() {
Expand Down Expand Up @@ -213,6 +232,7 @@ mod tests {
use codex_protocol::protocol::AgentMessageEvent;
use codex_protocol::protocol::AgentReasoningEvent;
use codex_protocol::protocol::AgentReasoningRawContentEvent;
use codex_protocol::protocol::ThreadRolledBackEvent;
use codex_protocol::protocol::TurnAbortReason;
use codex_protocol::protocol::TurnAbortedEvent;
use codex_protocol::protocol::UserMessageEvent;
Expand Down Expand Up @@ -410,4 +430,95 @@ mod tests {
}
);
}

#[test]
fn drops_last_turns_on_thread_rollback() {
let events = vec![
EventMsg::UserMessage(UserMessageEvent {
message: "First".into(),
images: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "A1".into(),
}),
EventMsg::UserMessage(UserMessageEvent {
message: "Second".into(),
images: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "A2".into(),
}),
EventMsg::ThreadRolledBack(ThreadRolledBackEvent { num_turns: 1 }),
EventMsg::UserMessage(UserMessageEvent {
message: "Third".into(),
images: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "A3".into(),
}),
];

let turns = build_turns_from_event_msgs(&events);
let expected = vec![
Turn {
id: "turn-1".into(),
status: TurnStatus::Completed,
error: None,
items: vec![
ThreadItem::UserMessage {
id: "item-1".into(),
content: vec![UserInput::Text {
text: "First".into(),
}],
},
ThreadItem::AgentMessage {
id: "item-2".into(),
text: "A1".into(),
},
],
},
Turn {
id: "turn-2".into(),
status: TurnStatus::Completed,
error: None,
items: vec![
ThreadItem::UserMessage {
id: "item-3".into(),
content: vec![UserInput::Text {
text: "Third".into(),
}],
},
ThreadItem::AgentMessage {
id: "item-4".into(),
text: "A3".into(),
},
],
},
];
assert_eq!(turns, expected);
}

#[test]
fn thread_rollback_clears_all_turns_when_num_turns_exceeds_history() {
let events = vec![
EventMsg::UserMessage(UserMessageEvent {
message: "One".into(),
images: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "A1".into(),
}),
EventMsg::UserMessage(UserMessageEvent {
message: "Two".into(),
images: None,
}),
EventMsg::AgentMessage(AgentMessageEvent {
message: "A2".into(),
}),
EventMsg::ThreadRolledBack(ThreadRolledBackEvent { num_turns: 99 }),
];

let turns = build_turns_from_event_msgs(&events);
assert_eq!(turns, Vec::<Turn>::new());
}
}
Loading
Loading