fix: preserve openclaw failure artifacts

This commit is contained in:
Cowork 3P 2026-06-04 22:59:56 +08:00
parent 60ed369df7
commit a05318324b
4 changed files with 89 additions and 7 deletions

View File

@ -1324,11 +1324,15 @@ extension AppControllerDesktopThreadActions on AppController {
return;
}
if (!result.success) {
clearGatewayTaskArtifactStateInternal(
sessionKey,
completedAtMs: completedAtMs,
syncStatus: 'failed',
);
if (hasCurrentRunArtifacts) {
await persistGoTaskArtifactsForSessionInternal(sessionKey, result);
} else {
clearGatewayTaskArtifactStateInternal(
sessionKey,
completedAtMs: completedAtMs,
syncStatus: 'failed',
);
}
appendLocalSessionMessageInternal(
sessionKey,
assistantErrorMessageInternal(

View File

@ -621,15 +621,19 @@ Object? _firstGoTaskArtifactList(Map<String, dynamic> result) {
final artifacts = <Object?>[];
for (final candidate in <Object?>[
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'],
]) {

View File

@ -1953,6 +1953,55 @@ void main() {
},
);
test(
'sendChatMessage keeps partial OpenClaw artifacts on terminal artifact failure',
() async {
final fakeGoTaskService = _RecordingGoTaskServiceClient()
..outcomes.add(
const GoTaskServiceResult(
success: false,
message: 'OpenClaw completed without required final artifacts.',
turnId: 'turn-1',
raw: <String, dynamic>{
'status': 'failed',
'code': 'OPENCLAW_REQUIRED_ARTIFACT_MISSING',
'artifacts': <Map<String, dynamic>>[
<String, dynamic>{
'relativePath': 'stages/chapter.md',
'content': 'partial chapter',
'contentType': 'text/markdown',
},
],
},
errorMessage:
'openclaw returned partial artifacts without required final deliverables',
resolvedModel: '',
route: GoTaskServiceRoute.externalAcpSingle,
),
);
final controller = _connectedController(fakeGoTaskService);
addTearDown(controller.dispose);
await controller.sessionsController.switchSession(
'unit-fixture-task-a',
);
await controller.sendChatMessage('first turn');
final failedThread = controller.taskThreadForSessionInternal(
'unit-fixture-task-a',
);
expect(
failedThread?.lifecycleState.lastResultCode,
'OPENCLAW_REQUIRED_ARTIFACT_MISSING',
);
expect(failedThread?.lastArtifactSyncStatus, 'synced');
expect(failedThread?.lastTaskArtifactRelativePaths, <String>[
'stages/chapter.md',
]);
},
);
test(
'sendChatMessage treats nested OpenClaw artifact errors as terminal failures',
() async {

View File

@ -251,6 +251,33 @@ void main() {
'https://xworkmate-bridge.svc.plus/artifacts/summary.pdf',
);
});
test('uses OpenClaw finalArtifacts as required deliverables', () {
final result = goTaskServiceResultFromAcpResponse(<String, dynamic>{
'jsonrpc': '2.0',
'id': 'request-id',
'result': <String, dynamic>{
'success': true,
'message': 'created final deliverable',
'finalArtifacts': <Map<String, dynamic>>[
<String, dynamic>{
'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', () {
@ -2128,8 +2155,6 @@ void main() {
);
},
);
});
}