diff --git a/crates/app-tauri/src/commands.rs b/crates/app-tauri/src/commands.rs index d6f65ed..5c16879 100644 --- a/crates/app-tauri/src/commands.rs +++ b/crates/app-tauri/src/commands.rs @@ -1064,28 +1064,37 @@ pub async fn launch_agent( let session_id = output.session.id; - // Register the xterm output channel for this session. - let gen = state.pty_bridge.register(session_id, on_output); + // §17.4/§17.6: a structured launch routes to an `AgentSession`, registered + // **only** in `StructuredSessions` — its id is never a live PTY. Such a cell is + // a chat view driven by `agent_send`/`reattach_agent_chat`, not by xterm bytes, + // so we must NOT wire it to the PTY bridge. Doing so would call + // `subscribe_output` with an id the PTY adapter has never seen → `NotFound` + // ("process error: pty handle not found"), failing every structured agent + // launch. Only the raw-PTY path (`structured: None`) gets the byte pump. + if output.structured.is_none() { + // Register the xterm output channel for this session. + let gen = state.pty_bridge.register(session_id, on_output); - // Subscribe to the PTY's byte stream and pump it to the channel. - // The stream is a blocking iterator; it runs on a dedicated OS thread and - // ends when the PTY hits EOF or this attach is superseded. - let handle = PtyHandle { session_id }; - match state.pty_port.subscribe_output(&handle) { - Ok(stream) => { - let bridge: std::sync::Arc = std::sync::Arc::clone(&state.pty_bridge); - std::thread::spawn(move || { - for chunk in stream { - if !bridge.send_output(&session_id, chunk) { - break; + // Subscribe to the PTY's byte stream and pump it to the channel. + // The stream is a blocking iterator; it runs on a dedicated OS thread and + // ends when the PTY hits EOF or this attach is superseded. + let handle = PtyHandle { session_id }; + match state.pty_port.subscribe_output(&handle) { + Ok(stream) => { + let bridge: std::sync::Arc = std::sync::Arc::clone(&state.pty_bridge); + std::thread::spawn(move || { + for chunk in stream { + if !bridge.send_output(&session_id, chunk) { + break; + } } - } - bridge.unregister_if(&session_id, gen); - }); - } - Err(e) => { - state.pty_bridge.unregister(&session_id); - return Err(ErrorDto::from(AppError::from(e))); + bridge.unregister_if(&session_id, gen); + }); + } + Err(e) => { + state.pty_bridge.unregister(&session_id); + return Err(ErrorDto::from(AppError::from(e))); + } } }