diff --git a/codex-rs/core/src/agent/control/sender_context.rs b/codex-rs/core/src/agent/control/sender_context.rs
index ded0de1cf4..303840f78a 100644
--- a/codex-rs/core/src/agent/control/sender_context.rs
+++ b/codex-rs/core/src/agent/control/sender_context.rs
@@ -1,9 +1,11 @@
-//! Captures genuine sender instructions for host-delivered task messages.
+//! Captures genuine sender instructions and assistant context for host-delivered task messages.
//! Only turn-input admission calls this, before queueing; ordinary tool results and
//! quoted delegation text cannot establish sender provenance. Lookup stays in this host.
use crate::context::ContextualUserFragment;
+use crate::context::GuardianSenderExchange;
use crate::context::GuardianSenderMessages;
+use codex_guardian_context::GuardianRootMessage;
use codex_history::RetainedContextEntry;
use codex_history::RetainedContextOrder;
use codex_history::SenderUserMessages;
@@ -30,17 +32,25 @@ impl LocalAgentRuntime {
else {
return None;
};
- if !matches!(namespace.as_str(), "codex_app" | "codex_tui")
- || name != "send_message_to_thread"
- {
+ if !matches!(
+ (namespace.as_str(), name.as_str()),
+ ("codex_app" | "codex_tui", "send_message_to_thread")
+ | ("cloud_threads", "send_message")
+ ) {
return None;
}
// Recognized deliveries always get their own snapshot, even without usable provenance.
let source_thread_id = output.body.to_text().and_then(|text| {
let (source, input) = text
- .strip_prefix("\n ")?
- .split_once("\n ")?;
- input.strip_suffix("\n")?;
+ .strip_prefix("")?
+ .strip_suffix("")?
+ .trim()
+ .strip_prefix("")?
+ .split_once("")?;
+ input
+ .trim()
+ .strip_prefix("")?
+ .strip_suffix("")?;
ThreadId::from_string(source)
.ok()
.filter(|source| *source != receiver_thread_id)
@@ -56,19 +66,36 @@ impl LocalAgentRuntime {
{
let history = sender.conversation_history_snapshot().await;
if let Some(context) = history.retained_context() {
- fragment.messages = context
- .ordered_entries()
- .filter_map(|(order, entry)| match (order, entry) {
+ let mut exchanges = Vec::new();
+ let mut assistant = None;
+ for (order, entry) in context.ordered_entries() {
+ match (order, entry) {
(
RetainedContextOrder::Local(_),
RetainedContextEntry::UserMessage(message),
- ) => Some(message.complete.then(|| message.text.clone())),
+ ) => exchanges.push((message, assistant.take())),
+ (
+ RetainedContextOrder::Local(_),
+ RetainedContextEntry::AssistantMessage(message),
+ ) => assistant = Some(message),
(RetainedContextOrder::Inherited(_), _)
- | (_, RetainedContextEntry::AssistantMessage(_))
- | (_, RetainedContextEntry::VerifiedAnswer(_)) => None,
- })
+ | (_, RetainedContextEntry::VerifiedAnswer(_)) => {}
+ }
+ }
+ fragment.messages = exchanges
+ .into_iter()
.rev()
.take(/*n*/ 3)
+ .map(|(user, assistant)| GuardianSenderExchange {
+ user: user.complete.then(|| user.text.clone()),
+ assistant: assistant.map(|message| {
+ if message.complete {
+ GuardianRootMessage::Assistant(message.text.clone())
+ } else {
+ GuardianRootMessage::IncompleteAssistantContext
+ }
+ }),
+ })
.collect();
fragment.messages.reverse();
}
diff --git a/codex-rs/core/src/context/guardian_sender_messages.rs b/codex-rs/core/src/context/guardian_sender_messages.rs
index 620c31b36e..b1bc4ece09 100644
--- a/codex-rs/core/src/context/guardian_sender_messages.rs
+++ b/codex-rs/core/src/context/guardian_sender_messages.rs
@@ -1,4 +1,5 @@
-//! Reviewer-only snapshot of original user instructions for one accepted delegation.
+//! Reviewer-only snapshot of sender instructions and preceding assistant context.
+//! Each exchange shares the existing user-message budget; user evidence takes priority.
use super::ContextualUserFragment;
use codex_guardian_context::GuardianRootMessage;
@@ -9,7 +10,13 @@ use codex_protocol::models::ContentItemKind;
pub(crate) struct GuardianSenderMessages {
pub source: Option,
pub delivery: String,
- pub messages: Vec