



















@@ -14,6 +14,79 @@ struct ExecApprovalPromptRequest: Codable {
1414var agentId: String?
1515var resolvedPath: String?
1616var sessionKey: String?
17+var allowedDecisions: [ExecApprovalDecision]?
18+19+init(
20+ command: String,
21+ cwd: String? = nil,
22+ host: String? = nil,
23+ security: String? = nil,
24+ ask: String? = nil,
25+ agentId: String? = nil,
26+ resolvedPath: String? = nil,
27+ sessionKey: String? = nil,
28+ allowedDecisions: [ExecApprovalDecision]? = nil)
29+{
30+self.command = command
31+self.cwd = cwd
32+self.host = host
33+self.security = security
34+self.ask = ask
35+self.agentId = agentId
36+self.resolvedPath = resolvedPath
37+self.sessionKey = sessionKey
38+self.allowedDecisions = allowedDecisions
39+}
40+41+private enum CodingKeys: String, CodingKey {
42+case command
43+case cwd
44+case host
45+case security
46+case ask
47+case agentId
48+case resolvedPath
49+case sessionKey
50+case allowedDecisions
51+}
52+53+init(from decoder: Decoder) throws {
54+let container = try decoder.container(keyedBy: CodingKeys.self)
55+self.command = try container.decode(String.self, forKey: .command)
56+self.cwd = try container.decodeIfPresent(String.self, forKey: .cwd)
57+self.host = try container.decodeIfPresent(String.self, forKey: .host)
58+self.security = try container.decodeIfPresent(String.self, forKey: .security)
59+self.ask = try container.decodeIfPresent(String.self, forKey: .ask)
60+self.agentId = try container.decodeIfPresent(String.self, forKey: .agentId)
61+self.resolvedPath = try container.decodeIfPresent(String.self, forKey: .resolvedPath)
62+self.sessionKey = try container.decodeIfPresent(String.self, forKey: .sessionKey)
63+let decodedDecisions = (try? container.decodeIfPresent(
64+[DecodedExecApprovalDecision].self,
65+ forKey: .allowedDecisions)) ?? []
66+self.allowedDecisions = decodedDecisions.compactMap(\.decision)
67+}
68+69+static func allowedDecisions(forAsk ask: String?) -> [ExecApprovalDecision] {
70+ // Older payloads did not carry ask/allowedDecisions. Preserve their durable
71+ // approval option; explicit ask=always and allowedDecisions payloads are the
72+ // policy-carrying shapes that remove it.
73+ ask == ExecAsk.always.rawValue
74+? [.allowOnce, .deny]
75+: [.allowOnce, .allowAlways, .deny]
76+}
77+}
78+79+private struct DecodedExecApprovalDecision: Decodable {
80+var decision: ExecApprovalDecision?
81+82+init(from decoder: Decoder) throws {
83+let container = try decoder.singleValueContainer()
84+guard let raw = try? container.decode(String.self) else {
85+self.decision = nil
86+return
87+}
88+self.decision = ExecApprovalDecision(rawValue: raw)
89+}
1790}
18911992private struct ExecApprovalSocketRequest: Codable {
@@ -227,28 +300,55 @@ final class ExecApprovalsPromptServer {
227300228301enum ExecApprovalsPromptPresenter {
229302@MainActor
230-static func prompt(_ request: ExecApprovalPromptRequest) -> ExecApprovalDecision {
303+static func prompt(_ request: ExecApprovalPromptRequest) -> ExecApprovalDecision? {
231304NSApp.activate(ignoringOtherApps: true)
232305let alert = NSAlert()
233306 alert.alertStyle = .warning
234307 alert.messageText = "Allow this command?"
235308 alert.informativeText = "Review the command details before allowing."
236309 alert.accessoryView = self.buildAccessoryView(request)
237310238- alert.addButton(withTitle: "Allow Once")
239- alert.addButton(withTitle: "Always Allow")
240- alert.addButton(withTitle: "Don't Allow")
241-if #available(macOS 11.0, *), alert.buttons.indices.contains(2) {
242- alert.buttons[2].hasDestructiveAction = true
311+let decisions = self.allowedPromptDecisions(request)
312+for decision in decisions {
313+ alert.addButton(withTitle: self.buttonTitle(for: decision))
314+}
315+if #available(macOS 11.0, *),
316+let denyIndex = decisions.firstIndex(of: .deny),
317+ alert.buttons.indices.contains(denyIndex)
318+{
319+ alert.buttons[denyIndex].hasDestructiveAction = true
320+}
321+322+return self.decision(forModalResponse: alert.runModal(), decisions: decisions)
323+}
324+325+static func decision(
326+ forModalResponse response: NSApplication.ModalResponse,
327+ decisions: [ExecApprovalDecision]) -> ExecApprovalDecision?
328+{
329+let selectedIndex = response.rawValue
330+- NSApplication.ModalResponse.alertFirstButtonReturn.rawValue
331+if decisions.indices.contains(selectedIndex) {
332+return decisions[selectedIndex]
333+}
334+return decisions.contains(.deny) ? .deny : nil
335+}
336+337+static func allowedPromptDecisions(_ request: ExecApprovalPromptRequest) -> [ExecApprovalDecision] {
338+if let allowedDecisions = request.allowedDecisions, !allowedDecisions.isEmpty {
339+return allowedDecisions
243340}
341+return ExecApprovalPromptRequest.allowedDecisions(forAsk: request.ask)
342+}
244343245-switch alert.runModal() {
246-case .alertFirstButtonReturn:
247-return .allowOnce
248-case .alertSecondButtonReturn:
249-return .allowAlways
250-default:
251-return .deny
344+private static func buttonTitle(for decision: ExecApprovalDecision) -> String {
345+switch decision {
346+case .allowOnce:
347+"Allow Once"
348+case .allowAlways:
349+"Always Allow"
350+case .deny:
351+"Don't Allow"
252352}
253353}
254354@@ -392,7 +492,7 @@ private enum ExecHostExecutor {
392492case .allow:
393493break
394494case .requiresPrompt:
395-let decision = ExecApprovalsPromptPresenter.prompt(
495+guard let decision = ExecApprovalsPromptPresenter.prompt(
396496ExecApprovalPromptRequest(
397497 command: context.displayCommand,
398498 cwd: request.cwd,
@@ -401,7 +501,15 @@ private enum ExecHostExecutor {
401501 ask: context.ask.rawValue,
402502 agentId: context.agentId,
403503 resolvedPath: context.resolution?.resolvedPath,
404- sessionKey: request.sessionKey))
504+ sessionKey: request.sessionKey,
505+ allowedDecisions: ExecApprovalPromptRequest.allowedDecisions(
506+ forAsk: context.ask.rawValue)))
507+else {
508+return self.errorResponse(
509+ code: "UNAVAILABLE",
510+ message: "SYSTEM_RUN_DENIED: approval prompt closed without decision",
511+ reason: "approval-cancelled")
512+}
405513406514let followupDecision: ExecApprovalDecision
407515switch decision {
@@ -657,7 +765,7 @@ private final class ExecApprovalsSocketServer: @unchecked Sendable {
657765private let logger = Logger(subsystem: "ai.openclaw", category: "exec-approvals.socket")
658766private let socketPath: String
659767private let token: String
660-private let onPrompt: @Sendable (ExecApprovalPromptRequest) async -> ExecApprovalDecision
768+private let onPrompt: @Sendable (ExecApprovalPromptRequest) async -> ExecApprovalDecision?
661769private let onExec: @Sendable (ExecHostRequest) async -> ExecHostResponse
662770private var socketFD: Int32 = -1
663771private var acceptTask: Task<Void, Never>?
@@ -666,7 +774,7 @@ private final class ExecApprovalsSocketServer: @unchecked Sendable {
666774init(
667775 socketPath: String,
668776 token: String,
669- onPrompt: @escaping @Sendable (ExecApprovalPromptRequest) async -> ExecApprovalDecision,
777+ onPrompt: @escaping @Sendable (ExecApprovalPromptRequest) async -> ExecApprovalDecision?,
670778 onExec: @escaping @Sendable (ExecHostRequest) async -> ExecHostResponse)
671779{
672780self.socketPath = socketPath
@@ -808,7 +916,7 @@ private final class ExecApprovalsSocketServer: @unchecked Sendable {
808916try self.sendApprovalResponse(handle: handle, id: request.id, decision: .deny)
809917return
810918}
811-let decision = await self.onPrompt(request.request)
919+guard let decision = await self.onPrompt(request.request) else { return }
812920try self.sendApprovalResponse(handle: handle, id: request.id, decision: decision)
813921return
814922}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。