story-kit: start 62_story_allow_frontend_ui_to_accept_permissions_requests

This commit is contained in:
Dave
2026-02-23 16:07:24 +00:00
2 changed files with 73 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
---
name: Agent Permission Prompts in Web UI
test_plan: pending
---
# Story 62: Agent Permission Prompts in Web UI
## User Story
As a user interacting with an agent through the web UI, I want to be prompted for permission approvals (e.g. file writes, commits) so that the agent can complete tasks that require elevated permissions without getting blocked.
Right now, the web UI does not have any way of the user allowing permissions requests, so dev processes are basically blocked.
## Acceptance Criteria
- [ ] When an agent action requires permission (e.g. writing to a file, committing), the web UI surfaces a prompt to the user
- [ ] The user can approve or deny the permission request from the UI
- [ ] On approval, the agent continues with the requested action
- [ ] On denial, the agent receives the denial and adjusts its approach
- [ ] Permission prompts display enough context (file path, action type) for the user to make an informed decision
## Out of Scope
- Bulk/blanket permission grants (e.g. "allow all writes to this directory")
- Persisting permission decisions across sessions

View File

@@ -369,6 +369,53 @@ fn run_pty_session(
let _ = writeln!(pty_writer, "{}", response);
}
}
// Claude Code is requesting user approval before executing a tool.
// Forward the request to the async context via permission_tx and
// block until the user responds (or a 5-minute timeout elapses).
"permission_request" => {
let request_id = json
.get("id")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let tool_name = json
.get("tool_name")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string();
let tool_input = json
.get("input")
.cloned()
.unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
if let Some(ref ptx) = permission_tx {
let (resp_tx, resp_rx) = std::sync::mpsc::sync_channel(1);
let _ = ptx.send(PermissionReqMsg {
request_id: request_id.clone(),
tool_name,
tool_input,
response_tx: resp_tx,
});
// Block until the user responds or a 5-minute timeout elapses.
let approved = resp_rx
.recv_timeout(std::time::Duration::from_secs(300))
.unwrap_or(false);
let response = serde_json::json!({
"type": "permission_response",
"id": request_id,
"approved": approved,
});
let _ = writeln!(pty_writer, "{}", response);
} else {
// No handler configured — deny by default.
let response = serde_json::json!({
"type": "permission_response",
"id": request_id,
"approved": false,
});
let _ = writeln!(pty_writer, "{}", response);
}
}
_ => {}
}
}