Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions codex-rs/Cargo.lock

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

Binary file not shown.
42 changes: 42 additions & 0 deletions codex-rs/app-server-transport/src/transport/unix_socket_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,48 @@ fn listen_unix_socket_accepts_relative_custom_path() {
);
}

#[cfg(unix)]
#[tokio::test]
async fn long_control_socket_paths_connect_to_distinct_daemons() {
let temp_dir = tempfile::TempDir::new().expect("temp dir");
let long_parent = temp_dir.path().join("x".repeat(120));
let mut sockets = Vec::new();
let mut acceptors = Vec::new();

for name in ["first", "second"] {
let codex_home = long_parent.join(name);
std::fs::create_dir_all(&codex_home).expect("codex home");
let socket_path = app_server_control_socket_path(&codex_home).expect("socket path");
let (transport_event_tx, _transport_event_rx) =
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
let shutdown_token = CancellationToken::new();
let acceptor = start_control_socket_acceptor(
socket_path.clone(),
transport_event_tx,
shutdown_token.clone(),
DaemonShutdownAccess::Disabled,
)
.await
.expect("control socket acceptor should start");
sockets.push(socket_path);
acceptors.push((shutdown_token, acceptor));
}

assert_ne!(
std::fs::read_link(sockets[0].as_path()).expect("first socket target"),
std::fs::read_link(sockets[1].as_path()).expect("second socket target")
);
for socket in &sockets {
connect_to_socket(socket.as_path())
.await
.expect("client should connect through long path");
}
for (shutdown_token, acceptor) in acceptors {
shutdown_token.cancel();
acceptor.await.expect("acceptor should stop");
}
}

#[tokio::test]
async fn control_socket_acceptor_upgrades_and_forwards_websocket_text_messages_and_pings() {
let temp_dir = tempfile::TempDir::new().expect("temp dir");
Expand Down
5 changes: 3 additions & 2 deletions codex-rs/app-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ and caching behavior remain in effect while the provider satisfies current requi
# MCP App UI

`mcpToolCall.mcpAppUi` records the invoked descriptor's `resourceUri`
and `preferredModelDisplayMode` (`inline` or `fullscreen`). Descriptors with a widget
URI default to `inline` when the preference is missing or unsupported. The
and explicit `preferredModelDisplayMode` (`inline` or `fullscreen`). Missing or
unsupported preferences leave `mcpAppUi` unset; `mcpAppResourceUri` retains the URI
so clients can apply resource display defaults. The
UI information is preserved in tool-call events and saved history so clients can
render without waiting for the full MCP catalog.

Expand Down
5 changes: 1 addition & 4 deletions codex-rs/cli/tests/app_server_daemon.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
#![cfg(unix)]

#[path = "support/executable.rs"]
mod executable;

use std::path::PathBuf;
use std::process::Child;
use std::process::Command;
Expand All @@ -13,7 +10,7 @@ use std::time::Instant;
use anyhow::Context;
use anyhow::Result;
use anyhow::ensure;
use executable::copy_executable;
use codex_utils_cargo_bin::copy_executable;
use pretty_assertions::assert_eq;
use serde_json::Value;
use tempfile::TempDir;
Expand Down
5 changes: 1 addition & 4 deletions codex-rs/cli/tests/doctor_path_safety.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
//! Black-box coverage for safe diagnostic execution and config error reporting.

#[path = "support/executable.rs"]
mod executable;

use std::ffi::OsString;
use std::path::PathBuf;
use std::time::Duration;
Expand All @@ -15,7 +12,7 @@ use codex_app_server_protocol::RequestId;
use codex_config::loader::project_trust_key;
use codex_state::SqliteConfig;
use codex_utils_absolute_path::AbsolutePathBuf;
use executable::copy_executable;
use codex_utils_cargo_bin::copy_executable;
use pretty_assertions::assert_eq;
use serde_json::Value;
use serde_json::json;
Expand Down
5 changes: 1 addition & 4 deletions codex-rs/cli/tests/exec_server.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
#[path = "support/executable.rs"]
mod executable;

#[cfg(target_os = "linux")]
#[path = "exec_server/pid_namespace_tests.rs"]
mod pid_namespace_tests;
Expand Down Expand Up @@ -36,7 +33,7 @@ use codex_exec_server::NoiseRendezvousConnectBundle;
use codex_exec_server::ProcessId;
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
use executable::copy_executable;
use codex_utils_cargo_bin::copy_executable;
use futures::SinkExt;
use futures::StreamExt;
use predicates::prelude::PredicateBooleanExt;
Expand Down
39 changes: 0 additions & 39 deletions codex-rs/cli/tests/support/executable.rs

This file was deleted.

18 changes: 10 additions & 8 deletions codex-rs/core/src/agent/control/user_authorization.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Projects bounded retained root evidence for worker reviewers.
//! Retained root instructions stay authoritative while old checkpoints use legacy review.
//! Selects recent user and assistant evidence together, excluding explicit assistant commentary.
//! Recovery preserves phase filtering and confirmed messaging context across compaction.
//! Selects recent user and assistant evidence together by source order.
//! Explicit assistant commentary is excluded; confirmed messaging survives compaction.
//! Projection omissions keep missing authorization and assistant context explicit.
//! Retained-history reconciliation owns recovery order and missing-instruction provenance.
//! Known positions preserve host order, not delivery order or inferred question-answer pairs.
Expand All @@ -19,6 +19,7 @@ use crate::context::ContextualUserFragment;
use crate::context::GuardianReviewEvidence;
use crate::context::UserGoalUpdate;
use crate::context::is_contextual_user_fragment;
use crate::context::render_retained_assistant_context;
use crate::event_mapping::parse_turn_item;
use crate::guardian::GUARDIAN_MAX_ROOT_MESSAGE_TOKENS;
use crate::guardian::guardian_truncate_text;
Expand Down Expand Up @@ -221,10 +222,9 @@ impl LocalAgentControl {
.map(|_| (order, message))
});
if let Some((order, message)) = retained
&& let Some(message) =
codex_guardian_context::retained_assistant_message(message)
&& let Some(text) = render_retained_assistant_context(message)
{
return Some((id, Some(order), message));
return Some((id, Some(order), GuardianRootMessage::Assistant(text)));
}
let order = envelope
.metadata
Expand Down Expand Up @@ -268,7 +268,8 @@ impl LocalAgentControl {
}) {
return None;
}
let rendered = codex_guardian_context::retained_assistant_message(message);
let rendered =
render_retained_assistant_context(message).map(GuardianRootMessage::Assistant);
missing_assistant_context |= rendered.is_none();
rendered.map(|message| (Some(order), message))
})
Expand All @@ -281,8 +282,8 @@ impl LocalAgentControl {
messages.extend(assistant_messages);
// Unknown order cannot establish recency; prune these assistants first.
messages.sort_by_key(|(order, _)| *order);
// Apply one shared cap after ordering eligible messages. User records must
// not consume the entire window before assistant questions are considered.
// Apply one shared cap so user records and confirmed questions compete
// while keeping omissions explicit for authorization and assistant context.
let removed = messages.len().saturating_sub(MAX_ROOT_MESSAGES);
missing_root_instructions |= messages[..removed].iter().any(|(_, message)| {
matches!(
Expand Down Expand Up @@ -342,6 +343,7 @@ impl LocalAgentControl {
root_thread_id,
history_reset_version: root_history.reset_version,
authorization_version,
review_context_revision: history.guardian_review_context_revision(),
messages,
trusted_skill_paths,
})
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/core/src/codex_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ pub struct GuardianRootSnapshot {
/// Distinguishes a history reset from additional authorization in the same history.
pub(crate) history_reset_version: u64,
pub authorization_version: GuardianAuthorizationVersion,
/// Confirmed assistant context is relevant to review but cannot authorize an action.
pub review_context_revision: u64,
pub messages: Vec<GuardianRootMessage>,
pub trusted_skill_paths: Vec<String>,
}
Expand Down
38 changes: 38 additions & 0 deletions codex-rs/core/src/context/guardian_assistant_context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! Marks bounded assistant evidence used to interpret user replies in Guardian reviews.
//! Assistant text remains conversational context and never grants authorization.

use codex_history::RetainedUserMessage;
use codex_protocol::models::ContentItemKind;

use super::ContextualUserFragment;

struct GuardianAssistantContext<'a>(&'a str);

impl ContextualUserFragment for GuardianAssistantContext<'_> {
fn role(&self) -> &'static str {
"assistant"
}

fn content_kind(&self) -> ContentItemKind {
ContentItemKind("guardian.assistant_context".to_owned())
}

fn markers(&self) -> (&'static str, &'static str) {
Self::type_markers()
}

fn type_markers() -> (&'static str, &'static str) {
("", "")
}

fn body(&self) -> String {
self.0.to_owned()
}
}

/// Renders original assistant context through its typed fragment after checking the
/// whole role-labeled message against the retained evidence budget.
pub fn render_retained_assistant_context(message: &RetainedUserMessage) -> Option<String> {
codex_guardian_context::retained_assistant_message(message)?;
Some(GuardianAssistantContext(&message.text).render())
}
6 changes: 6 additions & 0 deletions codex-rs/core/src/context/guardian_review_evidence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,19 @@ impl GuardianReviewEvidence {
assessment: &GuardianAssessmentEvent,
action: &str,
authorization_version: GuardianAuthorizationVersion,
review_context_revision: u64,
root_authorization_version: Option<GuardianAuthorizationVersion>,
root_review_context_revision: Option<u64>,
) {
let Some(completed_at_ms) = assessment.completed_at_ms else {
return;
};
let review = Arc::new(GuardianReviewEvidenceRecord {
completed_at_ms,
authorization_version,
review_context_revision,
root_authorization_version,
root_review_context_revision,
correlation: json!({
"review_id": assessment.id,
"turn_id": assessment.turn_id,
Expand Down Expand Up @@ -167,7 +171,9 @@ impl GuardianReviewEvidence {
#[derive(Debug)]
pub struct GuardianReviewEvidenceRecord {
pub authorization_version: GuardianAuthorizationVersion,
pub review_context_revision: u64,
pub root_authorization_version: Option<GuardianAuthorizationVersion>,
pub root_review_context_revision: Option<u64>,
completed_at_ms: i64,
pub correlation: serde_json::Value,
pub decision: serde_json::Value,
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/core/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod developer_instructions;
mod environment_context;
mod environments_instructions;
mod guardian_approved_action;
mod guardian_assistant_context;
mod guardian_budget_omission;
mod guardian_context_mode;
mod user_goal;
Expand Down Expand Up @@ -76,6 +77,7 @@ pub(crate) use current_time_reminder::CurrentTimeUnavailable;
pub(crate) use developer_instructions::DeveloperInstructions;
pub(crate) use environments_instructions::EnvironmentsInstructions;
pub(crate) use guardian_approved_action::GuardianApprovedAction;
pub use guardian_assistant_context::render_retained_assistant_context;
pub use guardian_context_mode::GuardianContextMode;
pub(crate) use guardian_followup_review_reminder::GuardianFollowupReviewReminder;
pub(crate) use guardian_node_repl_policy::GuardianNodeReplPolicy;
Expand Down
Loading
Loading