






















@@ -2,43 +2,102 @@ import Foundation
22import OpenClawProtocol
3344extension ChannelsStore {
5-func loadConfigSchema() async {
6-guard !self.configSchemaLoading else { return }
5+func loadConfigSchema(force: Bool = false) async {
6+let sourceKey = self.currentConfigCacheSourceKey()
7+self.resetConfigSchemaCacheIfSourceChanged(sourceKey)
8+if !force, self.configSchema != nil {
9+return
10+}
11+guard !self.queueConfigSchemaReloadIfLoading(sourceKey: sourceKey, force: force) else { return }
712self.configSchemaLoading = true
8-defer { self.configSchemaLoading = false }
13+self.configSchemaLoadingSourceKey = sourceKey
14+defer {
15+self.configSchemaLoading = false
16+self.configSchemaLoadingSourceKey = nil
17+}
91810-do {
11-let res: ConfigSchemaResponse = try await GatewayConnection.shared.requestDecoded(
12- method: .configSchema,
13- params: nil,
14- timeoutMs: 8000)
15-let schemaValue = res.schema.foundationValue
16-self.configSchema = ConfigSchemaNode(raw: schemaValue)
17-let hintValues = res.uihints.mapValues { $0.foundationValue }
18-self.configUiHints = decodeUiHints(hintValues)
19-} catch {
20-self.configStatus = error.localizedDescription
19+var requestSourceKey = sourceKey
20+21+while true {
22+self.configSchemaLoadingSourceKey = requestSourceKey
23+do {
24+let res: ConfigSchemaResponse = try await GatewayConnection.shared.requestDecoded(
25+ method: .configSchema,
26+ params: nil,
27+ timeoutMs: 8000)
28+self.applyConfigSchemaResponse(res, sourceKey: requestSourceKey)
29+} catch {
30+self.configStatus = error.localizedDescription
31+}
32+33+guard self.configSchemaReloadPending else { break }
34+self.configSchemaReloadPending = false
35+ requestSourceKey = self.currentConfigCacheSourceKey()
36+self.resetConfigSchemaCacheIfSourceChanged(requestSourceKey)
2137}
2238}
233924-func loadConfig() async {
25-do {
26-let snap: ConfigSnapshot = try await GatewayConnection.shared.requestDecoded(
27- method: .configGet,
28- params: nil,
29- timeoutMs: 10000)
30-self.configStatus = snap.valid == false
31-? "Config invalid; fix it in ~/.openclaw/openclaw.json."
32-: nil
33-self.configRoot = snap.config?.mapValues { $0.foundationValue } ?? [:]
34-self.configDraft = cloneConfigValue(self.configRoot) as? [String: Any] ?? self.configRoot
35-self.configDirty = false
36-self.configLoaded = true
37-38-self.applyUIConfig(snap)
39-} catch {
40-self.configStatus = error.localizedDescription
40+func loadConfig(force: Bool = true) async {
41+let sourceKey = self.currentConfigCacheSourceKey()
42+self.resetConfigCacheIfSourceChanged(sourceKey)
43+if !force, self.configLoaded {
44+return
45+}
46+guard !self.queueConfigReloadIfLoading(sourceKey: sourceKey, force: force) else { return }
47+self.configLoading = true
48+self.configLoadingSourceKey = sourceKey
49+defer {
50+self.configLoading = false
51+self.configLoadingSourceKey = nil
4152}
53+54+var requestForce = force
55+var requestSourceKey = sourceKey
56+57+while true {
58+self.configLoadingSourceKey = requestSourceKey
59+do {
60+let snap: ConfigSnapshot = try await GatewayConnection.shared.requestDecoded(
61+ method: .configGet,
62+ params: nil,
63+ timeoutMs: 10000)
64+self.applyConfigSnapshot(snap, sourceKey: requestSourceKey, force: requestForce)
65+} catch {
66+self.configStatus = error.localizedDescription
67+}
68+69+guard self.configForceReloadPending else { break }
70+self.configForceReloadPending = false
71+ requestForce = true
72+ requestSourceKey = self.currentConfigCacheSourceKey()
73+self.resetConfigCacheIfSourceChanged(requestSourceKey)
74+}
75+}
76+77+func applyConfigSnapshot(_ snap: ConfigSnapshot, sourceKey: String, force: Bool) {
78+guard self.configSourceKey == sourceKey else { return }
79+guard force || !self.configDirty else { return }
80+81+self.configStatus = snap.valid == false
82+? "Config invalid; fix it in ~/.openclaw/openclaw.json."
83+: nil
84+self.configRoot = snap.config?.mapValues { $0.foundationValue } ?? [:]
85+self.configDraft = cloneConfigValue(self.configRoot) as? [String: Any] ?? self.configRoot
86+self.configDirty = false
87+self.configLoaded = true
88+self.configSourceKey = sourceKey
89+90+self.applyUIConfig(snap)
91+}
92+93+func applyConfigSchemaResponse(_ res: ConfigSchemaResponse, sourceKey: String) {
94+guard self.configSchemaSourceKey == sourceKey else { return }
95+96+let schemaValue = res.schema.foundationValue
97+self.configSchema = ConfigSchemaNode(raw: schemaValue)
98+let hintValues = res.uihints.mapValues { $0.foundationValue }
99+self.configUiHints = decodeUiHints(hintValues)
100+self.configSchemaSourceKey = sourceKey
42101}
4310244103private func applyUIConfig(_ snap: ConfigSnapshot) {
@@ -85,7 +144,75 @@ extension ChannelsStore {
85144}
8614587146func reloadConfigDraft() async {
88-await self.loadConfig()
147+await self.loadConfig(force: true)
148+}
149+150+func resetConfigSchemaCacheIfSourceChanged(_ sourceKey: String) {
151+guard let cachedSourceKey = self.configSchemaSourceKey else {
152+self.configSchemaSourceKey = sourceKey
153+return
154+}
155+guard cachedSourceKey != sourceKey else { return }
156+self.configSchema = nil
157+self.configUiHints = [:]
158+self.configSchemaSourceKey = sourceKey
159+}
160+161+func resetConfigCacheIfSourceChanged(_ sourceKey: String) {
162+guard let cachedSourceKey = self.configSourceKey else {
163+self.configSourceKey = sourceKey
164+return
165+}
166+guard cachedSourceKey != sourceKey else { return }
167+self.configRoot = [:]
168+self.configDraft = [:]
169+self.configDirty = false
170+self.configLoaded = false
171+self.configSourceKey = sourceKey
172+}
173+174+func queueConfigReloadIfLoading(sourceKey: String, force: Bool) -> Bool {
175+guard self.configLoading else { return false }
176+if force || self.configLoadingSourceKey != sourceKey {
177+self.configForceReloadPending = true
178+}
179+return true
180+}
181+182+func queueConfigSchemaReloadIfLoading(sourceKey: String, force: Bool) -> Bool {
183+guard self.configSchemaLoading else { return false }
184+if force || self.configSchemaLoadingSourceKey != sourceKey {
185+self.configSchemaReloadPending = true
186+}
187+return true
188+}
189+190+private func currentConfigCacheSourceKey() -> String {
191+let root = OpenClawConfigFile.loadDict()
192+let settings = CommandResolver.connectionSettings(configRoot: root)
193+let env = ProcessInfo.processInfo.environment
194+return [
195+"mode:\(settings.mode.rawValue)",
196+"target:\(settings.target)",
197+"identity:\(settings.identity)",
198+"project:\(settings.projectRoot)",
199+"cli:\(settings.cliPath)",
200+"port:\(GatewayEnvironment.gatewayPort())",
201+"gateway:\(Self.configFingerprint(root["gateway"]))",
202+"token:\(Self.configFingerprint(env["OPENCLAW_GATEWAY_TOKEN"]))",
203+"password:\(Self.configFingerprint(env["OPENCLAW_GATEWAY_PASSWORD"]))",
204+].joined(separator: "|")
205+}
206+207+private static func configFingerprint(_ value: Any?) -> String {
208+guard let value else { return "nil" }
209+if JSONSerialization.isValidJSONObject(value),
210+let data = try? JSONSerialization.data(withJSONObject: value, options: [.sortedKeys])
211+{
212+return "\(data.count):\(data.hashValue)"
213+}
214+let text = String(describing: value)
215+return "\(text.count):\(text.hashValue)"
89216}
90217}
91218此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。