fix(agent): ne câbler le PTY qu'en lancement brut, pas structuré — §17.4/§17.6
Un launch structuré route vers une AgentSession enregistrée dans
StructuredSessions ; son id n'est jamais un PTY vivant. Câbler le bridge PTY
appelait subscribe_output avec un id inconnu de l'adapter → NotFound
("pty handle not found"), faisant échouer tout lancement d'agent structuré.
On ne pompe les octets que sur le chemin PTY brut (structured: None).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -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<PtyBridge> = 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<PtyBridge> = 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user