Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions src/OpenClaw.Shared/Capabilities/SystemCapability.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ public class SystemCapability : NodeCapabilityBase

// Event to let UI handle the actual notification display
public event EventHandler<SystemNotifyArgs>? NotifyRequested;

/// <summary>
/// Fired when policy denies an exec request non-interactively (no native
/// prompt is shown — e.g. default action is Deny, or matched rule action
/// is Deny). Lets UI surfaces (chat) render a denial card that would
/// otherwise be invisible to the user. Not raised when the user clicks
/// Deny in the native prompt — that path already raises
/// <see cref="ExecApprovalPromptService.Decided"/>.
/// </summary>
public event EventHandler<ExecApprovalPromptDecidedEventArgs>? PolicyAutoDecided;

// Command runner for system.run (swappable: local, docker, wsl)
private ICommandRunner? _commandRunner;
Expand Down Expand Up @@ -474,7 +484,10 @@ private async Task<ExecApprovalCheckResult> EnsureApprovedAsync(
return new ExecApprovalCheckResult(true, null);

if (approval.Action != ExecApprovalAction.Prompt || _promptHandler == null || _approvalPolicy == null)
{
RaisePolicyAutoDenied(command, shell, approval);
return new ExecApprovalCheckResult(false, null);
}

var decision = await _promptHandler.RequestAsync(new ExecApprovalPromptRequest
{
Expand Down Expand Up @@ -532,6 +545,31 @@ private static bool IsExplicitDeny(ExecApprovalResult approval) =>
private readonly record struct ExecApprovalCheckResult(
bool Allowed,
ExecApprovalPromptDecisionKind? PromptDecisionKind);

private void RaisePolicyAutoDenied(string command, string? shell, ExecApprovalResult approval)
{
var handler = PolicyAutoDecided;
if (handler == null) return;
try
{
var request = new ExecApprovalPromptRequest
{
Command = command,
Shell = shell,
MatchedPattern = approval.MatchedPattern,
Reason = approval.Reason ?? "Command denied by policy"
};
var decision = ExecApprovalPromptDecision.Deny(approval.Reason ?? "Command denied by policy");
handler(this, new ExecApprovalPromptDecidedEventArgs(
request,
decision,
ExecApprovalPromptDecisionSource.PolicyAutoDeny));
}
catch (Exception ex)
{
Logger.Warn($"PolicyAutoDecided handler threw: {ex.Message}");
}
}

private NodeInvokeResponse HandleExecApprovalsGet()
{
Expand Down Expand Up @@ -616,7 +654,7 @@ private NodeInvokeResponse HandleExecApprovalsSet(NodeInvokeRequest request)
rule.Action = actStr.ToLowerInvariant() switch
{
"allow" => ExecApprovalAction.Allow,
"prompt" => ExecApprovalAction.Prompt,
"prompt" or "ask" => ExecApprovalAction.Prompt,
_ => ExecApprovalAction.Deny
};
}
Expand Down Expand Up @@ -650,7 +688,7 @@ private NodeInvokeResponse HandleExecApprovalsSet(NodeInvokeRequest request)
defaultAction = defStr.ToLowerInvariant() switch
{
"allow" => ExecApprovalAction.Allow,
"prompt" => ExecApprovalAction.Prompt,
"prompt" or "ask" => ExecApprovalAction.Prompt,
_ => ExecApprovalAction.Deny
};
}
Expand Down
Loading
Loading