feat: manual approvals (#748)
* manual user approvals * refactor implementation * cleanup * fix lint errors * i18n * remove isLastEntry frontend check * address fe feedback * always run claude plan with approvals * add watchkill script back to plan mode * update timeout * tooltip hover * use response type * put back watchkill append hack
This commit is contained in:
committed by
GitHub
parent
eaff3dee9e
commit
798bcb80a3
69
crates/utils/src/approvals.rs
Normal file
69
crates/utils/src/approvals.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub const APPROVAL_TIMEOUT_SECONDS: i64 = 3600; // 1 hour
|
||||
pub const EXIT_PLAN_MODE_TOOL_NAME: &str = "ExitPlanMode";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
pub struct ApprovalRequest {
|
||||
pub id: String,
|
||||
pub tool_name: String,
|
||||
pub tool_input: serde_json::Value,
|
||||
pub session_id: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub timeout_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl ApprovalRequest {
|
||||
pub fn from_create(request: CreateApprovalRequest) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
tool_name: request.tool_name,
|
||||
tool_input: request.tool_input,
|
||||
session_id: request.session_id,
|
||||
created_at: now,
|
||||
timeout_at: now + Duration::seconds(APPROVAL_TIMEOUT_SECONDS),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct CreateApprovalRequest {
|
||||
pub tool_name: String,
|
||||
pub tool_input: serde_json::Value,
|
||||
pub session_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "status", rename_all = "snake_case")]
|
||||
pub enum ApprovalStatus {
|
||||
Pending,
|
||||
Approved,
|
||||
Denied {
|
||||
#[ts(optional)]
|
||||
reason: Option<String>,
|
||||
},
|
||||
TimedOut,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct ApprovalResponse {
|
||||
pub execution_process_id: Uuid,
|
||||
pub status: ApprovalStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct ApprovalPendingInfo {
|
||||
pub approval_id: String,
|
||||
pub execution_process_id: Uuid,
|
||||
pub tool_name: String,
|
||||
pub requested_at: DateTime<Utc>,
|
||||
pub timeout_at: DateTime<Utc>,
|
||||
}
|
||||
@@ -2,6 +2,7 @@ use std::{env, sync::OnceLock};
|
||||
|
||||
use directories::ProjectDirs;
|
||||
|
||||
pub mod approvals;
|
||||
pub mod assets;
|
||||
pub mod browser;
|
||||
pub mod diff;
|
||||
|
||||
@@ -67,6 +67,7 @@ impl MsgStore {
|
||||
pub fn push_stdout<S: Into<String>>(&self, s: S) {
|
||||
self.push(LogMsg::Stdout(s.into()));
|
||||
}
|
||||
|
||||
pub fn push_stderr<S: Into<String>>(&self, s: S) {
|
||||
self.push(LogMsg::Stderr(s.into()));
|
||||
}
|
||||
@@ -85,6 +86,7 @@ impl MsgStore {
|
||||
pub fn get_receiver(&self) -> broadcast::Receiver<LogMsg> {
|
||||
self.sender.subscribe()
|
||||
}
|
||||
|
||||
pub fn get_history(&self) -> Vec<LogMsg> {
|
||||
self.inner
|
||||
.read()
|
||||
|
||||
@@ -10,3 +10,25 @@ pub async fn write_port_file(port: u16) -> std::io::Result<PathBuf> {
|
||||
fs::write(&path, port.to_string()).await?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub async fn read_port_file(app_name: &str) -> std::io::Result<u16> {
|
||||
let base = if cfg!(target_os = "linux") {
|
||||
match env::var("XDG_RUNTIME_DIR") {
|
||||
Ok(val) if !val.is_empty() => PathBuf::from(val),
|
||||
_ => env::temp_dir(),
|
||||
}
|
||||
} else {
|
||||
env::temp_dir()
|
||||
};
|
||||
|
||||
let path = base.join(app_name).join(format!("{app_name}.port"));
|
||||
tracing::debug!("Reading port from {:?}", path);
|
||||
|
||||
let content = fs::read_to_string(&path).await?;
|
||||
let port: u16 = content
|
||||
.trim()
|
||||
.parse()
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
||||
|
||||
Ok(port)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user