Sync inline ACP artifacts from task results

This commit is contained in:
Haitao Pan 2026-04-28 16:49:18 +08:00
parent 32ed1e129c
commit 65ced40ced
3 changed files with 121 additions and 1 deletions

View File

@ -503,7 +503,7 @@ class GoTaskServiceResult {
_castMapList(raw['memorySources']);
List<GoTaskServiceArtifact> get artifacts {
final rawArtifacts = raw['artifacts'];
final rawArtifacts = _firstGoTaskArtifactList(raw);
if (rawArtifacts is! List) {
return const <GoTaskServiceArtifact>[];
}
@ -545,6 +545,20 @@ class GoTaskServiceResult {
}
}
Object? _firstGoTaskArtifactList(Map<String, dynamic> result) {
for (final candidate in <Object?>[
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,

View File

@ -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: <String, dynamic>{
'artifacts': <Map<String, dynamic>>[
<String, dynamic>{
'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',
);
});
}

View File

@ -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(<String, dynamic>{
'jsonrpc': '2.0',
'id': 'request-id',
'result': <String, dynamic>{
'success': true,
'message': 'hello',
'artifacts': <Map<String, dynamic>>[
<String, dynamic>{
'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(<String, dynamic>{
'jsonrpc': '2.0',
'id': 'request-id',
'result': <String, dynamic>{
'success': true,
'payload': <String, dynamic>{
'message': 'hello',
'artifacts': <Map<String, dynamic>>[
<String, dynamic>{
'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', () {