story-219: add Always Allow button to web UI permission dialog

Cherry-pick from feature branch — code was never squash-merged
despite story being accepted (bug 226).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dave
2026-02-27 10:00:33 +00:00
parent de03cfe8b3
commit eeec745abc
6 changed files with 331 additions and 13 deletions

View File

@@ -7,6 +7,18 @@ use poem::http::StatusCode;
use std::sync::Arc;
use tokio::sync::{broadcast, mpsc, oneshot};
/// The user's decision when responding to a permission dialog.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PermissionDecision {
/// One-time denial.
Deny,
/// One-time approval.
Approve,
/// Approve and persist the rule to `.claude/settings.json` so Claude Code's
/// built-in permission system handles future checks without prompting.
AlwaysAllow,
}
/// A permission request forwarded from the MCP `prompt_permission` tool to the
/// active WebSocket session. The MCP handler blocks on `response_tx` until the
/// user approves or denies via the frontend dialog.
@@ -14,7 +26,7 @@ pub struct PermissionForward {
pub request_id: String,
pub tool_name: String,
pub tool_input: serde_json::Value,
pub response_tx: oneshot::Sender<bool>,
pub response_tx: oneshot::Sender<PermissionDecision>,
}
#[derive(Clone)]
@@ -82,4 +94,13 @@ mod tests {
let err = bad_request(String::new());
assert_eq!(err.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn permission_decision_equality() {
assert_eq!(PermissionDecision::Deny, PermissionDecision::Deny);
assert_eq!(PermissionDecision::Approve, PermissionDecision::Approve);
assert_eq!(PermissionDecision::AlwaysAllow, PermissionDecision::AlwaysAllow);
assert_ne!(PermissionDecision::Deny, PermissionDecision::Approve);
assert_ne!(PermissionDecision::Approve, PermissionDecision::AlwaysAllow);
}
}