import Foundation final class ConfigFileWatcher: @unchecked Sendable { private let url: URL private let watchedDir: URL private let targetPath: String private let targetName: String private let watcher: CoalescingFSEventsWatcher init(url: URL, onChange: @escaping () -> Void) { self.url = url self.watchedDir = url.deletingLastPathComponent() self.targetPath = url.path self.targetName = url.lastPathComponent let watchedDirPath = self.watchedDir.path let targetPath = self.targetPath let targetName = self.targetName self.watcher = CoalescingFSEventsWatcher( paths: [watchedDirPath], queueLabel: "ai.openclaw.configwatcher", shouldNotify: { _, eventPaths in guard let eventPaths else { return true } let paths = unsafeBitCast(eventPaths, to: NSArray.self) for case let path as String in paths { if path == targetPath { return true } if path.hasSuffix("/\(targetName)") { return true } if path == watchedDirPath { return true } } return false }, onChange: onChange) } deinit { self.stop() } func start() { self.watcher.start() } func stop() { self.watcher.stop() } }