diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 89831b3feb..c23252f43c 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -5208,6 +5208,15 @@ "description": "Concise warning message for the user.", "type": "string" }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/WarningSource" + } + ], + "default": "runtime", + "description": "Lifecycle source that produced the warning." + }, "threadId": { "description": "Optional thread target when the warning applies to a specific thread.", "type": [ @@ -5221,6 +5230,31 @@ ], "type": "object" }, + "WarningSource": { + "oneOf": [ + { + "description": "Warning without a more specific startup origin.", + "enum": [ + "runtime" + ], + "type": "string" + }, + { + "description": "Warning loaded with configuration before constructing the thread.", + "enum": [ + "config" + ], + "type": "string" + }, + { + "description": "Warning discovered while constructing a thread before its first turn.", + "enum": [ + "threadStartup" + ], + "type": "string" + } + ] + }, "WebSearchAction": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index ba23f67a66..4a67a69b1e 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -19354,6 +19354,15 @@ "description": "Concise warning message for the user.", "type": "string" }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/v2/WarningSource" + } + ], + "default": "runtime", + "description": "Lifecycle source that produced the warning." + }, "threadId": { "description": "Optional thread target when the warning applies to a specific thread.", "type": [ @@ -19368,6 +19377,31 @@ "title": "WarningNotification", "type": "object" }, + "WarningSource": { + "oneOf": [ + { + "description": "Warning without a more specific startup origin.", + "enum": [ + "runtime" + ], + "type": "string" + }, + { + "description": "Warning loaded with configuration before constructing the thread.", + "enum": [ + "config" + ], + "type": "string" + }, + { + "description": "Warning discovered while constructing a thread before its first turn.", + "enum": [ + "threadStartup" + ], + "type": "string" + } + ] + }, "WebSearchAction": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 2ff8c8b558..47557cc7f5 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -17171,6 +17171,15 @@ "description": "Concise warning message for the user.", "type": "string" }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/WarningSource" + } + ], + "default": "runtime", + "description": "Lifecycle source that produced the warning." + }, "threadId": { "description": "Optional thread target when the warning applies to a specific thread.", "type": [ @@ -17185,6 +17194,31 @@ "title": "WarningNotification", "type": "object" }, + "WarningSource": { + "oneOf": [ + { + "description": "Warning without a more specific startup origin.", + "enum": [ + "runtime" + ], + "type": "string" + }, + { + "description": "Warning loaded with configuration before constructing the thread.", + "enum": [ + "config" + ], + "type": "string" + }, + { + "description": "Warning discovered while constructing a thread before its first turn.", + "enum": [ + "threadStartup" + ], + "type": "string" + } + ] + }, "WebSearchAction": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/WarningNotification.json b/codex-rs/app-server-protocol/schema/json/v2/WarningNotification.json index 460486896d..e42a7b7612 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/WarningNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/WarningNotification.json @@ -1,10 +1,46 @@ { "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "WarningSource": { + "oneOf": [ + { + "description": "Warning without a more specific startup origin.", + "enum": [ + "runtime" + ], + "type": "string" + }, + { + "description": "Warning loaded with configuration before constructing the thread.", + "enum": [ + "config" + ], + "type": "string" + }, + { + "description": "Warning discovered while constructing a thread before its first turn.", + "enum": [ + "threadStartup" + ], + "type": "string" + } + ] + } + }, "properties": { "message": { "description": "Concise warning message for the user.", "type": "string" }, + "source": { + "allOf": [ + { + "$ref": "#/definitions/WarningSource" + } + ], + "default": "runtime", + "description": "Lifecycle source that produced the warning." + }, "threadId": { "description": "Optional thread target when the warning applies to a specific thread.", "type": [ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/WarningNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/WarningNotification.ts index bd3433be41..d34f890c64 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/WarningNotification.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/WarningNotification.ts @@ -1,6 +1,7 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { WarningSource } from "./WarningSource"; export type WarningNotification = { /** @@ -10,4 +11,8 @@ threadId: string | null, /** * Concise warning message for the user. */ -message: string, }; +message: string, +/** + * Lifecycle source that produced the warning. + */ +source: WarningSource, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/WarningSource.ts b/codex-rs/app-server-protocol/schema/typescript/v2/WarningSource.ts new file mode 100644 index 0000000000..ae8364b530 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/WarningSource.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type WarningSource = "runtime" | "config" | "threadStartup"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index b4cd88aadf..744ded8e8a 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -469,6 +469,7 @@ export type { TurnSteerResponse } from "./TurnSteerResponse"; export type { TurnsPage } from "./TurnsPage"; export type { UserInput } from "./UserInput"; export type { WarningNotification } from "./WarningNotification"; +export type { WarningSource } from "./WarningSource"; export type { WebSearchAction } from "./WebSearchAction"; export type { WindowsSandboxReadiness } from "./WindowsSandboxReadiness"; export type { WindowsSandboxReadinessResponse } from "./WindowsSandboxReadinessResponse"; diff --git a/codex-rs/app-server-protocol/src/protocol/v2/notification.rs b/codex-rs/app-server-protocol/src/protocol/v2/notification.rs index 8289cf5683..7de0e15c1f 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/notification.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/notification.rs @@ -23,6 +23,22 @@ pub struct WarningNotification { pub thread_id: Option, /// Concise warning message for the user. pub message: String, + /// Lifecycle source that produced the warning. + #[serde(default)] + pub source: WarningSource, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(rename_all = "camelCase", export_to = "v2/")] +pub enum WarningSource { + /// Warning without a more specific startup origin. + #[default] + Runtime, + /// Warning loaded with configuration before constructing the thread. + Config, + /// Warning discovered while constructing a thread before its first turn. + ThreadStartup, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 42ac510a8a..84d0e0a5c2 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -1243,7 +1243,7 @@ Thread realtime uses a separate thread-scoped notification surface. `thread/real Recoverable configuration and initialization warnings use the existing `configWarning` notification: `{ summary, details?, path?, range? }`. App-server may emit it during initialization for config parsing and related setup diagnostics. -Generic runtime warnings use the `warning` notification: `{ threadId?, message }`. App-server emits this for non-fatal warnings from the core event stream, including cases where not all enabled skills are included in the model-visible skills list for a session. +Thread-scoped warnings use the `warning` notification: `{ threadId?, message, source }`. The `source` is `config` for warnings preloaded with configuration, `threadStartup` for warnings discovered while constructing a thread, such as unreadable project instruction files, and `runtime` for warnings without a more specific startup origin, including cases where not all enabled skills are included in the model-visible skills list for a session. ### Notification opt-out diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index 1f47bc4dd8..52df183a0b 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -81,6 +81,7 @@ use codex_app_server_protocol::TurnPlanUpdatedNotification; use codex_app_server_protocol::TurnStartedNotification; use codex_app_server_protocol::TurnStatus; use codex_app_server_protocol::WarningNotification; +use codex_app_server_protocol::WarningSource; use codex_app_server_protocol::build_item_from_guardian_event; use codex_app_server_protocol::guardian_auto_approval_review_notification; use codex_app_server_protocol::item_event_to_server_notification; @@ -226,6 +227,27 @@ pub(crate) async fn apply_bespoke_event_handling( let notification = WarningNotification { thread_id: Some(conversation_id.to_string()), message: warning_event.message, + source: WarningSource::Runtime, + }; + outgoing + .send_server_notification(ServerNotification::Warning(notification)) + .await; + } + EventMsg::ConfigWarning(warning_event) => { + let notification = WarningNotification { + thread_id: Some(conversation_id.to_string()), + message: warning_event.message, + source: WarningSource::Config, + }; + outgoing + .send_server_notification(ServerNotification::Warning(notification)) + .await; + } + EventMsg::ThreadStartupWarning(warning_event) => { + let notification = WarningNotification { + thread_id: Some(conversation_id.to_string()), + message: warning_event.message, + source: WarningSource::ThreadStartup, }; outgoing .send_server_notification(ServerNotification::Warning(notification)) diff --git a/codex-rs/app-server/tests/suite/v2/thread_start.rs b/codex-rs/app-server/tests/suite/v2/thread_start.rs index 6366c6fbca..675e998029 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_start.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_start.rs @@ -21,6 +21,8 @@ use codex_app_server_protocol::ThreadStartedNotification; use codex_app_server_protocol::ThreadStatus; use codex_app_server_protocol::ThreadStatusChangedNotification; use codex_app_server_protocol::TurnEnvironmentParams; +use codex_app_server_protocol::WarningNotification; +use codex_app_server_protocol::WarningSource; use codex_config::loader::project_trust_key; use codex_config::types::AuthCredentialsStoreMode; use codex_core::config::set_project_trust_level; @@ -399,6 +401,103 @@ async fn thread_start_response_excludes_empty_project_instruction_source() -> Re Ok(()) } +#[tokio::test] +async fn thread_start_warning_identifies_thread_startup_source() -> Result<()> { + let server = create_mock_responses_server_repeating_assistant("Done").await; + let codex_home = TempDir::new()?; + create_config_toml_without_approval_policy(codex_home.path(), &server.uri())?; + let workspace = TempDir::new()?; + let project_agents_path = workspace.path().join("AGENTS.md"); + std::fs::write(&project_agents_path, b"project\xFFinstructions")?; + + let mut mcp = TestAppServer::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp + .send_thread_start_request(ThreadStartParams { + cwd: Some(workspace.path().display().to_string()), + ..Default::default() + }) + .await?; + let response: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(request_id)), + ) + .await??; + let ThreadStartResponse { thread, .. } = to_response::(response)?; + + let notification = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("warning"), + ) + .await??; + let warning: WarningNotification = + serde_json::from_value(notification.params.expect("thread startup warning params"))?; + + assert_eq!( + warning, + WarningNotification { + thread_id: Some(thread.id), + message: format!( + "Project AGENTS.md instructions from `{}` contain invalid UTF-8: invalid utf-8 sequence of 1 bytes from index 7. Invalid byte sequences were replaced.", + project_agents_path.display() + ), + source: WarningSource::ThreadStartup, + } + ); + + Ok(()) +} + +#[tokio::test] +async fn thread_start_warning_identifies_config_source() -> Result<()> { + let server = create_mock_responses_server_repeating_assistant("Done").await; + let codex_home = TempDir::new()?; + create_config_toml_without_approval_policy(codex_home.path(), &server.uri())?; + let global_agents_path = codex_home.path().join("AGENTS.md"); + std::fs::write(&global_agents_path, b"global\xFFinstructions")?; + let global_agents_path = global_agents_path.canonicalize()?; + let workspace = TempDir::new()?; + + let mut mcp = TestAppServer::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp + .send_thread_start_request(ThreadStartParams { + cwd: Some(workspace.path().display().to_string()), + ..Default::default() + }) + .await?; + let response: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(request_id)), + ) + .await??; + let ThreadStartResponse { thread, .. } = to_response::(response)?; + + let notification = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("warning"), + ) + .await??; + let warning: WarningNotification = + serde_json::from_value(notification.params.expect("configuration warning params"))?; + + assert_eq!( + warning, + WarningNotification { + thread_id: Some(thread.id), + message: format!( + "Global AGENTS.md instructions from `{}` contain invalid UTF-8: invalid utf-8 sequence of 1 bytes from index 6. Invalid byte sequences were replaced.", + global_agents_path.display() + ), + source: WarningSource::Config, + } + ); + + Ok(()) +} + #[tokio::test] async fn thread_start_without_selected_environment_excludes_instruction_sources() -> Result<()> { let server = create_mock_responses_server_repeating_assistant("Done").await; diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 7fc0d8c71d..6b5072ea03 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -62,6 +62,7 @@ use codex_app_server_protocol::TurnStatus; use codex_app_server_protocol::TurnSteerParams; use codex_app_server_protocol::UserInput as V2UserInput; use codex_app_server_protocol::WarningNotification; +use codex_app_server_protocol::WarningSource; use codex_config::config_toml::ConfigToml; use codex_core::personality_migration::PERSONALITY_MIGRATION_FILENAME; use codex_core::test_support::all_model_presets; @@ -643,6 +644,7 @@ async fn turn_start_emits_thread_scoped_warning_notification_for_trimmed_skills( let warning: WarningNotification = serde_json::from_value(params).expect("deserialize warning notification"); assert_eq!(warning.thread_id.as_deref(), Some(thread.id.as_str())); + assert_eq!(warning.source, WarningSource::Runtime); assert_eq!( warning.message, "Exceeded skills context budget of 2%. All skill descriptions were removed and 7 additional skills were not included in the model-visible skills list." diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 8424ce216f..c80fa9b6fc 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -476,7 +476,7 @@ impl Codex { async fn spawn_internal(args: CodexSpawnArgs) -> CodexResult { let CodexSpawnArgs { - mut config, + config, installation_id, auth_manager, models_manager, @@ -517,7 +517,6 @@ impl Codex { } else { None }; - config.startup_warnings.extend(user_instruction_warnings); let exec_policy = if crate::guardian::is_guardian_reviewer_source(&session_source) { // Guardian review should rely on the built-in shell safety checks, @@ -658,6 +657,14 @@ impl Codex { error!("Failed to create session: {e:#}"); map_session_init_error(&e, &config.codex_home) })?; + for message in user_instruction_warnings { + session + .send_event_raw(Event { + id: INITIAL_SUBMIT_ID.to_owned(), + msg: EventMsg::ThreadStartupWarning(WarningEvent { message }), + }) + .await; + } let thread_id = session.thread_id; // This task will run until Op::Shutdown is received. diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index e42cf202e8..1e94fd5b16 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -711,7 +711,7 @@ impl Session { for message in &config.startup_warnings { post_session_configured_events.push(Event { id: "".to_owned(), - msg: EventMsg::Warning(WarningEvent { + msg: EventMsg::ConfigWarning(WarningEvent { message: message.clone(), }), }); @@ -732,7 +732,7 @@ impl Session { if config.permissions.approval_policy.value() == AskForApproval::OnFailure { post_session_configured_events.push(Event { id: "".to_owned(), - msg: EventMsg::Warning(WarningEvent { + msg: EventMsg::ThreadStartupWarning(WarningEvent { message: "`on-failure` approval policy is deprecated and will be removed in a future release. Use `on-request` for interactive approvals or `never` for non-interactive runs.".to_string(), }), }); @@ -922,7 +922,7 @@ impl Session { for warning in hooks.startup_warnings() { post_session_configured_events.push(Event { id: INITIAL_SUBMIT_ID.to_owned(), - msg: EventMsg::Warning(WarningEvent { + msg: EventMsg::ThreadStartupWarning(WarningEvent { message: warning.clone(), }), }); diff --git a/codex-rs/core/src/session/turn.rs b/codex-rs/core/src/session/turn.rs index f65ada6112..2b11b01044 100644 --- a/codex-rs/core/src/session/turn.rs +++ b/codex-rs/core/src/session/turn.rs @@ -1365,6 +1365,8 @@ pub(super) fn realtime_text_for_event(msg: &EventMsg) -> Option { }, EventMsg::Error(_) | EventMsg::Warning(_) + | EventMsg::ConfigWarning(_) + | EventMsg::ThreadStartupWarning(_) | EventMsg::GuardianWarning(_) | EventMsg::RealtimeConversationStarted(_) | EventMsg::RealtimeConversationSdp(_) diff --git a/codex-rs/core/tests/suite/agents_md.rs b/codex-rs/core/tests/suite/agents_md.rs index 88f014f757..f1012365db 100644 --- a/codex-rs/core/tests/suite/agents_md.rs +++ b/codex-rs/core/tests/suite/agents_md.rs @@ -474,7 +474,7 @@ async fn global_loading_warning_surfaces_during_thread_creation() -> Result<()> let mut builder = test_codex().with_home(home); let test = builder.build(&server).await?; let warning = wait_for_event_match(&test.codex, |event| match event { - EventMsg::Warning(warning) + EventMsg::ConfigWarning(warning) if warning .message .contains(source.as_path().display().to_string().as_str()) => @@ -500,6 +500,38 @@ async fn global_loading_warning_surfaces_during_thread_creation() -> Result<()> Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn project_loading_warning_is_attributed_to_thread_startup() -> Result<()> { + let server = responses::start_mock_server().await; + let mut builder = test_codex().with_workspace_setup(|cwd, fs| async move { + fs.write_file( + &cwd.join(GLOBAL_AGENTS_FILENAME), + b"project\xFFinstructions".to_vec(), + /*sandbox*/ None, + ) + .await?; + Ok::<(), anyhow::Error>(()) + }); + let test = builder.build_with_remote_env(&server).await?; + let source = test.config.cwd.join(GLOBAL_AGENTS_FILENAME); + + let warning = wait_for_event_match(&test.codex, |event| match event { + EventMsg::ThreadStartupWarning(warning) + if warning + .message + .contains(source.as_path().display().to_string().as_str()) => + { + Some(warning.message.clone()) + } + _ => None, + }) + .await; + + assert!(warning.contains("invalid UTF-8"), "{warning}"); + + Ok(()) +} + // TODO(anp): Align cold-resume instruction sources with the historical instructions replayed to // the model so the API source list and model-visible context describe the same files. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] diff --git a/codex-rs/core/tests/suite/unstable_features_warning.rs b/codex-rs/core/tests/suite/unstable_features_warning.rs index 2bc75a7c5e..95b266b9e6 100644 --- a/codex-rs/core/tests/suite/unstable_features_warning.rs +++ b/codex-rs/core/tests/suite/unstable_features_warning.rs @@ -51,8 +51,11 @@ async fn emits_warning_when_unstable_features_enabled_via_config() { .await .expect("spawn conversation"); - let warning = wait_for_event(&conversation, |ev| matches!(ev, EventMsg::Warning(_))).await; - let EventMsg::Warning(WarningEvent { message }) = warning else { + let warning = wait_for_event(&conversation, |ev| { + matches!(ev, EventMsg::ThreadStartupWarning(_)) + }) + .await; + let EventMsg::ThreadStartupWarning(WarningEvent { message }) = warning else { panic!("expected warning event"); }; assert!(message.contains("child_agents_md")); @@ -99,7 +102,9 @@ async fn suppresses_warning_when_configured() { let warning = timeout( Duration::from_millis(150), - wait_for_event(&conversation, |ev| matches!(ev, EventMsg::Warning(_))), + wait_for_event(&conversation, |ev| { + matches!(ev, EventMsg::ThreadStartupWarning(_)) + }), ) .await; assert!(warning.is_err()); diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index b30c255165..058cedba68 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -1282,7 +1282,7 @@ pub fn unstable_features_warning_event( ); Some(Event { id: String::new(), - msg: EventMsg::Warning(WarningEvent { message }), + msg: EventMsg::ThreadStartupWarning(WarningEvent { message }), }) } diff --git a/codex-rs/features/src/tests.rs b/codex-rs/features/src/tests.rs index b7d158ca36..b9bb988a2f 100644 --- a/codex-rs/features/src/tests.rs +++ b/codex-rs/features/src/tests.rs @@ -705,7 +705,7 @@ fn unstable_warning_event_only_mentions_enabled_under_development_features() { ) .expect("warning event"); - let EventMsg::Warning(WarningEvent { message }) = warning.msg else { + let EventMsg::ThreadStartupWarning(WarningEvent { message }) = warning.msg else { panic!("expected warning event"); }; assert!(message.contains("child_agents_md")); diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index c4ec218dc9..6fc1e91727 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -263,6 +263,8 @@ async fn run_codex_tool_session_inner( break; } EventMsg::Warning(_) + | EventMsg::ConfigWarning(_) + | EventMsg::ThreadStartupWarning(_) | EventMsg::GuardianWarning(_) | EventMsg::ModelVerification(_) | EventMsg::TurnModerationMetadata(_) => { diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 01e664c734..c3355fbf77 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -1167,6 +1167,12 @@ pub enum EventMsg { /// indicates the turn continued but the user should still be notified. Warning(WarningEvent), + /// Warning loaded with configuration before constructing the thread. + ConfigWarning(WarningEvent), + + /// Warning discovered while constructing a thread, before its first turn. + ThreadStartupWarning(WarningEvent), + /// Warning issued by the guardian automatic approval reviewer. GuardianWarning(WarningEvent), diff --git a/codex-rs/rollout-trace/src/protocol_event.rs b/codex-rs/rollout-trace/src/protocol_event.rs index bbba245307..5d7701f5da 100644 --- a/codex-rs/rollout-trace/src/protocol_event.rs +++ b/codex-rs/rollout-trace/src/protocol_event.rs @@ -225,6 +225,8 @@ pub(crate) fn tool_runtime_trace_event(event: &EventMsg) -> Option Option<&'static s EventMsg::ThreadRolledBack(_) => Some("thread_rolled_back"), EventMsg::Error(_) => Some("error"), EventMsg::Warning(_) => Some("warning"), + EventMsg::ConfigWarning(_) => Some("config_warning"), + EventMsg::ThreadStartupWarning(_) => Some("thread_startup_warning"), EventMsg::ShutdownComplete => Some("shutdown_complete"), EventMsg::GuardianWarning(_) | EventMsg::RealtimeConversationStarted(_) diff --git a/codex-rs/rollout/src/policy.rs b/codex-rs/rollout/src/policy.rs index 9760191bd4..05d0bf11b5 100644 --- a/codex-rs/rollout/src/policy.rs +++ b/codex-rs/rollout/src/policy.rs @@ -111,6 +111,8 @@ pub fn should_persist_event_msg(ev: &EventMsg) -> bool { | EventMsg::DynamicToolCallRequest(_) | EventMsg::DynamicToolCallResponse(_) | EventMsg::Warning(_) + | EventMsg::ConfigWarning(_) + | EventMsg::ThreadStartupWarning(_) | EventMsg::GuardianWarning(_) | EventMsg::RealtimeConversationStarted(_) | EventMsg::RealtimeConversationSdp(_) diff --git a/codex-rs/tui/src/app/app_server_event_targets.rs b/codex-rs/tui/src/app/app_server_event_targets.rs index fa2bab0111..465e3c090f 100644 --- a/codex-rs/tui/src/app/app_server_event_targets.rs +++ b/codex-rs/tui/src/app/app_server_event_targets.rs @@ -235,6 +235,7 @@ mod tests { let notification = ServerNotification::Warning(WarningNotification { thread_id: None, message: "warning".to_string(), + source: codex_app_server_protocol::WarningSource::Runtime, }); let target = server_notification_thread_target(¬ification); @@ -248,6 +249,7 @@ mod tests { let notification = ServerNotification::Warning(WarningNotification { thread_id: Some(thread_id.to_string()), message: "warning".to_string(), + source: codex_app_server_protocol::WarningSource::Runtime, }); let target = server_notification_thread_target(¬ification); diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index e8ed3e33da..9921cadc33 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -2343,6 +2343,7 @@ async fn replay_snapshot_with_pending_request_suppresses_replay_notices() { WarningNotification { thread_id: Some(thread_id.to_string()), message: stale_warning.to_string(), + source: codex_app_server_protocol::WarningSource::Runtime, }, )), ThreadBufferedEvent::Request(exec_approval_request( diff --git a/codex-rs/tui/src/chatwidget/tests/app_server.rs b/codex-rs/tui/src/chatwidget/tests/app_server.rs index f4fcc85e43..2008744c3a 100644 --- a/codex-rs/tui/src/chatwidget/tests/app_server.rs +++ b/codex-rs/tui/src/chatwidget/tests/app_server.rs @@ -411,6 +411,7 @@ async fn live_app_server_warning_notification_renders_message() { ServerNotification::Warning(WarningNotification { thread_id: None, message: "Exceeded skills context budget of 2%. All skill descriptions were removed and 2 additional skills were not included in the model-visible skills list.".to_string(), + source: codex_app_server_protocol::WarningSource::Runtime, }), /*replay_kind*/ None, ); diff --git a/codex-rs/tui/src/chatwidget/tests/helpers.rs b/codex-rs/tui/src/chatwidget/tests/helpers.rs index 92a903b7de..a96547ad54 100644 --- a/codex-rs/tui/src/chatwidget/tests/helpers.rs +++ b/codex-rs/tui/src/chatwidget/tests/helpers.rs @@ -466,6 +466,7 @@ pub(super) fn handle_warning(chat: &mut ChatWidget, message: impl Into) ServerNotification::Warning(WarningNotification { thread_id: Some(thread_id(chat)), message: message.into(), + source: codex_app_server_protocol::WarningSource::Runtime, }), /*replay_kind*/ None, );