Move apply-patch file changes into turn items (#20540)

## Why

Apply-patch file changes are now part of the core turn item stream, so
v2 clients can consume the same first-class item lifecycle path used by
other turn items instead of relying on app-server-specific remapping
from legacy patch events.

## What changed

- Added a core `TurnItem::FileChange` carrying apply-patch changes and
completion metadata.
- Updated the apply-patch tool emitter to send `ItemStarted` /
`ItemCompleted` with the new `FileChange` item while preserving legacy
`PatchApplyBegin` / `PatchApplyEnd` fan-out.
- Updated app-server v2 conversion to render the new core item directly
and stopped `event_mapping` from remapping old patch begin/end events
into item notifications.
- Kept thread history reconstruction based on the existing old
apply-patch events for rollout compatibility.

## Verification

- `cargo test -p codex-protocol -p codex-app-server-protocol`
- `cargo test -p codex-core --test all
apply_patch_tool_executes_and_emits_patch_events`
- `cargo test -p codex-app-server bespoke_event_handling`
This commit is contained in:
pakrym-oai
2026-05-01 08:47:18 -07:00
committed by GitHub
parent 0b04d1b3cc
commit f476338f93
10 changed files with 240 additions and 165 deletions

View File

@@ -4,6 +4,7 @@ use std::fs;
use assert_matches::assert_matches;
use codex_features::Feature;
use codex_protocol::items::TurnItem;
use codex_protocol::models::PermissionProfile;
use codex_protocol::plan_tool::StepStatus;
use codex_protocol::protocol::AskForApproval;
@@ -365,9 +366,30 @@ async fn apply_patch_tool_executes_and_emits_patch_events() -> anyhow::Result<()
})
.await?;
let mut saw_file_change_started = false;
let mut saw_file_change_completed = false;
let mut saw_patch_begin = false;
let mut patch_end_success = None;
wait_for_event(&codex, |event| match event {
EventMsg::ItemStarted(started) => {
if let TurnItem::FileChange(item) = &started.item {
saw_file_change_started = true;
assert_eq!(item.id, call_id);
assert_eq!(item.status, None);
}
false
}
EventMsg::ItemCompleted(completed) => {
if let TurnItem::FileChange(item) = &completed.item {
saw_file_change_completed = true;
assert_eq!(item.id, call_id);
assert_eq!(
item.status,
Some(codex_protocol::protocol::PatchApplyStatus::Completed)
);
}
false
}
EventMsg::PatchApplyBegin(begin) => {
saw_patch_begin = true;
assert_eq!(begin.call_id, call_id);
@@ -383,6 +405,14 @@ async fn apply_patch_tool_executes_and_emits_patch_events() -> anyhow::Result<()
})
.await;
assert!(
saw_file_change_started,
"expected ItemStarted for TurnItem::FileChange"
);
assert!(
saw_file_change_completed,
"expected ItemCompleted for TurnItem::FileChange"
);
assert!(saw_patch_begin, "expected PatchApplyBegin event");
let patch_end_success =
patch_end_success.expect("expected PatchApplyEnd event to capture success flag");