story-kit: merge 91_bug_permissions_dialog_never_triggers_in_web_ui
This commit is contained in:
@@ -760,6 +760,24 @@ fn handle_tools_list(id: Option<Value>) -> JsonRpcResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "prompt_permission",
|
||||
"description": "Present a permission request to the user via the web UI. Used by Claude Code's --permission-prompt-tool to delegate permission decisions to the frontend dialog. Returns on approval; returns an error on denial.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tool_name": {
|
||||
"type": "string",
|
||||
"description": "The tool requesting permission (e.g. 'Bash', 'Write')"
|
||||
},
|
||||
"input": {
|
||||
"type": "object",
|
||||
"description": "The tool's input arguments"
|
||||
}
|
||||
},
|
||||
"required": ["tool_name", "input"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}),
|
||||
@@ -818,6 +836,8 @@ async fn handle_tools_call(
|
||||
"request_qa" => tool_request_qa(&args, ctx).await,
|
||||
// Diagnostics
|
||||
"get_server_logs" => tool_get_server_logs(&args),
|
||||
// Permission bridge (Claude Code → frontend dialog)
|
||||
"prompt_permission" => tool_prompt_permission(&args, ctx).await,
|
||||
_ => Err(format!("Unknown tool: {tool_name}")),
|
||||
};
|
||||
|
||||
@@ -1550,6 +1570,49 @@ fn tool_get_server_logs(args: &Value) -> Result<String, String> {
|
||||
Ok(recent.join("\n"))
|
||||
}
|
||||
|
||||
/// MCP tool called by Claude Code via `--permission-prompt-tool`.
|
||||
///
|
||||
/// Forwards the permission request through the shared channel to the active
|
||||
/// WebSocket session, which presents a dialog to the user. Blocks until the
|
||||
/// user approves or denies (with a 5-minute timeout).
|
||||
async fn tool_prompt_permission(args: &Value, ctx: &AppContext) -> Result<String, String> {
|
||||
let tool_name = args
|
||||
.get("tool_name")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
let tool_input = args
|
||||
.get("input")
|
||||
.cloned()
|
||||
.unwrap_or(json!({}));
|
||||
|
||||
let request_id = uuid::Uuid::new_v4().to_string();
|
||||
let (response_tx, response_rx) = tokio::sync::oneshot::channel();
|
||||
|
||||
ctx.perm_tx
|
||||
.send(crate::http::context::PermissionForward {
|
||||
request_id: request_id.clone(),
|
||||
tool_name: tool_name.clone(),
|
||||
tool_input,
|
||||
response_tx,
|
||||
})
|
||||
.map_err(|_| "No active WebSocket session to receive permission request".to_string())?;
|
||||
|
||||
let approved = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(300),
|
||||
response_rx,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| format!("Permission request for '{tool_name}' timed out after 5 minutes"))?
|
||||
.map_err(|_| "Permission response channel closed unexpectedly".to_string())?;
|
||||
|
||||
if approved {
|
||||
Ok(format!("Permission granted for '{tool_name}'"))
|
||||
} else {
|
||||
Err(format!("User denied permission for '{tool_name}'"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1647,7 +1710,8 @@ mod tests {
|
||||
assert!(names.contains(&"move_story_to_merge"));
|
||||
assert!(names.contains(&"request_qa"));
|
||||
assert!(names.contains(&"get_server_logs"));
|
||||
assert_eq!(tools.len(), 27);
|
||||
assert!(names.contains(&"prompt_permission"));
|
||||
assert_eq!(tools.len(), 28);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user