fix(node): handle invoke approvals and errors
This commit is contained in:
committed by
Peter Steinberger
parent
7d93de710e
commit
1092b30531
@@ -486,8 +486,10 @@ actor MacNodeRuntime {
|
|||||||
return false
|
return false
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let approvedByAsk = params.approved == true
|
let decisionFromParams = Self.parseApprovalDecision(params.approvalDecision)
|
||||||
if requiresAsk, !approvedByAsk {
|
var approvedByAsk = params.approved == true || decisionFromParams != nil
|
||||||
|
var persistAllowlist = decisionFromParams == .allowAlways
|
||||||
|
if decisionFromParams == .deny {
|
||||||
await self.emitExecEvent(
|
await self.emitExecEvent(
|
||||||
"exec.denied",
|
"exec.denied",
|
||||||
payload: ExecEventPayload(
|
payload: ExecEventPayload(
|
||||||
@@ -495,11 +497,53 @@ actor MacNodeRuntime {
|
|||||||
runId: runId,
|
runId: runId,
|
||||||
host: "node",
|
host: "node",
|
||||||
command: displayCommand,
|
command: displayCommand,
|
||||||
reason: "approval-required"))
|
reason: "user-denied"))
|
||||||
return Self.errorResponse(
|
return Self.errorResponse(
|
||||||
req,
|
req,
|
||||||
code: .unavailable,
|
code: .unavailable,
|
||||||
message: "SYSTEM_RUN_DENIED: approval required")
|
message: "SYSTEM_RUN_DENIED: user denied")
|
||||||
|
}
|
||||||
|
if requiresAsk, !approvedByAsk {
|
||||||
|
let decision = await MainActor.run {
|
||||||
|
ExecApprovalsPromptPresenter.prompt(
|
||||||
|
ExecApprovalPromptRequest(
|
||||||
|
command: displayCommand,
|
||||||
|
cwd: params.cwd,
|
||||||
|
host: "node",
|
||||||
|
security: security.rawValue,
|
||||||
|
ask: ask.rawValue,
|
||||||
|
agentId: agentId,
|
||||||
|
resolvedPath: resolution?.resolvedPath))
|
||||||
|
}
|
||||||
|
switch decision {
|
||||||
|
case .deny:
|
||||||
|
await self.emitExecEvent(
|
||||||
|
"exec.denied",
|
||||||
|
payload: ExecEventPayload(
|
||||||
|
sessionKey: sessionKey,
|
||||||
|
runId: runId,
|
||||||
|
host: "node",
|
||||||
|
command: displayCommand,
|
||||||
|
reason: "user-denied"))
|
||||||
|
return Self.errorResponse(
|
||||||
|
req,
|
||||||
|
code: .unavailable,
|
||||||
|
message: "SYSTEM_RUN_DENIED: user denied")
|
||||||
|
case .allowAlways:
|
||||||
|
approvedByAsk = true
|
||||||
|
persistAllowlist = true
|
||||||
|
case .allowOnce:
|
||||||
|
approvedByAsk = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if persistAllowlist, security == .allowlist {
|
||||||
|
let pattern = resolution?.resolvedPath
|
||||||
|
?? resolution?.rawExecutable
|
||||||
|
?? command.first
|
||||||
|
?? ""
|
||||||
|
if !pattern.isEmpty {
|
||||||
|
ExecApprovalsStore.addAllowlistEntry(agentId: agentId, pattern: pattern)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if security == .allowlist, allowlistMatch == nil, !skillAllow, !approvedByAsk {
|
if security == .allowlist, allowlistMatch == nil, !skillAllow, !approvedByAsk {
|
||||||
@@ -763,6 +807,12 @@ extension MacNodeRuntime {
|
|||||||
UserDefaults.standard.object(forKey: cameraEnabledKey) as? Bool ?? false
|
UserDefaults.standard.object(forKey: cameraEnabledKey) as? Bool ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func parseApprovalDecision(_ raw: String?) -> ExecApprovalDecision? {
|
||||||
|
let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||||
|
guard !trimmed.isEmpty else { return nil }
|
||||||
|
return ExecApprovalDecision(rawValue: trimmed)
|
||||||
|
}
|
||||||
|
|
||||||
private static let blockedEnvKeys: Set<String> = [
|
private static let blockedEnvKeys: Set<String> = [
|
||||||
"PATH",
|
"PATH",
|
||||||
"NODE_OPTIONS",
|
"NODE_OPTIONS",
|
||||||
|
|||||||
@@ -571,7 +571,14 @@ public actor GatewayChannelActor {
|
|||||||
id: id,
|
id: id,
|
||||||
method: method,
|
method: method,
|
||||||
params: paramsObject)
|
params: paramsObject)
|
||||||
let data = try self.encoder.encode(frame)
|
let data: Data
|
||||||
|
do {
|
||||||
|
data = try self.encoder.encode(frame)
|
||||||
|
} catch {
|
||||||
|
self.logger.error(
|
||||||
|
"gateway request encode failed \(method, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
|
||||||
|
throw error
|
||||||
|
}
|
||||||
let response = try await withCheckedThrowingContinuation { (cont: CheckedContinuation<GatewayFrame, Error>) in
|
let response = try await withCheckedThrowingContinuation { (cont: CheckedContinuation<GatewayFrame, Error>) in
|
||||||
self.pending[id] = cont
|
self.pending[id] = cont
|
||||||
Task { [weak self] in
|
Task { [weak self] in
|
||||||
|
|||||||
@@ -219,8 +219,8 @@ public actor GatewayNodeSession {
|
|||||||
}
|
}
|
||||||
if let error = response.error {
|
if let error = response.error {
|
||||||
params["error"] = AnyCodable([
|
params["error"] = AnyCodable([
|
||||||
"code": AnyCodable(error.code.rawValue),
|
"code": error.code.rawValue,
|
||||||
"message": AnyCodable(error.message),
|
"message": error.message,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public struct ClawdbotSystemRunParams: Codable, Sendable, Equatable {
|
|||||||
public var agentId: String?
|
public var agentId: String?
|
||||||
public var sessionKey: String?
|
public var sessionKey: String?
|
||||||
public var approved: Bool?
|
public var approved: Bool?
|
||||||
|
public var approvalDecision: String?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
command: [String],
|
command: [String],
|
||||||
@@ -40,7 +41,8 @@ public struct ClawdbotSystemRunParams: Codable, Sendable, Equatable {
|
|||||||
needsScreenRecording: Bool? = nil,
|
needsScreenRecording: Bool? = nil,
|
||||||
agentId: String? = nil,
|
agentId: String? = nil,
|
||||||
sessionKey: String? = nil,
|
sessionKey: String? = nil,
|
||||||
approved: Bool? = nil)
|
approved: Bool? = nil,
|
||||||
|
approvalDecision: String? = nil)
|
||||||
{
|
{
|
||||||
self.command = command
|
self.command = command
|
||||||
self.rawCommand = rawCommand
|
self.rawCommand = rawCommand
|
||||||
@@ -51,6 +53,7 @@ public struct ClawdbotSystemRunParams: Codable, Sendable, Equatable {
|
|||||||
self.agentId = agentId
|
self.agentId = agentId
|
||||||
self.sessionKey = sessionKey
|
self.sessionKey = sessionKey
|
||||||
self.approved = approved
|
self.approved = approved
|
||||||
|
self.approvalDecision = approvalDecision
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user