fix(android): align node protocol payloads
This commit is contained in:
@@ -6,6 +6,7 @@ Docs: https://docs.clawd.bot
|
|||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- Android: remove legacy bridge transport code now that nodes use the gateway protocol.
|
- Android: remove legacy bridge transport code now that nodes use the gateway protocol.
|
||||||
|
- Android: send structured payloads in node events/invokes and include user-agent metadata in gateway connects.
|
||||||
|
|
||||||
## 2026.1.19-2
|
## 2026.1.19-2
|
||||||
|
|
||||||
|
|||||||
@@ -498,6 +498,13 @@ class NodeRuntime(context: Context) {
|
|||||||
.ifEmpty { null }
|
.ifEmpty { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildUserAgent(): String {
|
||||||
|
val version = resolvedVersionName()
|
||||||
|
val release = Build.VERSION.RELEASE?.trim().orEmpty()
|
||||||
|
val releaseLabel = if (release.isEmpty()) "unknown" else release
|
||||||
|
return "ClawdbotAndroid/$version (Android $releaseLabel; SDK ${Build.VERSION.SDK_INT})"
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildClientInfo(clientId: String, clientMode: String): GatewayClientInfo {
|
private fun buildClientInfo(clientId: String, clientMode: String): GatewayClientInfo {
|
||||||
return GatewayClientInfo(
|
return GatewayClientInfo(
|
||||||
id = clientId,
|
id = clientId,
|
||||||
@@ -519,6 +526,7 @@ class NodeRuntime(context: Context) {
|
|||||||
commands = buildInvokeCommands(),
|
commands = buildInvokeCommands(),
|
||||||
permissions = emptyMap(),
|
permissions = emptyMap(),
|
||||||
client = buildClientInfo(clientId = "node-host", clientMode = "node"),
|
client = buildClientInfo(clientId = "node-host", clientMode = "node"),
|
||||||
|
userAgent = buildUserAgent(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,6 +538,7 @@ class NodeRuntime(context: Context) {
|
|||||||
commands = emptyList(),
|
commands = emptyList(),
|
||||||
permissions = emptyMap(),
|
permissions = emptyMap(),
|
||||||
client = buildClientInfo(clientId = "clawdbot-control-ui", clientMode = "ui"),
|
client = buildClientInfo(clientId = "clawdbot-control-ui", clientMode = "ui"),
|
||||||
|
userAgent = buildUserAgent(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ data class GatewayConnectOptions(
|
|||||||
val commands: List<String>,
|
val commands: List<String>,
|
||||||
val permissions: Map<String, Boolean>,
|
val permissions: Map<String, Boolean>,
|
||||||
val client: GatewayClientInfo,
|
val client: GatewayClientInfo,
|
||||||
|
val userAgent: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
class GatewaySession(
|
class GatewaySession(
|
||||||
@@ -131,10 +132,17 @@ class GatewaySession(
|
|||||||
|
|
||||||
suspend fun sendNodeEvent(event: String, payloadJson: String?) {
|
suspend fun sendNodeEvent(event: String, payloadJson: String?) {
|
||||||
val conn = currentConnection ?: return
|
val conn = currentConnection ?: return
|
||||||
|
val parsedPayload = payloadJson?.let { parseJsonOrNull(it) }
|
||||||
val params =
|
val params =
|
||||||
buildJsonObject {
|
buildJsonObject {
|
||||||
put("event", JsonPrimitive(event))
|
put("event", JsonPrimitive(event))
|
||||||
if (payloadJson != null) put("payloadJSON", JsonPrimitive(payloadJson)) else put("payloadJSON", JsonNull)
|
if (parsedPayload != null) {
|
||||||
|
put("payload", parsedPayload)
|
||||||
|
} else if (payloadJson != null) {
|
||||||
|
put("payloadJSON", JsonPrimitive(payloadJson))
|
||||||
|
} else {
|
||||||
|
put("payloadJSON", JsonNull)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
conn.request("node.event", params, timeoutMs = 8_000)
|
conn.request("node.event", params, timeoutMs = 8_000)
|
||||||
@@ -377,6 +385,9 @@ class GatewaySession(
|
|||||||
authJson?.let { put("auth", it) }
|
authJson?.let { put("auth", it) }
|
||||||
deviceJson?.let { put("device", it) }
|
deviceJson?.let { put("device", it) }
|
||||||
put("locale", JsonPrimitive(locale))
|
put("locale", JsonPrimitive(locale))
|
||||||
|
options.userAgent?.trim()?.takeIf { it.isNotEmpty() }?.let {
|
||||||
|
put("userAgent", JsonPrimitive(it))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +414,8 @@ class GatewaySession(
|
|||||||
|
|
||||||
private fun handleEvent(frame: JsonObject) {
|
private fun handleEvent(frame: JsonObject) {
|
||||||
val event = frame["event"].asStringOrNull() ?: return
|
val event = frame["event"].asStringOrNull() ?: return
|
||||||
val payloadJson = frame["payload"]?.let { it.toString() }
|
val payloadJson =
|
||||||
|
frame["payload"]?.let { it.toString() } ?: frame["payloadJSON"].asStringOrNull()
|
||||||
if (event == "node.invoke.request" && payloadJson != null && onInvoke != null) {
|
if (event == "node.invoke.request" && payloadJson != null && onInvoke != null) {
|
||||||
handleInvokeEvent(payloadJson)
|
handleInvokeEvent(payloadJson)
|
||||||
return
|
return
|
||||||
@@ -421,7 +433,9 @@ class GatewaySession(
|
|||||||
val id = payload["id"].asStringOrNull() ?: return
|
val id = payload["id"].asStringOrNull() ?: return
|
||||||
val nodeId = payload["nodeId"].asStringOrNull() ?: return
|
val nodeId = payload["nodeId"].asStringOrNull() ?: return
|
||||||
val command = payload["command"].asStringOrNull() ?: return
|
val command = payload["command"].asStringOrNull() ?: return
|
||||||
val params = payload["paramsJSON"].asStringOrNull()
|
val params =
|
||||||
|
payload["paramsJSON"].asStringOrNull()
|
||||||
|
?: payload["params"]?.let { value -> if (value is JsonNull) null else value.toString() }
|
||||||
val timeoutMs = payload["timeoutMs"].asLongOrNull()
|
val timeoutMs = payload["timeoutMs"].asLongOrNull()
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val result =
|
val result =
|
||||||
@@ -436,12 +450,17 @@ class GatewaySession(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendInvokeResult(id: String, nodeId: String, result: InvokeResult) {
|
private suspend fun sendInvokeResult(id: String, nodeId: String, result: InvokeResult) {
|
||||||
|
val parsedPayload = result.payloadJson?.let { parseJsonOrNull(it) }
|
||||||
val params =
|
val params =
|
||||||
buildJsonObject {
|
buildJsonObject {
|
||||||
put("id", JsonPrimitive(id))
|
put("id", JsonPrimitive(id))
|
||||||
put("nodeId", JsonPrimitive(nodeId))
|
put("nodeId", JsonPrimitive(nodeId))
|
||||||
put("ok", JsonPrimitive(result.ok))
|
put("ok", JsonPrimitive(result.ok))
|
||||||
if (result.payloadJson != null) put("payloadJSON", JsonPrimitive(result.payloadJson))
|
if (parsedPayload != null) {
|
||||||
|
put("payload", parsedPayload)
|
||||||
|
} else if (result.payloadJson != null) {
|
||||||
|
put("payloadJSON", JsonPrimitive(result.payloadJson))
|
||||||
|
}
|
||||||
result.error?.let { err ->
|
result.error?.let { err ->
|
||||||
put(
|
put(
|
||||||
"error",
|
"error",
|
||||||
@@ -599,3 +618,13 @@ private fun JsonElement?.asLongOrNull(): Long? =
|
|||||||
is JsonPrimitive -> content.toLongOrNull()
|
is JsonPrimitive -> content.toLongOrNull()
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun parseJsonOrNull(payload: String): JsonElement? {
|
||||||
|
val trimmed = payload.trim()
|
||||||
|
if (trimmed.isEmpty()) return null
|
||||||
|
return try {
|
||||||
|
Json.parseToJsonElement(trimmed)
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user