fix: resolve merge conflict in claude_code.rs

Keep master's quiet system/rate_limit_event handlers while preserving
the story-62 permission_request handler (the core feature).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dave
2026-02-23 16:01:22 +00:00
parent 026ba3cbcf
commit 6962e92f0c
8 changed files with 367 additions and 43 deletions

View File

@@ -8,6 +8,7 @@ use poem::handler;
use poem::web::Data;
use poem::web::websocket::{Message as WsMessage, WebSocket};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::mpsc;
@@ -17,12 +18,17 @@ use tokio::sync::mpsc;
///
/// - `chat` starts a streaming chat session.
/// - `cancel` stops the active session.
/// - `permission_response` approves or denies a pending permission request.
enum WsRequest {
Chat {
messages: Vec<Message>,
config: chat::ProviderConfig,
},
Cancel,
PermissionResponse {
request_id: String,
approved: bool,
},
}
#[derive(Serialize)]
@@ -63,6 +69,12 @@ enum WsResponse {
qa: Vec<crate::http::workflow::UpcomingStory>,
merge: Vec<crate::http::workflow::UpcomingStory>,
},
/// Claude Code is requesting user approval before executing a tool.
PermissionRequest {
request_id: String,
tool_name: String,
tool_input: serde_json::Value,
},
}
impl From<WatcherEvent> for WsResponse {
@@ -139,52 +151,105 @@ pub async fn ws_handler(ws: WebSocket, ctx: Data<&Arc<AppContext>>) -> impl poem
}
});
while let Some(Ok(msg)) = stream.next().await {
if let WsMessage::Text(text) = msg {
let parsed: Result<WsRequest, _> = serde_json::from_str(&text);
match parsed {
Ok(WsRequest::Chat { messages, config }) => {
let tx_updates = tx.clone();
let tx_tokens = tx.clone();
let ctx_clone = ctx.clone();
// Channel for permission requests flowing from the PTY thread to this handler.
let (perm_req_tx, mut perm_req_rx) =
mpsc::unbounded_channel::<crate::llm::providers::claude_code::PermissionReqMsg>();
// Map of pending permission request_id → one-shot responder.
let mut pending_perms: HashMap<String, std::sync::mpsc::SyncSender<bool>> = HashMap::new();
let result = chat::chat(
messages,
config,
&ctx_clone.state,
ctx_clone.store.as_ref(),
|history| {
let _ = tx_updates.send(WsResponse::Update {
messages: history.to_vec(),
});
},
|token| {
let _ = tx_tokens.send(WsResponse::Token {
content: token.to_string(),
});
},
)
.await;
loop {
// Outer loop: wait for the next WebSocket message.
let Some(Ok(WsMessage::Text(text))) = stream.next().await else {
break;
};
match result {
Ok(chat_result) => {
if let Some(sid) = chat_result.session_id {
let _ = tx.send(WsResponse::SessionId { session_id: sid });
let parsed: Result<WsRequest, _> = serde_json::from_str(&text);
match parsed {
Ok(WsRequest::Chat { messages, config }) => {
let tx_updates = tx.clone();
let tx_tokens = tx.clone();
let ctx_clone = ctx.clone();
let perm_tx = perm_req_tx.clone();
// Build the chat future without driving it yet so we can
// interleave it with permission-request forwarding.
let chat_fut = chat::chat(
messages,
config,
&ctx_clone.state,
ctx_clone.store.as_ref(),
move |history| {
let _ = tx_updates.send(WsResponse::Update {
messages: history.to_vec(),
});
},
move |token| {
let _ = tx_tokens.send(WsResponse::Token {
content: token.to_string(),
});
},
Some(perm_tx),
);
tokio::pin!(chat_fut);
// Inner loop: drive the chat while concurrently handling
// permission requests and WebSocket messages.
let chat_result = loop {
tokio::select! {
result = &mut chat_fut => break result,
// Forward permission requests from PTY to the client.
Some(perm_req) = perm_req_rx.recv() => {
let _ = tx.send(WsResponse::PermissionRequest {
request_id: perm_req.request_id.clone(),
tool_name: perm_req.tool_name.clone(),
tool_input: perm_req.tool_input.clone(),
});
pending_perms.insert(
perm_req.request_id,
perm_req.response_tx,
);
}
// Handle WebSocket messages during an active chat
// (permission responses and cancellations).
Some(Ok(WsMessage::Text(inner_text))) = stream.next() => {
match serde_json::from_str::<WsRequest>(&inner_text) {
Ok(WsRequest::PermissionResponse { request_id, approved }) => {
if let Some(resp_tx) = pending_perms.remove(&request_id) {
let _ = resp_tx.send(approved);
}
}
Ok(WsRequest::Cancel) => {
let _ = chat::cancel_chat(&ctx.state);
}
_ => {}
}
}
Err(err) => {
let _ = tx.send(WsResponse::Error { message: err });
}
};
match chat_result {
Ok(chat_result) => {
if let Some(sid) = chat_result.session_id {
let _ = tx.send(WsResponse::SessionId { session_id: sid });
}
}
Err(err) => {
let _ = tx.send(WsResponse::Error { message: err });
}
}
Ok(WsRequest::Cancel) => {
let _ = chat::cancel_chat(&ctx.state);
}
Err(err) => {
let _ = tx.send(WsResponse::Error {
message: format!("Invalid request: {err}"),
});
}
}
Ok(WsRequest::Cancel) => {
let _ = chat::cancel_chat(&ctx.state);
}
Ok(WsRequest::PermissionResponse { .. }) => {
// Permission responses outside an active chat are ignored.
}
Err(err) => {
let _ = tx.send(WsResponse::Error {
message: format!("Invalid request: {err}"),
});
}
}
}