refactor(macos): drop duplicate AnyCodable
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import ClawdbotProtocol
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// Lightweight `Codable` wrapper that round-trips heterogeneous JSON payloads.
|
||||
/// Marked `@unchecked Sendable` because it can hold reference types.
|
||||
struct AnyCodable: Codable, @unchecked Sendable {
|
||||
let value: Any
|
||||
|
||||
init(_ value: Any) { self.value = value }
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
if let intVal = try? container.decode(Int.self) { self.value = intVal; return }
|
||||
if let doubleVal = try? container.decode(Double.self) { self.value = doubleVal; return }
|
||||
if let boolVal = try? container.decode(Bool.self) { self.value = boolVal; return }
|
||||
if let stringVal = try? container.decode(String.self) { self.value = stringVal; return }
|
||||
if container.decodeNil() { self.value = NSNull(); return }
|
||||
if let dict = try? container.decode([String: AnyCodable].self) { self.value = dict; return }
|
||||
if let array = try? container.decode([AnyCodable].self) { self.value = array; return }
|
||||
throw DecodingError.dataCorruptedError(
|
||||
in: container,
|
||||
debugDescription: "Unsupported type")
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
switch self.value {
|
||||
case let intVal as Int: try container.encode(intVal)
|
||||
case let doubleVal as Double: try container.encode(doubleVal)
|
||||
case let boolVal as Bool: try container.encode(boolVal)
|
||||
case let stringVal as String: try container.encode(stringVal)
|
||||
case is NSNull: try container.encodeNil()
|
||||
case let dict as [String: AnyCodable]: try container.encode(dict)
|
||||
case let array as [AnyCodable]: try container.encode(array)
|
||||
case let dict as [String: Any]:
|
||||
try container.encode(dict.mapValues { AnyCodable($0) })
|
||||
case let array as [Any]:
|
||||
try container.encode(array.map { AnyCodable($0) })
|
||||
case let dict as NSDictionary:
|
||||
var converted: [String: AnyCodable] = [:]
|
||||
for (k, v) in dict {
|
||||
guard let key = k as? String else { continue }
|
||||
converted[key] = AnyCodable(v)
|
||||
}
|
||||
try container.encode(converted)
|
||||
case let array as NSArray:
|
||||
try container.encode(array.map { AnyCodable($0) })
|
||||
default:
|
||||
let context = EncodingError.Context(
|
||||
codingPath: encoder.codingPath,
|
||||
debugDescription: "Unsupported type")
|
||||
throw EncodingError.invalidValue(self.value, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,7 +229,7 @@ actor BridgeServer {
|
||||
error: BridgeRPCError(code: "FORBIDDEN", message: "Method not allowed"))
|
||||
}
|
||||
|
||||
let params: [String: AnyCodable]?
|
||||
let params: [String: ClawdbotProtocol.AnyCodable]?
|
||||
if let json = req.paramsJSON?.trimmingCharacters(in: .whitespacesAndNewlines), !json.isEmpty {
|
||||
guard let data = json.data(using: .utf8) else {
|
||||
return BridgeRPCResponse(
|
||||
@@ -238,7 +238,7 @@ actor BridgeServer {
|
||||
error: BridgeRPCError(code: "INVALID_REQUEST", message: "paramsJSON not UTF-8"))
|
||||
}
|
||||
do {
|
||||
params = try JSONDecoder().decode([String: AnyCodable].self, from: data)
|
||||
params = try JSONDecoder().decode([String: ClawdbotProtocol.AnyCodable].self, from: data)
|
||||
} catch {
|
||||
return BridgeRPCResponse(
|
||||
id: req.id,
|
||||
@@ -360,16 +360,16 @@ actor BridgeServer {
|
||||
"reason \(reason)",
|
||||
].compactMap(\.self).joined(separator: " · ")
|
||||
|
||||
var params: [String: AnyCodable] = [
|
||||
"text": AnyCodable(summary),
|
||||
"instanceId": AnyCodable(nodeId),
|
||||
"host": AnyCodable(host),
|
||||
"mode": AnyCodable("node"),
|
||||
"reason": AnyCodable(reason),
|
||||
"tags": AnyCodable(tags),
|
||||
var params: [String: ClawdbotProtocol.AnyCodable] = [
|
||||
"text": ClawdbotProtocol.AnyCodable(summary),
|
||||
"instanceId": ClawdbotProtocol.AnyCodable(nodeId),
|
||||
"host": ClawdbotProtocol.AnyCodable(host),
|
||||
"mode": ClawdbotProtocol.AnyCodable("node"),
|
||||
"reason": ClawdbotProtocol.AnyCodable(reason),
|
||||
"tags": ClawdbotProtocol.AnyCodable(tags),
|
||||
]
|
||||
if let ip { params["ip"] = AnyCodable(ip) }
|
||||
if let version { params["version"] = AnyCodable(version) }
|
||||
if let ip { params["ip"] = ClawdbotProtocol.AnyCodable(ip) }
|
||||
if let version { params["version"] = ClawdbotProtocol.AnyCodable(version) }
|
||||
await GatewayConnection.shared.sendSystemEvent(params)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ClawdbotProtocol
|
||||
import Foundation
|
||||
|
||||
enum ClawdbotConfigFile {
|
||||
|
||||
@@ -3,9 +3,9 @@ import Foundation
|
||||
enum ClawdbotEnv {
|
||||
static func path(_ key: String) -> String? {
|
||||
// Normalize env overrides once so UI + file IO stay consistent.
|
||||
guard let value = ProcessInfo.processInfo.environment[key]?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!value.isEmpty
|
||||
guard let raw = getenv(key) else { return nil }
|
||||
let value = String(cString: raw).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !value.isEmpty
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ClawdbotProtocol
|
||||
import Foundation
|
||||
|
||||
enum ConfigStore {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ClawdbotProtocol
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ClawdbotProtocol
|
||||
import SwiftUI
|
||||
|
||||
struct CronJobEditor: View {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ClawdbotProtocol
|
||||
import Foundation
|
||||
|
||||
extension CronSettings {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import ClawdbotKit
|
||||
import ClawdbotProtocol
|
||||
import Foundation
|
||||
import Observation
|
||||
import SwiftUI
|
||||
@@ -53,7 +54,7 @@ final class WorkActivityStore {
|
||||
phase: String,
|
||||
name: String?,
|
||||
meta: String?,
|
||||
args: [String: AnyCodable]?)
|
||||
args: [String: ClawdbotProtocol.AnyCodable]?)
|
||||
{
|
||||
let toolKind = Self.mapToolKind(name)
|
||||
let label = Self.buildLabel(name: name, meta: meta, args: args)
|
||||
@@ -211,7 +212,7 @@ final class WorkActivityStore {
|
||||
private static func buildLabel(
|
||||
name: String?,
|
||||
meta: String?,
|
||||
args: [String: AnyCodable]?) -> String
|
||||
args: [String: ClawdbotProtocol.AnyCodable]?) -> String
|
||||
{
|
||||
let wrappedArgs = self.wrapToolArgs(args)
|
||||
let display = ToolDisplayRegistry.resolve(name: name ?? "tool", args: wrappedArgs, meta: meta)
|
||||
@@ -221,17 +222,17 @@ final class WorkActivityStore {
|
||||
return display.label
|
||||
}
|
||||
|
||||
private static func wrapToolArgs(_ args: [String: AnyCodable]?) -> ClawdbotKit.AnyCodable? {
|
||||
private static func wrapToolArgs(_ args: [String: ClawdbotProtocol.AnyCodable]?) -> ClawdbotKit.AnyCodable? {
|
||||
guard let args else { return nil }
|
||||
let converted: [String: Any] = args.mapValues { self.unwrapJSONValue($0.value) }
|
||||
return ClawdbotKit.AnyCodable(converted)
|
||||
}
|
||||
|
||||
private static func unwrapJSONValue(_ value: Any) -> Any {
|
||||
if let dict = value as? [String: AnyCodable] {
|
||||
if let dict = value as? [String: ClawdbotProtocol.AnyCodable] {
|
||||
return dict.mapValues { self.unwrapJSONValue($0.value) }
|
||||
}
|
||||
if let array = value as? [AnyCodable] {
|
||||
if let array = value as? [ClawdbotProtocol.AnyCodable] {
|
||||
return array.map { self.unwrapJSONValue($0.value) }
|
||||
}
|
||||
if let dict = value as? [String: Any] {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
import ClawdbotProtocol
|
||||
@testable import Clawdbot
|
||||
|
||||
@Suite
|
||||
@@ -15,7 +16,7 @@ struct AgentEventStoreTests {
|
||||
seq: 1,
|
||||
stream: "test",
|
||||
ts: 0,
|
||||
data: [:] as [String: AnyCodable],
|
||||
data: [:] as [String: ClawdbotProtocol.AnyCodable],
|
||||
summary: nil))
|
||||
#expect(store.events.count == 1)
|
||||
|
||||
@@ -32,7 +33,7 @@ struct AgentEventStoreTests {
|
||||
seq: i,
|
||||
stream: "test",
|
||||
ts: Double(i),
|
||||
data: [:] as [String: AnyCodable],
|
||||
data: [:] as [String: ClawdbotProtocol.AnyCodable],
|
||||
summary: nil))
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import Testing
|
||||
"null": NSNull(),
|
||||
]
|
||||
|
||||
let data = try JSONEncoder().encode(Clawdbot.AnyCodable(payload))
|
||||
let data = try JSONEncoder().encode(ClawdbotProtocol.AnyCodable(payload))
|
||||
let obj = try #require(JSONSerialization.jsonObject(with: data) as? [String: Any])
|
||||
|
||||
#expect(obj["tags"] as? [String] == ["node", "ios"])
|
||||
|
||||
@@ -35,7 +35,7 @@ struct CronJobEditorSmokeTests {
|
||||
thinking: "low",
|
||||
timeoutSeconds: 120,
|
||||
deliver: true,
|
||||
channel: "whatsapp",
|
||||
provider: "whatsapp",
|
||||
to: "+15551234567",
|
||||
bestEffortDeliver: true),
|
||||
isolation: CronIsolation(postToMainPrefix: "Cron"),
|
||||
|
||||
@@ -31,7 +31,7 @@ struct CronModelsTests {
|
||||
thinking: "low",
|
||||
timeoutSeconds: 15,
|
||||
deliver: true,
|
||||
channel: "whatsapp",
|
||||
provider: "whatsapp",
|
||||
to: "+15551234567",
|
||||
bestEffortDeliver: false)
|
||||
let data = try JSONEncoder().encode(payload)
|
||||
|
||||
@@ -170,7 +170,7 @@ import Testing
|
||||
let url = URL(string: "ws://example.invalid")!
|
||||
let cfg = ConfigSource(token: nil)
|
||||
let conn = GatewayConnection(
|
||||
configProvider: { (url, cfg.snapshotToken()) },
|
||||
configProvider: { (url: url, token: cfg.snapshotToken(), password: nil) },
|
||||
sessionBox: WebSocketSessionBox(session: session))
|
||||
|
||||
_ = try await conn.request(method: "status", params: nil)
|
||||
@@ -186,7 +186,7 @@ import Testing
|
||||
let url = URL(string: "ws://example.invalid")!
|
||||
let cfg = ConfigSource(token: "a")
|
||||
let conn = GatewayConnection(
|
||||
configProvider: { (url, cfg.snapshotToken()) },
|
||||
configProvider: { (url: url, token: cfg.snapshotToken(), password: nil) },
|
||||
sessionBox: WebSocketSessionBox(session: session))
|
||||
|
||||
_ = try await conn.request(method: "status", params: nil)
|
||||
@@ -203,7 +203,7 @@ import Testing
|
||||
let url = URL(string: "ws://example.invalid")!
|
||||
let cfg = ConfigSource(token: nil)
|
||||
let conn = GatewayConnection(
|
||||
configProvider: { (url, cfg.snapshotToken()) },
|
||||
configProvider: { (url: url, token: cfg.snapshotToken(), password: nil) },
|
||||
sessionBox: WebSocketSessionBox(session: session))
|
||||
|
||||
async let r1: Data = conn.request(method: "status", params: nil)
|
||||
@@ -218,7 +218,7 @@ import Testing
|
||||
let url = URL(string: "ws://example.invalid")!
|
||||
let cfg = ConfigSource(token: nil)
|
||||
let conn = GatewayConnection(
|
||||
configProvider: { (url, cfg.snapshotToken()) },
|
||||
configProvider: { (url: url, token: cfg.snapshotToken(), password: nil) },
|
||||
sessionBox: WebSocketSessionBox(session: session))
|
||||
|
||||
_ = try await conn.request(method: "status", params: nil)
|
||||
@@ -239,7 +239,7 @@ import Testing
|
||||
let url = URL(string: "ws://example.invalid")!
|
||||
let cfg = ConfigSource(token: nil)
|
||||
let conn = GatewayConnection(
|
||||
configProvider: { (url, cfg.snapshotToken()) },
|
||||
configProvider: { (url: url, token: cfg.snapshotToken(), password: nil) },
|
||||
sessionBox: WebSocketSessionBox(session: session))
|
||||
|
||||
let stream = await conn.subscribe(bufferingNewest: 10)
|
||||
|
||||
@@ -20,11 +20,27 @@ import Testing
|
||||
}
|
||||
|
||||
@Test func gatewayPortDefaultsAndRespectsOverride() {
|
||||
let envKey = "CLAWDBOT_CONFIG_PATH"
|
||||
let previousEnv = getenv(envKey).map { String(cString: $0) }
|
||||
let configPath = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("clawdbot-test-config-\(UUID().uuidString).json")
|
||||
.path
|
||||
setenv(envKey, configPath, 1)
|
||||
defer {
|
||||
if let previousEnv {
|
||||
setenv(envKey, previousEnv, 1)
|
||||
} else {
|
||||
unsetenv(envKey)
|
||||
}
|
||||
}
|
||||
|
||||
UserDefaults.standard.removeObject(forKey: "gatewayPort")
|
||||
defer { UserDefaults.standard.removeObject(forKey: "gatewayPort") }
|
||||
|
||||
let defaultPort = GatewayEnvironment.gatewayPort()
|
||||
#expect(defaultPort == 18789)
|
||||
|
||||
UserDefaults.standard.set(19999, forKey: "gatewayPort")
|
||||
defer { UserDefaults.standard.removeObject(forKey: "gatewayPort") }
|
||||
#expect(GatewayEnvironment.gatewayPort() == 19999)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import AppKit
|
||||
import Foundation
|
||||
import Testing
|
||||
import ClawdbotProtocol
|
||||
|
||||
@testable import Clawdbot
|
||||
|
||||
@@ -23,7 +24,7 @@ struct LowCoverageHelperTests {
|
||||
#expect(dict["list"]?.arrayValue?.count == 2)
|
||||
|
||||
let foundation = any.foundationValue as? [String: Any]
|
||||
#expect(foundation?["title"] as? String == "Hello")
|
||||
#expect((foundation?["title"] as? String) == "Hello")
|
||||
}
|
||||
|
||||
@Test func attributedStringStripsForegroundColor() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
import Testing
|
||||
import ClawdbotProtocol
|
||||
|
||||
@testable import Clawdbot
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ struct MenuSessionsInjectorTests {
|
||||
key: "main",
|
||||
kind: .direct,
|
||||
displayName: nil,
|
||||
surface: nil,
|
||||
provider: nil,
|
||||
subject: nil,
|
||||
room: nil,
|
||||
space: nil,
|
||||
@@ -47,7 +47,7 @@ struct MenuSessionsInjectorTests {
|
||||
key: "discord:group:alpha",
|
||||
kind: .group,
|
||||
displayName: nil,
|
||||
surface: nil,
|
||||
provider: nil,
|
||||
subject: nil,
|
||||
room: nil,
|
||||
space: nil,
|
||||
|
||||
@@ -28,7 +28,7 @@ struct SessionDataTests {
|
||||
key: "user@example.com",
|
||||
kind: .direct,
|
||||
displayName: nil,
|
||||
surface: nil,
|
||||
provider: nil,
|
||||
subject: nil,
|
||||
room: nil,
|
||||
space: nil,
|
||||
|
||||
@@ -45,7 +45,7 @@ struct SettingsViewSmokeTests {
|
||||
thinking: "low",
|
||||
timeoutSeconds: 30,
|
||||
deliver: true,
|
||||
channel: "sms",
|
||||
provider: "sms",
|
||||
to: "+15551234567",
|
||||
bestEffortDeliver: true),
|
||||
isolation: CronIsolation(postToMainPrefix: "[cron] "),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Testing
|
||||
import ClawdbotProtocol
|
||||
@testable import Clawdbot
|
||||
|
||||
@Suite(.serialized)
|
||||
|
||||
@@ -17,6 +17,6 @@ import Testing
|
||||
#expect(opts.thinking == "low")
|
||||
#expect(opts.deliver == true)
|
||||
#expect(opts.to == nil)
|
||||
#expect(opts.channel == .last)
|
||||
#expect(opts.provider == .last)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
import ClawdbotProtocol
|
||||
@testable import Clawdbot
|
||||
|
||||
@Suite
|
||||
|
||||
Reference in New Issue
Block a user