merge: poll openclaw task handle to terminal snapshot
This commit is contained in:
commit
c4191fa5c9
@ -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(
|
||||
|
||||
@ -218,18 +218,6 @@ class ExternalCodeAgentAcpDesktopTransport
|
||||
final association = OpenClawTaskAssociation.fromJsonOrNull(
|
||||
runningTaskSnapshot,
|
||||
);
|
||||
if (association != null) {
|
||||
return goTaskServiceResultFromAcpResponse(
|
||||
<String, dynamic>{
|
||||
'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: <String, dynamic>{
|
||||
'sessionId': request.sessionId,
|
||||
'threadId': request.threadId,
|
||||
},
|
||||
params: association?.toTaskGetParams() ??
|
||||
<String, dynamic>{
|
||||
'sessionId': request.sessionId,
|
||||
'threadId': request.threadId,
|
||||
},
|
||||
endpointOverride: endpoint,
|
||||
);
|
||||
} on GatewayAcpException {
|
||||
|
||||
@ -621,19 +621,15 @@ 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'],
|
||||
]) {
|
||||
|
||||
@ -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, <String>[
|
||||
'stages/chapter.md',
|
||||
]);
|
||||
expect(failedThread?.lastArtifactSyncStatus, 'failed');
|
||||
expect(failedThread?.lastTaskArtifactRelativePaths, isEmpty);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -252,32 +252,6 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
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', () {
|
||||
@ -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 = <Map<String, dynamic>>[];
|
||||
server.listen((request) async {
|
||||
final body = await utf8.decoder.bind(request).join();
|
||||
final decoded = jsonDecode(body) as Map<String, dynamic>;
|
||||
final method = decoded['method']?.toString() ?? '';
|
||||
final id = decoded['id']?.toString() ?? 'request-id';
|
||||
if (method == 'session.start') {
|
||||
final event = jsonEncode(<String, dynamic>{
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'session.update',
|
||||
'params': <String, dynamic>{
|
||||
'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<String, dynamic>(),
|
||||
);
|
||||
request.response.headers.contentType = ContentType.json;
|
||||
request.response.write(
|
||||
jsonEncode(<String, dynamic>{
|
||||
'jsonrpc': '2.0',
|
||||
'id': id,
|
||||
'result': <String, dynamic>{
|
||||
'status': 'completed',
|
||||
'sessionId': 'unit-fixture-task-handle',
|
||||
'threadId': 'unit-fixture-task-handle',
|
||||
'turnId': 'turn-running',
|
||||
'result': <String, dynamic>{
|
||||
'success': true,
|
||||
'output': 'completed after task handle',
|
||||
'turnId': 'turn-running',
|
||||
},
|
||||
'artifacts': <String, dynamic>{
|
||||
'items': <Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'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: <String>[],
|
||||
inlineAttachments: <GatewayChatAttachmentPayload>[],
|
||||
localAttachments: <CollaborationAttachment>[],
|
||||
agentId: '',
|
||||
metadata: <String, dynamic>{},
|
||||
),
|
||||
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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user