fix: label openclaw socket closures
This commit is contained in:
parent
700e463ec7
commit
da846871aa
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user