diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 9fc2a9914..76ba36735 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -507,6 +507,15 @@ "classification": { "type": "string" }, + "extraLogFiles": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "includeLogs": { "type": "boolean" }, 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 7a01b04b5..313c3e62b 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 @@ -12549,6 +12549,15 @@ "classification": { "type": "string" }, + "extraLogFiles": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "includeLogs": { "type": "boolean" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/FeedbackUploadParams.json b/codex-rs/app-server-protocol/schema/json/v2/FeedbackUploadParams.json index e4171b27f..47b752b86 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/FeedbackUploadParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/FeedbackUploadParams.json @@ -4,6 +4,15 @@ "classification": { "type": "string" }, + "extraLogFiles": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "includeLogs": { "type": "boolean" }, diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadParams.ts index 3066e6540..ebbe3b32d 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/FeedbackUploadParams.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type FeedbackUploadParams = { classification: string, reason?: string | null, threadId?: string | null, includeLogs: boolean, }; +export type FeedbackUploadParams = { classification: string, reason?: string | null, threadId?: string | null, includeLogs: boolean, extraLogFiles?: Array | null, }; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 103b5f56a..833e62821 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -1506,6 +1506,8 @@ pub struct FeedbackUploadParams { #[ts(optional = nullable)] pub thread_id: Option, pub include_logs: bool, + #[ts(optional = nullable)] + pub extra_log_files: Option>, } #[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 95ab4e43e..8ee15688c 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -150,7 +150,7 @@ Example with notification opt-out: - `config/mcpServer/reload` — reload MCP server config from disk and queue a refresh for loaded threads (applied on each thread's next active turn); returns `{}`. Use this after editing `config.toml` without restarting the server. - `mcpServerStatus/list` — enumerate configured MCP servers with their tools, resources, resource templates, and auth status; supports cursor+limit pagination. - `windowsSandbox/setupStart` — start Windows sandbox setup for the selected mode (`elevated` or `unelevated`); returns `{ started: true }` immediately and later emits `windowsSandbox/setupCompleted`. -- `feedback/upload` — submit a feedback report (classification + optional reason/logs and conversation_id); returns the tracking thread id. +- `feedback/upload` — submit a feedback report (classification + optional reason/logs, conversation_id, and optional `extraLogFiles` attachments array); returns the tracking thread id. - `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation). - `config/read` — fetch the effective config on disk after resolving config layering. - `config/value/write` — write a single config key/value to the user's config.toml on disk. diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 29d8acdbc..65b7f1ecb 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -6098,6 +6098,7 @@ impl CodexMessageProcessor { reason, thread_id, include_logs, + extra_log_files, } = params; let conversation_id = match thread_id.as_deref() { @@ -6127,15 +6128,19 @@ impl CodexMessageProcessor { } else { None }; + let mut attachment_paths = validated_rollout_path.into_iter().collect::>(); + if let Some(extra_log_files) = extra_log_files { + attachment_paths.extend(extra_log_files); + } + let session_source = self.thread_manager.session_source(); let upload_result = tokio::task::spawn_blocking(move || { - let rollout_path_ref = validated_rollout_path.as_deref(); snapshot.upload_feedback( &classification, reason.as_deref(), include_logs, - rollout_path_ref, + &attachment_paths, Some(session_source), ) }) diff --git a/codex-rs/feedback/src/lib.rs b/codex-rs/feedback/src/lib.rs index adda7173a..a4fe6cc18 100644 --- a/codex-rs/feedback/src/lib.rs +++ b/codex-rs/feedback/src/lib.rs @@ -224,7 +224,7 @@ impl CodexLogSnapshot { classification: &str, reason: Option<&str>, include_logs: bool, - rollout_path: Option<&std::path::Path>, + extra_log_files: &[PathBuf], session_source: Option, ) -> Result<()> { use std::collections::BTreeMap; @@ -317,11 +317,22 @@ impl CodexLogSnapshot { })); } - if let Some((path, data)) = rollout_path.and_then(|p| fs::read(p).ok().map(|d| (p, d))) { + for path in extra_log_files { + let data = match fs::read(path) { + Ok(data) => data, + Err(err) => { + tracing::warn!( + path = %path.display(), + error = %err, + "failed to read log attachment; skipping" + ); + continue; + } + }; let fname = path .file_name() .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_else(|| "rollout.jsonl".to_string()); + .unwrap_or_else(|| "extra-log.log".to_string()); let content_type = "text/plain".to_string(); envelope.add_item(EnvelopeItem::Attachment(Attachment { buffer: data, diff --git a/codex-rs/tui/src/bottom_pane/feedback_view.rs b/codex-rs/tui/src/bottom_pane/feedback_view.rs index 6cbfbdb9e..eeca6aef1 100644 --- a/codex-rs/tui/src/bottom_pane/feedback_view.rs +++ b/codex-rs/tui/src/bottom_pane/feedback_view.rs @@ -87,7 +87,11 @@ impl FeedbackNoteView { } else { Some(note.as_str()) }; - let rollout_path_ref = self.rollout_path.as_deref(); + let log_file_paths = if self.include_logs { + self.rollout_path.iter().cloned().collect::>() + } else { + Vec::new() + }; let classification = feedback_classification(self.category); let mut thread_id = self.snapshot.thread_id.clone(); @@ -96,11 +100,7 @@ impl FeedbackNoteView { classification, reason_opt, self.include_logs, - if self.include_logs { - rollout_path_ref - } else { - None - }, + &log_file_paths, Some(SessionSource::Cli), );