diff --git a/lib/app/app_controller_desktop_thread_actions.dart b/lib/app/app_controller_desktop_thread_actions.dart index 6a9b2500..b7926eec 100644 --- a/lib/app/app_controller_desktop_thread_actions.dart +++ b/lib/app/app_controller_desktop_thread_actions.dart @@ -1324,15 +1324,11 @@ extension AppControllerDesktopThreadActions on AppController { return; } if (!result.success) { - if (hasCurrentRunArtifacts) { - await persistGoTaskArtifactsForSessionInternal(sessionKey, result); - } else { - clearGatewayTaskArtifactStateInternal( - sessionKey, - completedAtMs: completedAtMs, - syncStatus: 'failed', - ); - } + clearGatewayTaskArtifactStateInternal( + sessionKey, + completedAtMs: completedAtMs, + syncStatus: 'failed', + ); appendLocalSessionMessageInternal( sessionKey, assistantErrorMessageInternal( diff --git a/lib/runtime/external_code_agent_acp_desktop_transport.dart b/lib/runtime/external_code_agent_acp_desktop_transport.dart index e8b639a4..9976ed5e 100644 --- a/lib/runtime/external_code_agent_acp_desktop_transport.dart +++ b/lib/runtime/external_code_agent_acp_desktop_transport.dart @@ -218,18 +218,6 @@ class ExternalCodeAgentAcpDesktopTransport final association = OpenClawTaskAssociation.fromJsonOrNull( runningTaskSnapshot, ); - if (association != null) { - return goTaskServiceResultFromAcpResponse( - { - 'jsonrpc': '2.0', - 'id': 'recovered-from-running-task-handle', - 'result': runningTaskSnapshot, - }, - route: request.route, - streamedText: streamedText, - completedMessage: completedMessage, - ); - } final attempts = _recoveryAttemptsForRequest(request); for (var attempt = 0; attempt < attempts; attempt += 1) { if (attempt > 0) { @@ -239,10 +227,11 @@ class ExternalCodeAgentAcpDesktopTransport try { response = await _client.request( method: 'xworkmate.tasks.get', - params: { - 'sessionId': request.sessionId, - 'threadId': request.threadId, - }, + params: association?.toTaskGetParams() ?? + { + 'sessionId': request.sessionId, + 'threadId': request.threadId, + }, endpointOverride: endpoint, ); } on GatewayAcpException { diff --git a/lib/runtime/go_task_service_client.dart b/lib/runtime/go_task_service_client.dart index e801b65c..892c3d23 100644 --- a/lib/runtime/go_task_service_client.dart +++ b/lib/runtime/go_task_service_client.dart @@ -621,19 +621,15 @@ Object? _firstGoTaskArtifactList(Map result) { final artifacts = []; for (final candidate in [ result['artifacts'], - result['finalArtifacts'], result['files'], result['attachments'], _castMap(result['payload'])['artifacts'], - _castMap(result['payload'])['finalArtifacts'], _castMap(result['payload'])['files'], _castMap(result['payload'])['attachments'], _castMap(result['result'])['artifacts'], - _castMap(result['result'])['finalArtifacts'], _castMap(result['result'])['files'], _castMap(result['result'])['attachments'], _castMap(result['data'])['artifacts'], - _castMap(result['data'])['finalArtifacts'], _castMap(result['data'])['files'], _castMap(result['data'])['attachments'], ]) { diff --git a/test/runtime/assistant_execution_target_test.dart b/test/runtime/assistant_execution_target_test.dart index c82e22f1..7df9b00c 100644 --- a/test/runtime/assistant_execution_target_test.dart +++ b/test/runtime/assistant_execution_target_test.dart @@ -1954,7 +1954,7 @@ void main() { ); test( - 'sendChatMessage keeps partial OpenClaw artifacts on terminal artifact failure', + 'sendChatMessage rejects partial OpenClaw artifacts on terminal artifact failure', () async { final fakeGoTaskService = _RecordingGoTaskServiceClient() ..outcomes.add( @@ -1995,10 +1995,8 @@ void main() { failedThread?.lifecycleState.lastResultCode, 'OPENCLAW_REQUIRED_ARTIFACT_MISSING', ); - expect(failedThread?.lastArtifactSyncStatus, 'synced'); - expect(failedThread?.lastTaskArtifactRelativePaths, [ - 'stages/chapter.md', - ]); + expect(failedThread?.lastArtifactSyncStatus, 'failed'); + expect(failedThread?.lastTaskArtifactRelativePaths, isEmpty); }, ); diff --git a/test/runtime/gateway_acp_client_auth_test.dart b/test/runtime/gateway_acp_client_auth_test.dart index 1b9e191c..4e5e0b91 100644 --- a/test/runtime/gateway_acp_client_auth_test.dart +++ b/test/runtime/gateway_acp_client_auth_test.dart @@ -252,32 +252,6 @@ void main() { ); }); - test('uses OpenClaw finalArtifacts as required deliverables', () { - final result = goTaskServiceResultFromAcpResponse({ - 'jsonrpc': '2.0', - 'id': 'request-id', - 'result': { - 'success': true, - 'message': 'created final deliverable', - 'finalArtifacts': >[ - { - 'relativePath': 'exports/final.pdf', - 'downloadUrl': - 'https://xworkmate-bridge.svc.plus/artifacts/final.pdf', - 'contentType': 'application/pdf', - }, - ], - }, - }, route: GoTaskServiceRoute.externalAcpSingle); - - expect(result.success, isTrue); - expect(result.artifacts, hasLength(1)); - expect(result.artifacts.single.relativePath, 'exports/final.pdf'); - expect( - result.artifacts.single.downloadUrl, - 'https://xworkmate-bridge.svc.plus/artifacts/final.pdf', - ); - }); }); group('GatewayAcpClient authorization', () { @@ -1075,6 +1049,130 @@ void main() { }, ); + test( + 'polls terminal OpenClaw snapshot after receiving a running task handle', + () async { + final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); + addTearDown(() => server.close(force: true)); + var snapshotPolls = 0; + final taskGetParams = >[]; + server.listen((request) async { + final body = await utf8.decoder.bind(request).join(); + final decoded = jsonDecode(body) as Map; + final method = decoded['method']?.toString() ?? ''; + final id = decoded['id']?.toString() ?? 'request-id'; + if (method == 'session.start') { + final event = jsonEncode({ + 'jsonrpc': '2.0', + 'method': 'session.update', + 'params': { + 'sessionId': 'unit-fixture-task-handle', + 'threadId': 'unit-fixture-task-handle', + 'turnId': 'turn-running', + 'type': 'status', + 'event': 'running', + 'status': 'running', + 'runId': 'run-running', + 'artifactScope': + 'tasks/unit-fixture-task-handle/run-running', + 'artifactDirectory': + '/home/ubuntu/.openclaw/workspace/tasks/unit-fixture-task-handle/run-running', + 'gatewayProviderId': 'openclaw', + }, + }); + final eventBytes = utf8.encode('data: $event\n\n'); + request.response.headers.set( + HttpHeaders.contentTypeHeader, + 'text/event-stream', + ); + request.response.contentLength = eventBytes.length + 128; + final socket = await request.response.detachSocket(); + socket.add(eventBytes); + await socket.flush(); + socket.destroy(); + return; + } + if (method == 'xworkmate.tasks.get') { + snapshotPolls += 1; + taskGetParams.add( + (decoded['params'] as Map).cast(), + ); + request.response.headers.contentType = ContentType.json; + request.response.write( + jsonEncode({ + 'jsonrpc': '2.0', + 'id': id, + 'result': { + 'status': 'completed', + 'sessionId': 'unit-fixture-task-handle', + 'threadId': 'unit-fixture-task-handle', + 'turnId': 'turn-running', + 'result': { + 'success': true, + 'output': 'completed after task handle', + 'turnId': 'turn-running', + }, + 'artifacts': { + 'items': >[ + { + 'relativePath': 'reports/final.md', + 'downloadUrl': + 'https://xworkmate-bridge.svc.plus/artifacts/openclaw/download' + '?sessionKey=unit-fixture-task-handle&runId=run-running&relativePath=reports%2Ffinal.md', + 'contentType': 'text/markdown', + }, + ], + }, + }, + }), + ); + await request.response.close(); + return; + } + request.response.statusCode = HttpStatus.badRequest; + await request.response.close(); + }); + final endpoint = Uri.parse('http://127.0.0.1:${server.port}'); + final transport = ExternalCodeAgentAcpDesktopTransport( + client: GatewayAcpClient(endpointResolver: () => endpoint), + endpointResolver: (_) => endpoint, + taskEndpointResolver: (_) => endpoint, + recoveryPollDelay: Duration.zero, + recoveryMaxAttempts: 2, + ); + addTearDown(transport.dispose); + + final result = await transport.executeTask( + const GoTaskServiceRequest( + sessionId: 'unit-fixture-task-handle', + threadId: 'unit-fixture-task-handle', + target: AssistantExecutionTarget.gateway, + provider: SingleAgentProvider.openclaw, + prompt: 'create final markdown', + workingDirectory: '/tmp/workspace', + model: '', + thinking: 'off', + selectedSkills: [], + inlineAttachments: [], + localAttachments: [], + agentId: '', + metadata: {}, + ), + onUpdate: (_) {}, + ); + + expect(snapshotPolls, 1); + expect(taskGetParams.single['runId'], 'run-running'); + expect( + taskGetParams.single['artifactScope'], + 'tasks/unit-fixture-task-handle/run-running', + ); + expect(result.success, isTrue); + expect(result.message, 'completed after task handle'); + expect(result.artifacts.single.relativePath, 'reports/final.md'); + }, + ); + test( 'recovers terminal failed OpenClaw snapshot without displayable result', () async {