use log::debug;
mod approval_specs;
pub use approval_specs::{
http_fetch as approval_spec_http_fetch,
http_request as approval_spec_http_request,
read_dir_external_path as approval_spec_read_dir_external_path,
run_command_unknown_cmd as approval_spec_run_command_unknown_cmd,
shell_script as approval_spec_shell_script,
workspace_external_path as approval_spec_workspace_external_path,
};
pub use crate::cm_approval::{
ApprovalRequestSpec, InteractiveGateOutcome, SensitiveCapability, SharedAllowlistHandles,
ToolApprovalWebError, WebApprovalChannelMode, WebApprovalSink, persist_allowlist_key,
run_web_tool_approval,
};
use crate::cm_types::CommandApprovalDecision;
pub fn web_tool_runtime_approval_sink(
rt: &crate::cm_tools::tool_runtime::WebToolRuntime,
) -> WebApprovalSink<'_> {
WebApprovalSink {
out_tx: &rt.out_tx,
approval_rx_shared: &rt.approval_rx_shared,
approval_request_guard: &rt.approval_request_guard,
}
}
pub async fn interactive_gate_after_whitelist_miss(
web: Option<WebApprovalSink<'_>>,
spec: &ApprovalRequestSpec,
sse_log_label: &'static str,
allowlist: &SharedAllowlistHandles<'_>,
) -> Result<InteractiveGateOutcome, ToolApprovalWebError> {
let decision = request_tool_interactive_approval(web, spec, sse_log_label).await?;
match decision {
CommandApprovalDecision::Deny => Ok(InteractiveGateOutcome::Denied(format!(
"用户拒绝 {}:{}",
spec.sse_command,
spec.sse_args.trim()
))),
CommandApprovalDecision::AllowOnce => Ok(InteractiveGateOutcome::Allowed),
CommandApprovalDecision::AllowAlways => {
if let Some(k) = spec.allowlist_key.as_deref() {
persist_allowlist_key(allowlist, k).await;
}
Ok(InteractiveGateOutcome::Allowed)
}
}
}
pub async fn request_tool_interactive_approval(
web: Option<WebApprovalSink<'_>>,
spec: &ApprovalRequestSpec,
sse_log_label: &'static str,
) -> Result<CommandApprovalDecision, ToolApprovalWebError> {
if let Some(sink) = web {
debug!(
target: "crabmate",
"tool_approval web capability={:?} title={}",
spec.capability,
spec.cli_title
);
return run_web_tool_approval(sink, spec, sse_log_label, WebApprovalChannelMode::Strict)
.await;
}
Err(ToolApprovalWebError::ChannelUnavailable)
}
pub fn shared_allowlist_handles_web(
web_ctx: Option<&crate::cm_tools::tool_runtime::WebToolRuntime>,
) -> SharedAllowlistHandles<'_> {
SharedAllowlistHandles {
web: web_ctx.map(|w| &w.persistent_allowlist_shared),
}
}
pub async fn interactive_gate_web_runtime(
web_ctx: Option<&crate::cm_tools::tool_runtime::WebToolRuntime>,
spec: &ApprovalRequestSpec,
sse_log_label: &'static str,
) -> Result<InteractiveGateOutcome, ToolApprovalWebError> {
interactive_gate_after_whitelist_miss(
web_ctx.map(web_tool_runtime_approval_sink),
spec,
sse_log_label,
&shared_allowlist_handles_web(web_ctx),
)
.await
}
pub fn interactive_gate_outcome_to_tool_err(outcome: InteractiveGateOutcome) -> Result<(), String> {
match outcome {
InteractiveGateOutcome::Allowed => Ok(()),
InteractiveGateOutcome::Denied(msg) => Err(msg),
}
}
pub const HTTP_TOOL_NO_APPROVAL_CHANNEL_ERR: &str =
"错误:当前 URL 未匹配配置的 http_fetch_allowed_prefixes,且无法使用审批通道(例如非流式 Web 会话)。";
pub const INTERACTIVE_GATE_CHANNEL_UNAVAILABLE_ERR: &str = "错误:审批通道不可用,请重试。";