diff --git a/server/packages/agent-management/src/agents.rs b/server/packages/agent-management/src/agents.rs index 1260604..263ee75 100644 --- a/server/packages/agent-management/src/agents.rs +++ b/server/packages/agent-management/src/agents.rs @@ -268,6 +268,9 @@ impl AgentManager { if let Some(variant) = options.variant.as_deref() { command.arg("--variant").arg(variant); } + if options.permission_mode.as_deref() == Some("bypass") { + command.arg("--dangerously-skip-permissions"); + } if let Some(session_id) = options.session_id.as_deref() { command.arg("-s").arg(session_id); } @@ -632,6 +635,9 @@ impl AgentManager { if let Some(variant) = options.variant.as_deref() { command.arg("--variant").arg(variant); } + if options.permission_mode.as_deref() == Some("bypass") { + command.arg("--dangerously-skip-permissions"); + } if let Some(session_id) = options.session_id.as_deref() { command.arg("-s").arg(session_id); } diff --git a/server/packages/sandbox-agent/src/router.rs b/server/packages/sandbox-agent/src/router.rs index 17162f0..babc25e 100644 --- a/server/packages/sandbox-agent/src/router.rs +++ b/server/packages/sandbox-agent/src/router.rs @@ -24,12 +24,12 @@ use reqwest::Client; use sandbox_agent_error::{AgentError, ErrorType, ProblemDetails, SandboxError}; use sandbox_agent_universal_agent_schema::{ codex as codex_schema, convert_amp, convert_claude, convert_codex, convert_opencode, - turn_ended_event, turn_started_event, AgentUnparsedData, ContentPart, ErrorData, - EventConversion, EventSource, FileAction, ItemDeltaData, ItemEventData, ItemKind, ItemRole, - ItemStatus, PermissionEventData, PermissionStatus, QuestionEventData, QuestionStatus, - ReasoningVisibility, SessionEndReason, SessionEndedData, SessionStartedData, StderrOutput, - TerminatedBy, TurnEventData, TurnPhase, UniversalEvent, UniversalEventData, UniversalEventType, - UniversalItem, + opencode as opencode_schema, turn_ended_event, turn_started_event, AgentUnparsedData, + ContentPart, ErrorData, EventConversion, EventSource, FileAction, ItemDeltaData, ItemEventData, + ItemKind, ItemRole, ItemStatus, PermissionEventData, PermissionStatus, QuestionEventData, + QuestionStatus, ReasoningVisibility, SessionEndReason, SessionEndedData, SessionStartedData, + StderrOutput, TerminatedBy, TurnEventData, TurnPhase, UniversalEvent, UniversalEventData, + UniversalEventType, UniversalItem, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -3655,7 +3655,7 @@ impl SessionManager { } }; - let url = format!("{base_url}/event/subscribe"); + let url = format!("{base_url}/event"); let response = match self.http_client.get(url).send().await { Ok(response) => response, Err(err) => { @@ -3746,12 +3746,91 @@ impl SessionManager { if !opencode_event_matches_session(&value, &native_session_id) { continue; } - let conversions = match serde_json::from_value(value.clone()) { - Ok(event) => match convert_opencode::event_to_universal(&event) { - Ok(conversions) => conversions, - Err(err) => vec![agent_unparsed("opencode", &err, value.clone())], - }, - Err(err) => vec![agent_unparsed("opencode", &err.to_string(), value.clone())], + // Manual type-based dispatch to bypass broken #[serde(untagged)] + // enum ordering where ServerConnected (variant #5, empty properties) + // matches all events before MessageUpdated (variant #10) gets tried. + let event_type = value.get("type").and_then(|t| t.as_str()).unwrap_or(""); + let conversions = match event_type { + "message.updated" => { + match serde_json::from_value::(value.clone()) { + Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::MessageUpdated(e)) { + Ok(c) => c, + Err(err) => vec![agent_unparsed("opencode", &err, value.clone())], + }, + Err(err) => vec![agent_unparsed("opencode", &format!("message.updated: {}", err), value.clone())], + } + } + "message.part.updated" => { + match serde_json::from_value::(value.clone()) { + Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::MessagePartUpdated(e)) { + Ok(c) => c, + Err(err) => vec![agent_unparsed("opencode", &err, value.clone())], + }, + Err(err) => vec![agent_unparsed("opencode", &format!("message.part.updated: {}", err), value.clone())], + } + } + "question.asked" => { + match serde_json::from_value::(value.clone()) { + Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::QuestionAsked(e)) { + Ok(c) => c, + Err(err) => vec![agent_unparsed("opencode", &err, value.clone())], + }, + Err(err) => vec![agent_unparsed("opencode", &format!("question.asked: {}", err), value.clone())], + } + } + "permission.asked" => { + match serde_json::from_value::(value.clone()) { + Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::PermissionAsked(e)) { + Ok(c) => c, + Err(err) => vec![agent_unparsed("opencode", &err, value.clone())], + }, + Err(err) => vec![agent_unparsed("opencode", &format!("permission.asked: {}", err), value.clone())], + } + } + "session.created" => { + match serde_json::from_value::(value.clone()) { + Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::SessionCreated(e)) { + Ok(c) => c, + Err(err) => vec![agent_unparsed("opencode", &err, value.clone())], + }, + Err(err) => vec![agent_unparsed("opencode", &format!("session.created: {}", err), value.clone())], + } + } + "session.status" => { + match serde_json::from_value::(value.clone()) { + Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::SessionStatus(e)) { + Ok(c) => c, + Err(err) => vec![agent_unparsed("opencode", &err, value.clone())], + }, + Err(err) => vec![agent_unparsed("opencode", &format!("session.status: {}", err), value.clone())], + } + } + "session.idle" => { + match serde_json::from_value::(value.clone()) { + Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::SessionIdle(e)) { + Ok(c) => c, + Err(err) => vec![agent_unparsed("opencode", &err, value.clone())], + }, + Err(err) => vec![agent_unparsed("opencode", &format!("session.idle: {}", err), value.clone())], + } + } + "session.error" => { + match serde_json::from_value::(value.clone()) { + Ok(e) => match convert_opencode::event_to_universal(&opencode_schema::Event::SessionError(e)) { + Ok(c) => c, + Err(err) => vec![agent_unparsed("opencode", &err, value.clone())], + }, + Err(err) => vec![agent_unparsed("opencode", &format!("session.error: {}", err), value.clone())], + } + } + // Informational events we can safely skip + "server.connected" | "server.heartbeat" | "session.updated" + | "session.diff" | "file.watcher.updated" => { + continue; + } + _ => { + vec![agent_unparsed("opencode", &format!("unknown event type: {}", event_type), value.clone())] + } }; let _ = self.record_conversions(&session_id, conversions).await; } @@ -6447,7 +6526,7 @@ fn normalize_permission_mode( AgentId::Claude => false, AgentId::Codex => matches!(mode, "default" | "plan" | "bypass" | "acceptEdits"), AgentId::Amp => matches!(mode, "default" | "bypass"), - AgentId::Opencode => matches!(mode, "default"), + AgentId::Opencode => matches!(mode, "default" | "bypass"), AgentId::Mock => matches!(mode, "default" | "plan" | "bypass"), }; if !supported {