fix: pass android lint and swiftformat
This commit is contained in:
@@ -6,6 +6,7 @@ Docs: https://docs.clawd.bot
|
||||
|
||||
### Changes
|
||||
- Android: migrate node transport to the Gateway WebSocket protocol with TLS pinning support + gateway discovery naming.
|
||||
- Android: bump okhttp + dnsjava to satisfy lint dependency checks.
|
||||
- Docs: refresh Android node discovery docs for the Gateway WS service type.
|
||||
|
||||
## 2026.1.19-1
|
||||
|
||||
@@ -103,7 +103,7 @@ dependencies {
|
||||
|
||||
implementation("androidx.security:security-crypto:1.1.0")
|
||||
implementation("androidx.exifinterface:exifinterface:1.4.2")
|
||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||
implementation("com.squareup.okhttp3:okhttp:5.3.2")
|
||||
|
||||
// CameraX (for node.invoke camera.* parity)
|
||||
implementation("androidx.camera:camera-core:1.5.2")
|
||||
@@ -113,7 +113,7 @@ dependencies {
|
||||
implementation("androidx.camera:camera-view:1.5.2")
|
||||
|
||||
// Unicast DNS-SD (Wide-Area Bonjour) for tailnet discovery domains.
|
||||
implementation("dnsjava:dnsjava:3.6.3")
|
||||
implementation("dnsjava:dnsjava:3.6.4")
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.clawdbot.android.chat.ChatPendingToolCall
|
||||
import com.clawdbot.android.chat.ChatSessionEntry
|
||||
import com.clawdbot.android.chat.OutgoingAttachment
|
||||
import com.clawdbot.android.gateway.DeviceIdentityStore
|
||||
import com.clawdbot.android.gateway.GatewayClientInfo
|
||||
import com.clawdbot.android.gateway.GatewayConnectOptions
|
||||
import com.clawdbot.android.gateway.GatewayDiscovery
|
||||
import com.clawdbot.android.gateway.GatewayEndpoint
|
||||
@@ -208,7 +209,7 @@ class NodeRuntime(context: Context) {
|
||||
},
|
||||
)
|
||||
|
||||
private val chat =
|
||||
private val chat: ChatController =
|
||||
ChatController(
|
||||
scope = scope,
|
||||
session = operatorSession,
|
||||
|
||||
@@ -219,7 +219,7 @@ class GatewaySession(
|
||||
}
|
||||
}
|
||||
|
||||
fun awaitClose() = closedDeferred.await()
|
||||
suspend fun awaitClose() = closedDeferred.await()
|
||||
|
||||
fun closeQuietly() {
|
||||
if (isClosed.compareAndSet(false, true)) {
|
||||
@@ -315,41 +315,20 @@ class GatewaySession(
|
||||
client.modelIdentifier?.let { put("modelIdentifier", JsonPrimitive(it)) }
|
||||
}
|
||||
|
||||
val params =
|
||||
buildJsonObject {
|
||||
put("minProtocol", JsonPrimitive(GATEWAY_PROTOCOL_VERSION))
|
||||
put("maxProtocol", JsonPrimitive(GATEWAY_PROTOCOL_VERSION))
|
||||
put("client", clientObj)
|
||||
if (options.caps.isNotEmpty()) put("caps", JsonArray(options.caps.map(::JsonPrimitive)))
|
||||
if (options.commands.isNotEmpty()) put("commands", JsonArray(options.commands.map(::JsonPrimitive)))
|
||||
if (options.permissions.isNotEmpty()) {
|
||||
put(
|
||||
"permissions",
|
||||
buildJsonObject {
|
||||
options.permissions.forEach { (key, value) ->
|
||||
put(key, JsonPrimitive(value))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
put("role", JsonPrimitive(options.role))
|
||||
if (options.scopes.isNotEmpty()) put("scopes", JsonArray(options.scopes.map(::JsonPrimitive)))
|
||||
put("locale", JsonPrimitive(locale))
|
||||
}
|
||||
|
||||
val authToken = token?.trim().orEmpty()
|
||||
val authPassword = password?.trim().orEmpty()
|
||||
if (authToken.isNotEmpty()) {
|
||||
params["auth"] =
|
||||
buildJsonObject {
|
||||
put("token", JsonPrimitive(authToken))
|
||||
}
|
||||
} else if (authPassword.isNotEmpty()) {
|
||||
params["auth"] =
|
||||
buildJsonObject {
|
||||
put("password", JsonPrimitive(authPassword))
|
||||
}
|
||||
}
|
||||
val authJson =
|
||||
when {
|
||||
authToken.isNotEmpty() ->
|
||||
buildJsonObject {
|
||||
put("token", JsonPrimitive(authToken))
|
||||
}
|
||||
authPassword.isNotEmpty() ->
|
||||
buildJsonObject {
|
||||
put("password", JsonPrimitive(authPassword))
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
val identity = identityStore.loadOrCreate()
|
||||
val signedAtMs = System.currentTimeMillis()
|
||||
@@ -365,17 +344,40 @@ class GatewaySession(
|
||||
)
|
||||
val signature = identityStore.signPayload(payload, identity)
|
||||
val publicKey = identityStore.publicKeyBase64Url(identity)
|
||||
if (!signature.isNullOrBlank() && !publicKey.isNullOrBlank()) {
|
||||
params["device"] =
|
||||
val deviceJson =
|
||||
if (!signature.isNullOrBlank() && !publicKey.isNullOrBlank()) {
|
||||
buildJsonObject {
|
||||
put("id", JsonPrimitive(identity.deviceId))
|
||||
put("publicKey", JsonPrimitive(publicKey))
|
||||
put("signature", JsonPrimitive(signature))
|
||||
put("signedAt", JsonPrimitive(signedAtMs))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return params
|
||||
return buildJsonObject {
|
||||
put("minProtocol", JsonPrimitive(GATEWAY_PROTOCOL_VERSION))
|
||||
put("maxProtocol", JsonPrimitive(GATEWAY_PROTOCOL_VERSION))
|
||||
put("client", clientObj)
|
||||
if (options.caps.isNotEmpty()) put("caps", JsonArray(options.caps.map(::JsonPrimitive)))
|
||||
if (options.commands.isNotEmpty()) put("commands", JsonArray(options.commands.map(::JsonPrimitive)))
|
||||
if (options.permissions.isNotEmpty()) {
|
||||
put(
|
||||
"permissions",
|
||||
buildJsonObject {
|
||||
options.permissions.forEach { (key, value) ->
|
||||
put(key, JsonPrimitive(value))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
put("role", JsonPrimitive(options.role))
|
||||
if (options.scopes.isNotEmpty()) put("scopes", JsonArray(options.scopes.map(::JsonPrimitive)))
|
||||
authJson?.let { put("auth", it) }
|
||||
deviceJson?.let { put("device", it) }
|
||||
put("locale", JsonPrimitive(locale))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleMessage(text: String) {
|
||||
|
||||
@@ -309,11 +309,10 @@ fun SettingsSheet(viewModel: MainViewModel) {
|
||||
add("IP: ${gateway.host}:${gateway.port}")
|
||||
gateway.lanHost?.let { add("LAN: $it") }
|
||||
gateway.tailnetDns?.let { add("Tailnet: $it") }
|
||||
if (gateway.gatewayPort != null || gateway.bridgePort != null || gateway.canvasPort != null) {
|
||||
val gw = gateway.gatewayPort?.toString() ?: "—"
|
||||
val br = (gateway.bridgePort ?: gateway.port).toString()
|
||||
if (gateway.gatewayPort != null || gateway.canvasPort != null) {
|
||||
val gw = (gateway.gatewayPort ?: gateway.port).toString()
|
||||
val canvas = gateway.canvasPort?.toString() ?: "—"
|
||||
add("Ports: gw $gw · bridge $br · canvas $canvas")
|
||||
add("Ports: gw $gw · canvas $canvas")
|
||||
}
|
||||
}
|
||||
ListItem(
|
||||
|
||||
@@ -117,7 +117,7 @@ final class DevicePairingApprovalPrompter {
|
||||
|
||||
private func updatePendingCounts() {
|
||||
self.pendingCount = self.queue.count
|
||||
self.pendingRepairCount = self.queue.filter { $0.isRepair == true }.count
|
||||
self.pendingRepairCount = self.queue.count(where: { $0.isRepair == true })
|
||||
}
|
||||
|
||||
private func presentNextIfNeeded() {
|
||||
@@ -270,7 +270,8 @@ final class DevicePairingApprovalPrompter {
|
||||
let req = try GatewayPayloadDecoding.decode(payload, as: PendingRequest.self)
|
||||
self.enqueue(req)
|
||||
} catch {
|
||||
self.logger.error("failed to decode device pairing request: \(error.localizedDescription, privacy: .public)")
|
||||
self.logger
|
||||
.error("failed to decode device pairing request: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
case let .event(evt) where evt.event == "device.pair.resolved":
|
||||
guard let payload = evt.payload else { return }
|
||||
@@ -278,7 +279,9 @@ final class DevicePairingApprovalPrompter {
|
||||
let resolved = try GatewayPayloadDecoding.decode(payload, as: PairingResolvedEvent.self)
|
||||
self.handleResolved(resolved)
|
||||
} catch {
|
||||
self.logger.error("failed to decode device pairing resolution: \(error.localizedDescription, privacy: .public)")
|
||||
self.logger
|
||||
.error(
|
||||
"failed to decode device pairing resolution: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
default:
|
||||
break
|
||||
@@ -293,11 +296,15 @@ final class DevicePairingApprovalPrompter {
|
||||
}
|
||||
|
||||
private func handleResolved(_ resolved: PairingResolvedEvent) {
|
||||
let resolution = resolved.decision == PairingResolution.approved.rawValue ? PairingResolution.approved : .rejected
|
||||
let resolution = resolved.decision == PairingResolution.approved.rawValue ? PairingResolution
|
||||
.approved : .rejected
|
||||
if let activeRequestId, activeRequestId == resolved.requestId {
|
||||
self.resolvedByRequestId.insert(resolved.requestId)
|
||||
self.endActiveAlert()
|
||||
self.logger.info("device pairing resolved while active requestId=\(resolved.requestId, privacy: .public) decision=\(resolution.rawValue, privacy: .public)")
|
||||
let decision = resolution.rawValue
|
||||
self.logger.info(
|
||||
"device pairing resolved while active requestId=\(resolved.requestId, privacy: .public) " +
|
||||
"decision=\(decision, privacy: .public)")
|
||||
return
|
||||
}
|
||||
self.queue.removeAll { $0.requestId == resolved.requestId }
|
||||
@@ -314,7 +321,7 @@ final class DevicePairingApprovalPrompter {
|
||||
lines.append("Role: \(role)")
|
||||
}
|
||||
if let scopes = req.scopes, !scopes.isEmpty {
|
||||
lines.append("Scopes: \(scopes.joined(separator: \", \"))")
|
||||
lines.append("Scopes: \(scopes.joined(separator: ", "))")
|
||||
}
|
||||
if let remoteIp = req.remoteIp {
|
||||
lines.append("IP: \(remoteIp)")
|
||||
|
||||
@@ -53,11 +53,11 @@ enum ExecApprovalQuickMode: String, CaseIterable, Identifiable {
|
||||
static func from(security: ExecSecurity, ask: ExecAsk) -> ExecApprovalQuickMode {
|
||||
switch security {
|
||||
case .deny:
|
||||
return .deny
|
||||
.deny
|
||||
case .full:
|
||||
return .allow
|
||||
.allow
|
||||
case .allowlist:
|
||||
return .ask
|
||||
.ask
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,8 @@ struct ExecApprovalsAgent: Codable {
|
||||
var allowlist: [ExecAllowlistEntry]?
|
||||
|
||||
var isEmpty: Bool {
|
||||
security == nil && ask == nil && askFallback == nil && autoAllowSkills == nil && (allowlist?.isEmpty ?? true)
|
||||
self.security == nil && self.ask == nil && self.askFallback == nil && self
|
||||
.autoAllowSkills == nil && (self.allowlist?.isEmpty ?? true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,8 +468,8 @@ struct ExecCommandResolution: Sendable {
|
||||
command: [String],
|
||||
rawCommand: String?,
|
||||
cwd: String?,
|
||||
env: [String: String]?
|
||||
) -> ExecCommandResolution? {
|
||||
env: [String: String]?) -> ExecCommandResolution?
|
||||
{
|
||||
let trimmedRaw = rawCommand?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
if !trimmedRaw.isEmpty, let token = self.parseFirstToken(trimmedRaw) {
|
||||
return self.resolveExecutable(rawExecutable: token, cwd: cwd, env: env)
|
||||
@@ -486,8 +487,8 @@ struct ExecCommandResolution: Sendable {
|
||||
private static func resolveExecutable(
|
||||
rawExecutable: String,
|
||||
cwd: String?,
|
||||
env: [String: String]?
|
||||
) -> ExecCommandResolution? {
|
||||
env: [String: String]?) -> ExecCommandResolution?
|
||||
{
|
||||
let expanded = rawExecutable.hasPrefix("~") ? (rawExecutable as NSString).expandingTildeInPath : rawExecutable
|
||||
let hasPathSeparator = expanded.contains("/") || expanded.contains("\\")
|
||||
let resolvedPath: String? = {
|
||||
@@ -503,7 +504,11 @@ struct ExecCommandResolution: Sendable {
|
||||
return CommandResolver.findExecutable(named: expanded, searchPaths: searchPaths)
|
||||
}()
|
||||
let name = resolvedPath.map { URL(fileURLWithPath: $0).lastPathComponent } ?? expanded
|
||||
return ExecCommandResolution(rawExecutable: expanded, resolvedPath: resolvedPath, executableName: name, cwd: cwd)
|
||||
return ExecCommandResolution(
|
||||
rawExecutable: expanded,
|
||||
resolvedPath: resolvedPath,
|
||||
executableName: name,
|
||||
cwd: cwd)
|
||||
}
|
||||
|
||||
private static func parseFirstToken(_ command: String) -> String? {
|
||||
@@ -624,7 +629,7 @@ struct ExecEventPayload: Codable, Sendable {
|
||||
var output: String?
|
||||
var reason: String?
|
||||
|
||||
static func truncateOutput(_ raw: String, maxChars: Int = 20_000) -> String? {
|
||||
static func truncateOutput(_ raw: String, maxChars: Int = 20000) -> String? {
|
||||
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty else { return nil }
|
||||
if trimmed.count <= maxChars { return trimmed }
|
||||
|
||||
@@ -51,7 +51,7 @@ final class ExecApprovalsGatewayPrompter {
|
||||
"id": AnyCodable(request.id),
|
||||
"decision": AnyCodable(decision.rawValue),
|
||||
],
|
||||
timeoutMs: 10_000)
|
||||
timeoutMs: 10000)
|
||||
} catch {
|
||||
self.logger.error("exec approval handling failed \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ private struct ExecApprovalSocketDecision: Codable {
|
||||
var decision: ExecApprovalDecision
|
||||
}
|
||||
|
||||
fileprivate struct ExecHostSocketRequest: Codable {
|
||||
private struct ExecHostSocketRequest: Codable {
|
||||
var type: String
|
||||
var id: String
|
||||
var nonce: String
|
||||
@@ -37,7 +37,7 @@ fileprivate struct ExecHostSocketRequest: Codable {
|
||||
var requestJson: String
|
||||
}
|
||||
|
||||
fileprivate struct ExecHostRequest: Codable {
|
||||
private struct ExecHostRequest: Codable {
|
||||
var command: [String]
|
||||
var rawCommand: String?
|
||||
var cwd: String?
|
||||
@@ -48,7 +48,7 @@ fileprivate struct ExecHostRequest: Codable {
|
||||
var sessionKey: String?
|
||||
}
|
||||
|
||||
fileprivate struct ExecHostRunResult: Codable {
|
||||
private struct ExecHostRunResult: Codable {
|
||||
var exitCode: Int?
|
||||
var timedOut: Bool
|
||||
var success: Bool
|
||||
@@ -57,13 +57,13 @@ fileprivate struct ExecHostRunResult: Codable {
|
||||
var error: String?
|
||||
}
|
||||
|
||||
fileprivate struct ExecHostError: Codable {
|
||||
private struct ExecHostError: Codable {
|
||||
var code: String
|
||||
var message: String
|
||||
var reason: String?
|
||||
}
|
||||
|
||||
fileprivate struct ExecHostResponse: Codable {
|
||||
private struct ExecHostResponse: Codable {
|
||||
var type: String
|
||||
var id: String
|
||||
var ok: Bool
|
||||
@@ -74,14 +74,14 @@ fileprivate struct ExecHostResponse: Codable {
|
||||
enum ExecApprovalsSocketClient {
|
||||
private struct TimeoutError: LocalizedError {
|
||||
var message: String
|
||||
var errorDescription: String? { message }
|
||||
var errorDescription: String? { self.message }
|
||||
}
|
||||
|
||||
static func requestDecision(
|
||||
socketPath: String,
|
||||
token: String,
|
||||
request: ExecApprovalPromptRequest,
|
||||
timeoutMs: Int = 15_000) async -> ExecApprovalDecision?
|
||||
timeoutMs: Int = 15000) async -> ExecApprovalDecision?
|
||||
{
|
||||
let trimmedPath = socketPath.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let trimmedToken = token.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
@@ -254,7 +254,7 @@ enum ExecApprovalsPromptPresenter {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
fileprivate enum ExecHostExecutor {
|
||||
private enum ExecHostExecutor {
|
||||
private static let blockedEnvKeys: Set<String> = [
|
||||
"PATH",
|
||||
"NODE_OPTIONS",
|
||||
@@ -313,12 +313,15 @@ fileprivate enum ExecHostExecutor {
|
||||
id: UUID().uuidString,
|
||||
ok: false,
|
||||
payload: nil,
|
||||
error: ExecHostError(code: "UNAVAILABLE", message: "SYSTEM_RUN_DISABLED: security=deny", reason: "security=deny"))
|
||||
error: ExecHostError(
|
||||
code: "UNAVAILABLE",
|
||||
message: "SYSTEM_RUN_DISABLED: security=deny",
|
||||
reason: "security=deny"))
|
||||
}
|
||||
|
||||
let requiresAsk: Bool = {
|
||||
if ask == .always { return true }
|
||||
if ask == .onMiss && security == .allowlist && allowlistMatch == nil && !skillAllow { return true }
|
||||
if ask == .onMiss, security == .allowlist, allowlistMatch == nil, !skillAllow { return true }
|
||||
return false
|
||||
}()
|
||||
|
||||
@@ -341,7 +344,10 @@ fileprivate enum ExecHostExecutor {
|
||||
id: UUID().uuidString,
|
||||
ok: false,
|
||||
payload: nil,
|
||||
error: ExecHostError(code: "UNAVAILABLE", message: "SYSTEM_RUN_DENIED: user denied", reason: "user-denied"))
|
||||
error: ExecHostError(
|
||||
code: "UNAVAILABLE",
|
||||
message: "SYSTEM_RUN_DENIED: user denied",
|
||||
reason: "user-denied"))
|
||||
case .allowAlways:
|
||||
approvedByAsk = true
|
||||
if security == .allowlist {
|
||||
@@ -355,13 +361,16 @@ fileprivate enum ExecHostExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
if security == .allowlist && allowlistMatch == nil && !skillAllow && !approvedByAsk {
|
||||
if security == .allowlist, allowlistMatch == nil, !skillAllow, !approvedByAsk {
|
||||
return ExecHostResponse(
|
||||
type: "exec-res",
|
||||
id: UUID().uuidString,
|
||||
ok: false,
|
||||
payload: nil,
|
||||
error: ExecHostError(code: "UNAVAILABLE", message: "SYSTEM_RUN_DENIED: allowlist miss", reason: "allowlist-miss"))
|
||||
error: ExecHostError(
|
||||
code: "UNAVAILABLE",
|
||||
message: "SYSTEM_RUN_DENIED: allowlist miss",
|
||||
reason: "allowlist-miss"))
|
||||
}
|
||||
|
||||
if let match = allowlistMatch {
|
||||
@@ -381,7 +390,10 @@ fileprivate enum ExecHostExecutor {
|
||||
id: UUID().uuidString,
|
||||
ok: false,
|
||||
payload: nil,
|
||||
error: ExecHostError(code: "UNAVAILABLE", message: "PERMISSION_MISSING: screenRecording", reason: "permission:screenRecording"))
|
||||
error: ExecHostError(
|
||||
code: "UNAVAILABLE",
|
||||
message: "PERMISSION_MISSING: screenRecording",
|
||||
reason: "permission:screenRecording"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,7 +633,7 @@ private final class ExecApprovalsSocketServer: @unchecked Sendable {
|
||||
|
||||
private func handleExecRequest(_ request: ExecHostSocketRequest) async -> ExecHostResponse {
|
||||
let nowMs = Int(Date().timeIntervalSince1970 * 1000)
|
||||
if abs(nowMs - request.ts) > 10_000 {
|
||||
if abs(nowMs - request.ts) > 10000 {
|
||||
return ExecHostResponse(
|
||||
type: "exec-res",
|
||||
id: request.id,
|
||||
|
||||
@@ -120,8 +120,8 @@ enum GatewayEnvironment {
|
||||
kind: .missingNode,
|
||||
nodeVersion: nil,
|
||||
gatewayVersion: nil,
|
||||
requiredGateway: expectedString,
|
||||
message: RuntimeLocator.describeFailure(err))
|
||||
requiredGateway: expectedString,
|
||||
message: RuntimeLocator.describeFailure(err))
|
||||
case let .success(runtime):
|
||||
let gatewayBin = CommandResolver.clawdbotExecutable()
|
||||
|
||||
@@ -237,11 +237,10 @@ enum GatewayEnvironment {
|
||||
static func installGlobal(versionString: String?, statusHandler: @escaping @Sendable (String) -> Void) async {
|
||||
let preferred = CommandResolver.preferredPaths().joined(separator: ":")
|
||||
let trimmed = versionString?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let target: String
|
||||
if let trimmed, !trimmed.isEmpty {
|
||||
target = trimmed
|
||||
let target: String = if let trimmed, !trimmed.isEmpty {
|
||||
trimmed
|
||||
} else {
|
||||
target = "latest"
|
||||
"latest"
|
||||
}
|
||||
let npm = CommandResolver.findExecutable(named: "npm")
|
||||
let pnpm = CommandResolver.findExecutable(named: "pnpm")
|
||||
|
||||
@@ -482,12 +482,12 @@ actor MacNodeRuntime {
|
||||
|
||||
let requiresAsk: Bool = {
|
||||
if ask == .always { return true }
|
||||
if ask == .onMiss && security == .allowlist && allowlistMatch == nil && !skillAllow { return true }
|
||||
if ask == .onMiss, security == .allowlist, allowlistMatch == nil, !skillAllow { return true }
|
||||
return false
|
||||
}()
|
||||
|
||||
let approvedByAsk = params.approved == true
|
||||
if requiresAsk && !approvedByAsk {
|
||||
if requiresAsk, !approvedByAsk {
|
||||
await self.emitExecEvent(
|
||||
"exec.denied",
|
||||
payload: ExecEventPayload(
|
||||
@@ -502,7 +502,7 @@ actor MacNodeRuntime {
|
||||
message: "SYSTEM_RUN_DENIED: approval required")
|
||||
}
|
||||
|
||||
if security == .allowlist && allowlistMatch == nil && !skillAllow && !approvedByAsk {
|
||||
if security == .allowlist, allowlistMatch == nil, !skillAllow, !approvedByAsk {
|
||||
await self.emitExecEvent(
|
||||
"exec.denied",
|
||||
payload: ExecEventPayload(
|
||||
@@ -558,7 +558,7 @@ actor MacNodeRuntime {
|
||||
env: env,
|
||||
timeout: timeoutSec)
|
||||
let combined = [result.stdout, result.stderr, result.errorMessage]
|
||||
.compactMap { $0 }
|
||||
.compactMap(\.self)
|
||||
.filter { !$0.isEmpty }
|
||||
.joined(separator: "\n")
|
||||
await self.emitExecEvent(
|
||||
@@ -668,7 +668,7 @@ actor MacNodeRuntime {
|
||||
let resolvedPath = (socketPath?.isEmpty == false)
|
||||
? socketPath!
|
||||
: current.socket?.path?.trimmingCharacters(in: .whitespacesAndNewlines) ??
|
||||
ExecApprovalsStore.socketPath()
|
||||
ExecApprovalsStore.socketPath()
|
||||
let resolvedToken = (token?.isEmpty == false)
|
||||
? token!
|
||||
: current.socket?.token?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
|
||||
@@ -57,5 +57,4 @@ final class LiveMacNodeRuntimeMainActorServices: MacNodeRuntimeMainActorServices
|
||||
maxAgeMs: maxAgeMs,
|
||||
timeoutMs: timeoutMs)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -168,7 +168,6 @@ struct SessionMenuPreviewView: View {
|
||||
.font(.caption)
|
||||
.foregroundStyle(self.primaryColor)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum SessionMenuPreviewLoader {
|
||||
@@ -182,7 +181,7 @@ enum SessionMenuPreviewLoader {
|
||||
|
||||
static func load(sessionKey: String, maxItems: Int) async -> SessionMenuPreviewSnapshot {
|
||||
if let cached = await SessionPreviewCache.shared.cachedItems(for: sessionKey, maxAge: cacheMaxAgeSeconds) {
|
||||
return Self.snapshot(from: cached)
|
||||
return self.snapshot(from: cached)
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
@@ -81,8 +81,9 @@ struct SystemRunSettingsView: View {
|
||||
.pickerStyle(.menu)
|
||||
|
||||
Text(self.model.isDefaultsScope
|
||||
? "Defaults apply when an agent has no overrides. Ask controls prompt behavior; fallback is used when no companion UI is reachable."
|
||||
: "Security controls whether system.run can execute on this Mac when paired as a node. Ask controls prompt behavior; fallback is used when no companion UI is reachable.")
|
||||
? "Defaults apply when an agent has no overrides. Ask controls prompt behavior; fallback is used when no companion UI is reachable."
|
||||
:
|
||||
"Security controls whether system.run can execute on this Mac when paired as a node. Ask controls prompt behavior; fallback is used when no companion UI is reachable.")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.tertiary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
@@ -51,7 +51,6 @@ struct UsageMenuLabelView: View {
|
||||
.padding(.leading, 2)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.leading, self.paddingLeading)
|
||||
|
||||
Reference in New Issue
Block a user