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;
|
let session_id = output.session.id;
|
||||||
|
|
||||||
// Register the xterm output channel for this session.
|
// §17.4/§17.6: a structured launch routes to an `AgentSession`, registered
|
||||||
let gen = state.pty_bridge.register(session_id, on_output);
|
// **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.
|
// 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
|
// 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.
|
// ends when the PTY hits EOF or this attach is superseded.
|
||||||
let handle = PtyHandle { session_id };
|
let handle = PtyHandle { session_id };
|
||||||
match state.pty_port.subscribe_output(&handle) {
|
match state.pty_port.subscribe_output(&handle) {
|
||||||
Ok(stream) => {
|
Ok(stream) => {
|
||||||
let bridge: std::sync::Arc<PtyBridge> = std::sync::Arc::clone(&state.pty_bridge);
|
let bridge: std::sync::Arc<PtyBridge> = std::sync::Arc::clone(&state.pty_bridge);
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
for chunk in stream {
|
for chunk in stream {
|
||||||
if !bridge.send_output(&session_id, chunk) {
|
if !bridge.send_output(&session_id, chunk) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
bridge.unregister_if(&session_id, gen);
|
||||||
bridge.unregister_if(&session_id, gen);
|
});
|
||||||
});
|
}
|
||||||
}
|
Err(e) => {
|
||||||
Err(e) => {
|
state.pty_bridge.unregister(&session_id);
|
||||||
state.pty_bridge.unregister(&session_id);
|
return Err(ErrorDto::from(AppError::from(e)));
|
||||||
return Err(ErrorDto::from(AppError::from(e)));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user