惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
有赞技术团队
有赞技术团队
博客园 - 三生石上(FineUI控件)
博客园_首页
博客园 - 【当耐特】
WordPress大学
WordPress大学
月光博客
月光博客
博客园 - 叶小钗
S
SegmentFault 最新的问题
雷峰网
雷峰网
H
Help Net Security
宝玉的分享
宝玉的分享
A
About on SuperTechFans
IT之家
IT之家
J
Java Code Geeks
Hugging Face - Blog
Hugging Face - Blog
D
DataBreaches.Net
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 聂微东
T
The Blog of Author Tim Ferriss
B
Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Y
Y Combinator Blog

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(gateway): widen native protocol compatibility · openc...
steipete · 2026-05-11 · via Recent Commits to openclaw:main

File tree

  • apps

    • android/app/src

      • main/java/ai/openclaw/app/gateway

      • test/java/ai/openclaw/app/gateway

    • macos

      • Sources/OpenClawMacCLI

      • Tests/OpenClawIPCTests

    • shared/OpenClawKit/Sources

      • OpenClawKit

      • OpenClawProtocol

  • docs

    • concepts

    • gateway

  • scripts

  • src

    • gateway

      • protocol

        • schema

    • tui

Original file line numberDiff line numberDiff line change

@@ -38,6 +38,7 @@ Docs: https://docs.openclaw.ai

3838

### Fixes

3939
4040

- Google/Gemini: normalize retired nested Gemini 3 Pro Preview ids while converting manifest catalog rows into emitted provider config, so `google/gemini-3.1-pro-preview` is used for testing instead of `google/gemini-3-pro-preview`.

41+

- Native apps: advertise the Gateway protocol compatibility range so chat and node sessions can connect to v3 gateways after additive v4 client updates.

4142

- Gateway: avoid synchronous restart-sentinel state probes during post-attach startup, preventing slow Windows or redirected state directories from blocking channel turns. Fixes #79264. Thanks @liyi58.

4243

- Agents/auth: update successful model auth profile status with one locked store write, reducing post-model reply latency from duplicate `auth-profiles.json` saves. Thanks @mcaxtr.

4344

- Agents/image: honor explicit `image` tool model overrides even when `agents.defaults.imageModel` is unset, restoring one-off vision calls for configured multimodal providers. Fixes #79341. Thanks @haumanto.

Original file line numberDiff line numberDiff line change

@@ -1,3 +1,4 @@

11

package ai.openclaw.app.gateway

22
33

const val GATEWAY_PROTOCOL_VERSION = 4

4+

const val GATEWAY_MIN_PROTOCOL_VERSION = 3

Original file line numberDiff line numberDiff line change

@@ -687,7 +687,7 @@ class GatewaySession(

687687

}

688688
689689

return buildJsonObject {

690-

put("minProtocol", JsonPrimitive(GATEWAY_PROTOCOL_VERSION))

690+

put("minProtocol", JsonPrimitive(GATEWAY_MIN_PROTOCOL_VERSION))

691691

put("maxProtocol", JsonPrimitive(GATEWAY_PROTOCOL_VERSION))

692692

put("client", clientObj)

693693

if (options.caps.isNotEmpty()) put("caps", JsonArray(options.caps.map(::JsonPrimitive)))

Original file line numberDiff line numberDiff line change

@@ -79,6 +79,50 @@ private data class InvokeScenarioResult(

7979

@RunWith(RobolectricTestRunner::class)

8080

@Config(sdk = [34])

8181

class GatewaySessionInvokeTest {

82+

@Test

83+

fun connect_advertisesCompatibleProtocolRange() =

84+

runBlocking {

85+

val json = testJson()

86+

val connected = CompletableDeferred<Unit>()

87+

val connectParams = CompletableDeferred<JsonObject>()

88+

val lastDisconnect = AtomicReference("")

89+

val server =

90+

startGatewayServer(json) { webSocket, id, method, frame ->

91+

when (method) {

92+

"connect" -> {

93+

if (!connectParams.isCompleted) {

94+

connectParams.complete(frame["params"]!!.jsonObject)

95+

}

96+

webSocket.send(connectResponseFrame(id))

97+

webSocket.close(1000, "done")

98+

}

99+

}

100+

}

101+
102+

val harness =

103+

createNodeHarness(

104+

connected = connected,

105+

lastDisconnect = lastDisconnect,

106+

) { GatewaySession.InvokeResult.ok("""{"handled":true}""") }

107+
108+

try {

109+

connectNodeSession(harness.session, server.port)

110+

awaitConnectedOrThrow(connected, lastDisconnect, server)

111+
112+

val params = withTimeout(TEST_TIMEOUT_MS) { connectParams.await() }

113+

assertEquals(

114+

GATEWAY_MIN_PROTOCOL_VERSION,

115+

params["minProtocol"]?.jsonPrimitive?.content?.toInt(),

116+

)

117+

assertEquals(

118+

GATEWAY_PROTOCOL_VERSION,

119+

params["maxProtocol"]?.jsonPrimitive?.content?.toInt(),

120+

)

121+

} finally {

122+

shutdownHarness(harness, server)

123+

}

124+

}

125+
82126

@Test

83127

fun connect_usesBootstrapTokenWhenSharedAndDeviceTokensAreAbsent() =

84128

runBlocking {

Original file line numberDiff line numberDiff line change

@@ -257,7 +257,7 @@ actor GatewayWizardClient {

257257

]

258258
259259

var params: [String: ProtoAnyCodable] = [

260-

"minProtocol": ProtoAnyCodable(GATEWAY_PROTOCOL_VERSION),

260+

"minProtocol": ProtoAnyCodable(GATEWAY_MIN_PROTOCOL_VERSION),

261261

"maxProtocol": ProtoAnyCodable(GATEWAY_PROTOCOL_VERSION),

262262

"client": ProtoAnyCodable(client),

263263

"caps": ProtoAnyCodable([String]()),

Original file line numberDiff line numberDiff line change

@@ -1,9 +1,30 @@

11

import Foundation

22

import OpenClawKit

3+

import OpenClawProtocol

34

import Testing

45

@testable import OpenClaw

56
67

struct GatewayChannelConnectTests {

8+

private final class ConnectParamsRecorder: @unchecked Sendable {

9+

private let lock = NSLock()

10+

private var params: [String: Any]?

11+
12+

func record(_ message: URLSessionWebSocketTask.Message) {

13+

guard let params = GatewayWebSocketTestSupport.connectRequestParams(from: message) else {

14+

return

15+

}

16+

self.lock.lock()

17+

self.params = params

18+

self.lock.unlock()

19+

}

20+
21+

func snapshot() -> [String: Any]? {

22+

self.lock.lock()

23+

defer { self.lock.unlock() }

24+

return self.params

25+

}

26+

}

27+
728

private final class TLSFailureSession: WebSocketSessioning, GatewayTLSFailureProviding, @unchecked Sendable {

829

private var failure: GatewayTLSValidationFailure?

930

@@ -87,6 +108,28 @@ struct GatewayChannelConnectTests {

87108

#expect(session.snapshotMakeCount() == 1)

88109

}

89110
111+

@Test func `connect advertises compatible protocol range`() async throws {

112+

let recorder = ConnectParamsRecorder()

113+

let session = GatewayTestWebSocketSession(

114+

taskFactory: {

115+

GatewayTestWebSocketTask(

116+

sendHook: { _, message, sendIndex in

117+

guard sendIndex == 0 else { return }

118+

recorder.record(message)

119+

})

120+

})

121+

let channel = try GatewayChannelActor(

122+

url: #require(URL(string: "ws://example.invalid")),

123+

token: nil,

124+

session: WebSocketSessionBox(session: session))

125+
126+

try await channel.connect()

127+
128+

let params = try #require(recorder.snapshot())

129+

#expect(params["minProtocol"] as? Int == GATEWAY_MIN_PROTOCOL_VERSION)

130+

#expect(params["maxProtocol"] as? Int == GATEWAY_PROTOCOL_VERSION)

131+

}

132+
90133

@Test func `concurrent connect shares failure`() async throws {

91134

let session = self.makeSession(response: .invalid(delayMs: 200))

92135

let channel = try GatewayChannelActor(

Original file line numberDiff line numberDiff line change

@@ -28,6 +28,14 @@ enum GatewayWebSocketTestSupport {

2828

return obj["id"] as? String

2929

}

3030
31+

static func connectRequestParams(from message: URLSessionWebSocketTask.Message) -> [String: Any]? {

32+

guard let obj = self.requestFrameObject(from: message) else { return nil }

33+

guard (obj["type"] as? String) == "req", (obj["method"] as? String) == "connect" else {

34+

return nil

35+

}

36+

return obj["params"] as? [String: Any]

37+

}

38+
3139

static func connectOkData(id: String) -> Data {

3240

let json = """

3341

{

@@ -74,6 +82,7 @@ enum GatewayWebSocketTestSupport {

7482

"id": "\(id)",

7583

"ok": false,

7684

"error": {

85+

"code": "INVALID_REQUEST",

7786

"message": "\(message)",

7887

"details": {

7988

"code": "\(detailCode)",

Original file line numberDiff line numberDiff line change

@@ -130,7 +130,9 @@ private func gatewayErrorDetails(_ error: ErrorShape?) -> [String: ProtoAnyCodab

130130

details.merge(nested) { _, nestedValue in nestedValue }

131131

}

132132

if let error {

133-

details["code"] = ProtoAnyCodable(error.code)

133+

if details["code"] == nil {

134+

details["code"] = ProtoAnyCodable(error.code)

135+

}

134136

details["message"] = ProtoAnyCodable(error.message)

135137

if let retryable = error.retryable {

136138

details["retryable"] = ProtoAnyCodable(retryable)

@@ -423,7 +425,7 @@ public actor GatewayChannelActor {

423425

client["modelIdentifier"] = ProtoAnyCodable(model)

424426

}

425427

var params: [String: ProtoAnyCodable] = [

426-

"minProtocol": ProtoAnyCodable(GATEWAY_PROTOCOL_VERSION),

428+

"minProtocol": ProtoAnyCodable(GATEWAY_MIN_PROTOCOL_VERSION),

427429

"maxProtocol": ProtoAnyCodable(GATEWAY_PROTOCOL_VERSION),

428430

"client": ProtoAnyCodable(client),

429431

"caps": ProtoAnyCodable(options.caps),

Original file line numberDiff line numberDiff line change

@@ -3,6 +3,7 @@

33

import Foundation

44
55

public let GATEWAY_PROTOCOL_VERSION = 4

6+

public let GATEWAY_MIN_PROTOCOL_VERSION = 3

67
78

public enum ErrorCode: String, Codable, Sendable {

89

case notLinked = "NOT_LINKED"

Original file line numberDiff line numberDiff line change

@@ -94,7 +94,7 @@ Connect (first message):

9494

"id": "c1",

9595

"method": "connect",

9696

"params": {

97-

"minProtocol": 4,

97+

"minProtocol": 3,

9898

"maxProtocol": 4,

9999

"client": {

100100

"id": "openclaw-macos",

@@ -266,14 +266,15 @@ The Swift generator emits:

266266
267267

- `GatewayFrame` enum with `req`, `res`, `event`, and `unknown` cases

268268

- Strongly typed payload structs/enums

269-

- `ErrorCode` values and `GATEWAY_PROTOCOL_VERSION`

269+

- `ErrorCode` values, `GATEWAY_PROTOCOL_VERSION`, and `GATEWAY_MIN_PROTOCOL_VERSION`

270270
271271

Unknown frame types are preserved as raw payloads for forward compatibility.

272272
273273

## Versioning + compatibility

274274
275275

- `PROTOCOL_VERSION` lives in `src/gateway/protocol/version.ts`.

276-

- Clients send `minProtocol` + `maxProtocol`; the server rejects mismatches.

276+

- Clients send `minProtocol` + `maxProtocol`; the server rejects ranges that

277+

do not include its current protocol.

277278

- The Swift models keep unknown frame types to avoid breaking older clients.

278279
279280

## Schema patterns and conventions