fix: label openclaw socket closures

This commit is contained in:
Haitao Pan 2026-05-07 17:47:07 +08:00
parent 700e463ec7
commit da846871aa
4 changed files with 110 additions and 3 deletions

View File

@ -228,6 +228,24 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
}) {
final raw = error.toString().trim();
final lowered = raw.toLowerCase();
final detailCode = error is GatewayAcpException
? error.detailCode?.trim().toUpperCase()
: null;
final primaryCode = error is GatewayAcpException
? error.code?.trim().toUpperCase()
: null;
final openClawSocketClosed =
target.isGateway &&
(detailCode == 'OPENCLAW_GATEWAY_SOCKET_CLOSED' ||
primaryCode == 'OPENCLAW_GATEWAY_SOCKET_CLOSED' ||
raw.contains('OPENCLAW_GATEWAY_SOCKET_CLOSED') ||
lowered.contains('openclaw') && lowered.contains('socket closed'));
if (openClawSocketClosed) {
return appText(
'OpenClaw Gateway 连接在任务执行中断开,请稍后重试;若持续出现,请检查 xworkmate-bridge 主机到 127.0.0.1:18789 的 OpenClaw runtime 连接。',
'The OpenClaw Gateway connection closed during task execution. Try again later; if it keeps happening, check the OpenClaw runtime connection from the xworkmate-bridge host to 127.0.0.1:18789.',
);
}
if (lowered.contains('gateway not connected') ||
lowered.contains('code: offline') ||
lowered.contains('offlin') && lowered.contains('gateway')) {
@ -974,7 +992,9 @@ bool _usesOpenClawTaskSubmitEndpointInternal(GoTaskServiceRequest request) {
if (request.isMultiAgentRequest || !request.target.isGateway) {
return false;
}
final providerId = normalizeSingleAgentProviderId(request.provider.providerId);
final providerId = normalizeSingleAgentProviderId(
request.provider.providerId,
);
if (providerId == kCanonicalGatewayProviderId) {
return true;
}

View File

@ -6,10 +6,16 @@ import 'acp_endpoint_paths.dart';
import 'runtime_models.dart';
class GatewayAcpException implements Exception {
const GatewayAcpException(this.message, {this.code, this.details});
const GatewayAcpException(
this.message, {
this.code,
this.detailCode,
this.details,
});
final String message;
final String? code;
final String? detailCode;
final Object? details;
@override
@ -1022,13 +1028,22 @@ class GatewayAcpClient {
if (error.isEmpty) {
return;
}
final details = error['data'] ?? error['details'];
throw GatewayAcpException(
stringValue(error['message']) ?? 'ACP JSON-RPC request failed',
code: stringValue(error['code']),
details: error['data'],
detailCode: _jsonRpcErrorDetailCode(details),
details: details,
);
}
String? _jsonRpcErrorDetailCode(Object? details) {
final data = asMap(details);
return stringValue(data['code']) ??
stringValue(data['detailCode']) ??
stringValue(data['errorCode']);
}
Map<String, dynamic> _decodeMap(dynamic raw) {
if (raw is Map<String, dynamic>) {
return raw;

View File

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:xworkmate/app/app_controller.dart';
import 'package:xworkmate/runtime/gateway_acp_client.dart';
import 'package:xworkmate/runtime/runtime_models.dart';
import 'package:xworkmate/runtime/secure_config_store.dart';
@ -138,6 +139,27 @@ void main() {
},
);
test(
'labels OpenClaw socket close without exposing raw JSON-RPC error',
() async {
final controller = await _isolatedController();
addTearDown(controller.dispose);
final label = controller.gatewayExecutionErrorLabelInternal(
const GatewayAcpException(
'OPENCLAW_GATEWAY_SOCKET_CLOSED: OpenClaw gateway connection closed during task execution',
code: '-32002',
detailCode: 'OPENCLAW_GATEWAY_SOCKET_CLOSED',
),
target: AssistantExecutionTarget.gateway,
);
expect(label, contains('OpenClaw Gateway 连接在任务执行中断开'));
expect(label, isNot(contains('-32002')));
expect(label, isNot(contains('socket closed')));
},
);
test('keeps signed-out generic runtime failures disconnected', () async {
final controller = await _isolatedController();
addTearDown(controller.dispose);

View File

@ -1014,6 +1014,56 @@ void main() {
},
);
test('preserves OpenClaw gateway socket close detail code', () async {
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
addTearDown(() => server.close(force: true));
server.listen((request) async {
await utf8.decoder.bind(request).join();
request.response
..statusCode = HttpStatus.ok
..headers.contentType = ContentType.json
..write(
jsonEncode(<String, dynamic>{
'jsonrpc': '2.0',
'id': 'request-id',
'error': <String, dynamic>{
'code': -32002,
'message':
'OPENCLAW_GATEWAY_SOCKET_CLOSED: OpenClaw gateway connection closed during task execution',
'data': <String, dynamic>{
'code': 'OPENCLAW_GATEWAY_SOCKET_CLOSED',
'originalCode': 'SOCKET_CLOSED',
},
},
}),
);
await request.response.close();
});
final endpoint = Uri.parse('http://127.0.0.1:${server.port}');
final client = GatewayAcpClient(endpointResolver: () => endpoint);
await expectLater(
client.request(
method: 'session.start',
params: const <String, dynamic>{},
),
throwsA(
isA<GatewayAcpException>()
.having((error) => error.code, 'code', '-32002')
.having(
(error) => error.detailCode,
'detailCode',
'OPENCLAW_GATEWAY_SOCKET_CLOSED',
)
.having(
(error) => error.details,
'details',
containsPair('originalCode', 'SOCKET_CLOSED'),
),
),
);
});
test('desktop follow-up execution uses session.message', () async {
final capture = await _startAcpHttpServer();
addTearDown(capture.close);