diff --git a/lib/runtime/go_task_service_client.dart b/lib/runtime/go_task_service_client.dart index 28003d67..88dd6594 100644 --- a/lib/runtime/go_task_service_client.dart +++ b/lib/runtime/go_task_service_client.dart @@ -503,7 +503,7 @@ class GoTaskServiceResult { _castMapList(raw['memorySources']); List get artifacts { - final rawArtifacts = raw['artifacts']; + final rawArtifacts = _firstGoTaskArtifactList(raw); if (rawArtifacts is! List) { return const []; } @@ -545,6 +545,20 @@ class GoTaskServiceResult { } } +Object? _firstGoTaskArtifactList(Map result) { + for (final candidate in [ + result['artifacts'], + _castMap(result['payload'])['artifacts'], + _castMap(result['result'])['artifacts'], + _castMap(result['data'])['artifacts'], + ]) { + if (candidate is List) { + return candidate; + } + } + return null; +} + String? goTaskServiceGatewayEntryState({ required AssistantExecutionTarget requestedTarget, required GoTaskServiceResult result, diff --git a/test/runtime/app_controller_thread_workspace_binding_test.dart b/test/runtime/app_controller_thread_workspace_binding_test.dart index 82d0f355..8649432f 100644 --- a/test/runtime/app_controller_thread_workspace_binding_test.dart +++ b/test/runtime/app_controller_thread_workspace_binding_test.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:xworkmate/app/app_controller.dart'; import 'package:xworkmate/app/app_controller_desktop_runtime_coordination_impl.dart'; +import 'package:xworkmate/runtime/go_task_service_client.dart'; import 'package:xworkmate/runtime/runtime_models.dart'; void main() { @@ -52,4 +53,61 @@ void main() { ); }, ); + + test('writes inline ACP artifacts into the local thread workspace', () async { + final controller = AppController(); + addTearDown(controller.dispose); + + final localWorkspace = await Directory.systemTemp.createTemp( + 'xworkmate-artifact-workspace-', + ); + addTearDown(() async { + if (await localWorkspace.exists()) { + await localWorkspace.delete(recursive: true); + } + }); + + controller.upsertTaskThreadInternal( + 'session-1', + workspaceBinding: WorkspaceBinding( + workspaceId: 'session-1', + workspaceKind: WorkspaceKind.localFs, + workspacePath: localWorkspace.path, + displayPath: localWorkspace.path, + writable: true, + ), + ); + + final result = GoTaskServiceResult( + success: true, + message: 'hello', + turnId: 'turn-1', + raw: { + 'artifacts': >[ + { + 'relativePath': 'notes/hello.txt', + 'content': 'artifact body', + 'contentType': 'text/plain', + }, + ], + }, + errorMessage: '', + resolvedModel: '', + route: GoTaskServiceRoute.externalAcpSingle, + ); + + await controller.persistGoTaskArtifactsForSessionInternal( + 'session-1', + result, + ); + + final artifact = File('${localWorkspace.path}/notes/hello.txt'); + expect(await artifact.readAsString(), 'artifact body'); + expect( + controller + .requireTaskThreadForSessionInternal('session-1') + .lastArtifactSyncStatus, + 'synced', + ); + }); } diff --git a/test/runtime/gateway_acp_client_auth_test.dart b/test/runtime/gateway_acp_client_auth_test.dart index d3036e84..202cf352 100644 --- a/test/runtime/gateway_acp_client_auth_test.dart +++ b/test/runtime/gateway_acp_client_auth_test.dart @@ -94,6 +94,54 @@ void main() { expect(result.success, isFalse); expect(result.message, 'codex execution environment is unavailable'); }); + + test('keeps bridge message and inline artifacts together', () { + final result = goTaskServiceResultFromAcpResponse({ + 'jsonrpc': '2.0', + 'id': 'request-id', + 'result': { + 'success': true, + 'message': 'hello', + 'artifacts': >[ + { + 'relativePath': 'notes/hello.txt', + 'content': 'artifact body', + 'contentType': 'text/plain', + }, + ], + }, + }, route: GoTaskServiceRoute.externalAcpSingle); + + expect(result.success, isTrue); + expect(result.message, 'hello'); + expect(result.artifacts, hasLength(1)); + expect(result.artifacts.single.relativePath, 'notes/hello.txt'); + expect(result.artifacts.single.content, 'artifact body'); + }); + + test('uses nested bridge inline artifacts when provider wraps payload', () { + final result = goTaskServiceResultFromAcpResponse({ + 'jsonrpc': '2.0', + 'id': 'request-id', + 'result': { + 'success': true, + 'payload': { + 'message': 'hello', + 'artifacts': >[ + { + 'relativePath': 'hello.txt', + 'content': 'nested artifact body', + }, + ], + }, + }, + }, route: GoTaskServiceRoute.externalAcpSingle); + + expect(result.message, 'hello'); + expect(result.artifacts, hasLength(1)); + expect(result.artifacts.single.relativePath, 'hello.txt'); + expect(result.artifacts.single.content, 'nested artifact body'); + }); }); group('GatewayAcpClient authorization', () {