notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit d9c46bc912d7f7d9953a846bc243f53046055a34
parent 0094611f73eb8d61088e08a35eb9aa63fd451da0
Author: William Casarin <jb55@jb55.com>
Date:   Tue, 24 Feb 2026 16:02:30 -0800

wip codex debugging

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mcrates/notedeck_dave/src/backend/codex.rs | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mcrates/notedeck_dave/src/backend/codex_protocol.rs | 45+++++++++++++++++++++++++++++++++++++++++----
2 files changed, 137 insertions(+), 45 deletions(-)

diff --git a/crates/notedeck_dave/src/backend/codex.rs b/crates/notedeck_dave/src/backend/codex.rs @@ -473,11 +473,18 @@ fn handle_codex_message( let method = match &msg.method { Some(m) => m.as_str(), None => { - // Response to a request we sent (e.g. approval ack). Nothing to do. + tracing::debug!("codex msg with no method (response): id={:?}", msg.id); return HandleResult::Continue; } }; + tracing::debug!( + "codex msg: method={} id={:?} has_params={}", + method, + msg.id, + msg.params.is_some() + ); + match method { "item/agentMessage/delta" => { if let Some(params) = msg.params { @@ -521,54 +528,94 @@ fn handle_codex_message( } "item/commandExecution/requestApproval" => { + tracing::info!( + "CMD APPROVAL: id={:?} has_params={}", + msg.id, + msg.params.is_some() + ); if let (Some(rpc_id), Some(params)) = (msg.id, msg.params) { - if let Ok(approval) = serde_json::from_value::<CommandApprovalParams>(params) { - return check_approval_or_forward( - rpc_id, - "Bash", - serde_json::json!({ "command": approval.command }), - response_tx, - ctx, - ); + tracing::info!( + "CMD APPROVAL params: {}", + serde_json::to_string(&params).unwrap_or_default() + ); + match serde_json::from_value::<CommandApprovalParams>(params) { + Ok(approval) => { + let cmd = approval.command_string(); + tracing::info!("CMD APPROVAL deserialized ok: command={}", cmd); + return check_approval_or_forward( + rpc_id, + "Bash", + serde_json::json!({ "command": cmd }), + response_tx, + ctx, + ); + } + Err(e) => { + tracing::error!("CMD APPROVAL deser FAILED: {}", e); + } } + } else { + tracing::warn!("CMD APPROVAL missing id or params"); } } "item/fileChange/requestApproval" => { + tracing::info!( + "FILE APPROVAL: id={:?} has_params={}", + msg.id, + msg.params.is_some() + ); if let (Some(rpc_id), Some(params)) = (msg.id, msg.params) { - if let Ok(approval) = serde_json::from_value::<FileChangeApprovalParams>(params) { - let kind_str = approval - .kind - .as_ref() - .and_then(|k| k.get("type").and_then(|t| t.as_str())) - .unwrap_or("edit"); - - let (tool_name, tool_input) = match kind_str { - "create" => ( - "Write", - serde_json::json!({ - "file_path": approval.file_path, - "content": approval.diff.as_deref().unwrap_or(""), - }), - ), - _ => ( - "Edit", - serde_json::json!({ - "file_path": approval.file_path, - "old_string": "", - "new_string": approval.diff.as_deref().unwrap_or(""), - }), - ), - }; + tracing::info!( + "FILE APPROVAL params: {}", + serde_json::to_string(&params).unwrap_or_default() + ); + match serde_json::from_value::<FileChangeApprovalParams>(params) { + Ok(approval) => { + let file_path = approval.file_path.as_deref().unwrap_or("unknown"); + let kind_str = approval + .kind + .as_ref() + .and_then(|k| k.get("type").and_then(|t| t.as_str())) + .unwrap_or("edit"); + + let (tool_name, tool_input) = match kind_str { + "create" => ( + "Write", + serde_json::json!({ + "file_path": file_path, + "content": approval.diff.as_deref().unwrap_or(""), + }), + ), + _ => ( + "Edit", + serde_json::json!({ + "file_path": file_path, + "old_string": "", + "new_string": approval.diff.as_deref().unwrap_or(""), + }), + ), + }; - return check_approval_or_forward( - rpc_id, - tool_name, - tool_input, - response_tx, - ctx, - ); + tracing::info!( + "FILE APPROVAL deserialized ok: tool={} file={}", + tool_name, + file_path + ); + return check_approval_or_forward( + rpc_id, + tool_name, + tool_input, + response_tx, + ctx, + ); + } + Err(e) => { + tracing::error!("FILE APPROVAL deser FAILED: {}", e); + } } + } else { + tracing::warn!("FILE APPROVAL missing id or params"); } } @@ -592,7 +639,15 @@ fn handle_codex_message( } other => { - tracing::debug!("Unhandled codex notification: {}", other); + tracing::debug!( + "Unhandled codex notification: {} id={:?} params={}", + other, + msg.id, + msg.params + .as_ref() + .map(|p| serde_json::to_string(p).unwrap_or_default()) + .unwrap_or_default() + ); } } diff --git a/crates/notedeck_dave/src/backend/codex_protocol.rs b/crates/notedeck_dave/src/backend/codex_protocol.rs @@ -191,22 +191,59 @@ pub struct ItemCompletedParams { pub content: Option<String>, } -/// `item/commandExecution/requestApproval` params — server asks client to approve a command +/// `item/commandExecution/requestApproval` params — server asks client to approve a command. +/// +/// In V2, `command` and `cwd` are optional. When absent the command can be +/// reconstructed from `proposed_execpolicy_amendment` (an argv vec). #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CommandApprovalParams { - pub command: String, + /// The command string (present in V2 when the Command presentation is used). + #[serde(default)] + pub command: Option<String>, #[serde(default)] pub cwd: Option<String>, + /// Reason the approval is needed (e.g. "Write outside workspace"). + #[serde(default)] + pub reason: Option<String>, + /// Proposed execpolicy amendment — an argv array that can serve as a + /// fallback when `command` is absent. + #[serde(default)] + pub proposed_execpolicy_amendment: Option<Vec<String>>, +} + +impl CommandApprovalParams { + /// Best-effort command string for display / auto-accept evaluation. + pub fn command_string(&self) -> String { + if let Some(ref cmd) = self.command { + return cmd.clone(); + } + if let Some(ref argv) = self.proposed_execpolicy_amendment { + return argv.join(" "); + } + "unknown command".to_string() + } } -/// `item/fileChange/requestApproval` params — server asks client to approve a file change +/// `item/fileChange/requestApproval` params — server asks client to approve a file change. +/// +/// In V2, the params are minimal (just item/thread/turn ids + reason). +/// `file_path`, `diff`, and `kind` may be absent. #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FileChangeApprovalParams { - pub file_path: String, + #[serde(default)] + pub file_path: Option<String>, + #[serde(default)] pub diff: Option<String>, + #[serde(default)] pub kind: Option<Value>, + /// Reason the approval is needed. + #[serde(default)] + pub reason: Option<String>, + /// Root directory the agent is requesting write access for. + #[serde(default)] + pub grant_root: Option<String>, } /// `turn/completed` params