Merge branch 'codex/openclaw-final-deliverables' into release/v1.1.4
# Conflicts: # docs/architecture/cross-repo-task-state-workflow.md # test/runtime/assistant_execution_target_test.dart
This commit is contained in:
commit
f8449d42e7
@ -129,6 +129,7 @@ stateDiagram-v2
|
||||
Running --> Ready: success
|
||||
Running --> Ready: failed / artifact_missing
|
||||
Running --> Ready: ACP_HTTP_* / unrecovered SSE interruption
|
||||
Running --> Ready: xworkmate.tasks.get failure after transport recovery
|
||||
Running --> Ready: abort
|
||||
|
||||
Ready --> Archived: user archives task
|
||||
@ -212,6 +213,8 @@ When `session.update` contains `status=running` with `runId` and `artifactScope`
|
||||
- `failed`, `artifact_missing`, `cancelled`, or `canceled` -> failure path.
|
||||
- no terminal snapshot after recovery attempts -> unrecovered interruption path.
|
||||
|
||||
If a persisted OpenClaw running association is polled later and `xworkmate.tasks.get` fails after transport-level recovery, the app must record the concrete diagnostic code, clear the pending run, and return the TaskThread to `ready`. It must not silently swallow the failure and leave the task without an execution result.
|
||||
|
||||
## Artifact Rules
|
||||
|
||||
The app syncs artifacts only when the bridge/OpenClaw terminal result establishes them as current-run final artifacts.
|
||||
|
||||
@ -364,7 +364,8 @@ extension AppControllerDesktopThreadActions on AppController {
|
||||
connectionState = assistantConnectionStateForSession(
|
||||
normalizedSessionKey,
|
||||
);
|
||||
} catch (_) {
|
||||
} catch (error) {
|
||||
debugPrint('Gateway capability refresh fallback: $error');
|
||||
// Fallback to existing connection state if refresh fails.
|
||||
}
|
||||
}
|
||||
@ -411,7 +412,8 @@ extension AppControllerDesktopThreadActions on AppController {
|
||||
if (providerCatalogForExecutionTarget(currentTarget).isEmpty) {
|
||||
try {
|
||||
await refreshSingleAgentCapabilitiesInternal(forceRefresh: true);
|
||||
} catch (_) {
|
||||
} catch (error) {
|
||||
debugPrint('Gateway provider catalog refresh fallback: $error');
|
||||
// Keep the local guard focused on the post-refresh catalog state.
|
||||
}
|
||||
if (providerCatalogForExecutionTarget(currentTarget).isEmpty) {
|
||||
@ -793,8 +795,19 @@ extension AppControllerDesktopThreadActions on AppController {
|
||||
recomputeTasksInternal();
|
||||
notifyIfActiveInternal();
|
||||
return;
|
||||
} catch (_) {
|
||||
continue;
|
||||
} catch (error) {
|
||||
if (aiGatewayPendingSessionKeysInternal.contains(sessionKey)) {
|
||||
await applyGatewayChatFailureInternal(
|
||||
sessionKey: sessionKey,
|
||||
target: target,
|
||||
error: error,
|
||||
);
|
||||
}
|
||||
aiGatewayPendingSessionKeysInternal.remove(sessionKey);
|
||||
clearAiGatewayStreamingTextInternal(sessionKey);
|
||||
recomputeTasksInternal();
|
||||
notifyIfActiveInternal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
final nowMs = DateTime.now().millisecondsSinceEpoch.toDouble();
|
||||
@ -1572,7 +1585,8 @@ extension AppControllerDesktopThreadActions on AppController {
|
||||
sessionKey,
|
||||
)?.openClawTaskAssociation,
|
||||
);
|
||||
} catch (_) {
|
||||
} catch (error) {
|
||||
debugPrint('OpenClaw cancellation fallback: $error');
|
||||
// Best effort cancellation only. Local state must still leave pending.
|
||||
}
|
||||
removeQueuedOpenClawGatewayTurnsForSessionInternal(sessionKey);
|
||||
@ -1586,7 +1600,8 @@ extension AppControllerDesktopThreadActions on AppController {
|
||||
Future<void> prepareForExit() async {
|
||||
try {
|
||||
await abortRun();
|
||||
} catch (_) {
|
||||
} catch (error) {
|
||||
debugPrint('Prepare for exit abort fallback: $error');
|
||||
// Best effort only. Native termination still proceeds.
|
||||
}
|
||||
await flushAssistantThreadPersistenceInternal();
|
||||
|
||||
@ -163,7 +163,8 @@ extension AppControllerDesktopThreadBinding on AppController {
|
||||
}
|
||||
try {
|
||||
Directory(normalizedPath).createSync(recursive: true);
|
||||
} catch (_) {
|
||||
} catch (error) {
|
||||
debugPrint('Ensure local thread workspace fallback: $error');
|
||||
// Best effort only. The caller can still decide whether to fail fast.
|
||||
}
|
||||
return Directory(normalizedPath).existsSync();
|
||||
|
||||
@ -43,7 +43,7 @@ Future<MediaStream?> desktopRemoteVideoStreamForTrack(
|
||||
}
|
||||
|
||||
final stream = await createFallbackStream('xworkmate-remote-desktop');
|
||||
await stream.addTrack(event.track);
|
||||
await stream.addTrack(event.track, addToNative: false);
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
@ -86,6 +86,14 @@ void main() {
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('unit-fixture-task-a');
|
||||
controller.initializeAssistantThreadContext(
|
||||
'unit-fixture-task-a',
|
||||
executionTarget: AssistantExecutionTarget.agent,
|
||||
messageViewMode: controller.assistantMessageViewModeForSession(
|
||||
'unit-fixture-task-a',
|
||||
),
|
||||
);
|
||||
controller.notifyListeners();
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(child: _buildLowerPane(controller: controller)),
|
||||
@ -107,7 +115,7 @@ void main() {
|
||||
find.byKey(const Key('assistant-provider-menu-item-gemini')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byIcon(Icons.check_rounded), findsNothing);
|
||||
expect(find.byIcon(Icons.check_rounded), findsOneWidget);
|
||||
await tester.tap(
|
||||
find.byKey(const Key('assistant-provider-menu-item-codex')),
|
||||
);
|
||||
@ -372,81 +380,71 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'allows switching from Gateway back to Agent when bridge reports both',
|
||||
(tester) async {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
uiFeatureManifest: _defaultDesktopManifest(),
|
||||
initialBridgeProviderCatalog: const <SingleAgentProvider>[
|
||||
SingleAgentProvider.codex,
|
||||
SingleAgentProvider.opencode,
|
||||
],
|
||||
initialGatewayProviderCatalog: const <SingleAgentProvider>[
|
||||
SingleAgentProvider.openclaw,
|
||||
],
|
||||
initialAvailableExecutionTargets: const <AssistantExecutionTarget>[
|
||||
AssistantExecutionTarget.agent,
|
||||
AssistantExecutionTarget.gateway,
|
||||
],
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
testWidgets('shows Agent and Gateway modes when bridge reports both', (
|
||||
tester,
|
||||
) async {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
uiFeatureManifest: _defaultDesktopManifest(),
|
||||
initialBridgeProviderCatalog: const <SingleAgentProvider>[
|
||||
SingleAgentProvider.codex,
|
||||
SingleAgentProvider.opencode,
|
||||
],
|
||||
initialGatewayProviderCatalog: const <SingleAgentProvider>[
|
||||
SingleAgentProvider.openclaw,
|
||||
],
|
||||
initialAvailableExecutionTargets: const <AssistantExecutionTarget>[
|
||||
AssistantExecutionTarget.agent,
|
||||
AssistantExecutionTarget.gateway,
|
||||
],
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession(
|
||||
await controller.sessionsController.switchSession('unit-fixture-task-a');
|
||||
controller.initializeAssistantThreadContext(
|
||||
'unit-fixture-task-a',
|
||||
executionTarget: AssistantExecutionTarget.gateway,
|
||||
messageViewMode: controller.assistantMessageViewModeForSession(
|
||||
'unit-fixture-task-a',
|
||||
);
|
||||
controller.initializeAssistantThreadContext(
|
||||
'unit-fixture-task-a',
|
||||
executionTarget: AssistantExecutionTarget.gateway,
|
||||
messageViewMode: controller.assistantMessageViewModeForSession(
|
||||
'unit-fixture-task-a',
|
||||
),
|
||||
);
|
||||
controller.notifyListeners();
|
||||
),
|
||||
);
|
||||
controller.notifyListeners();
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(child: _buildLowerPane(controller: controller)),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(child: _buildLowerPane(controller: controller)),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(controller.currentAssistantExecutionTarget.isGateway, isTrue);
|
||||
expect(controller.currentAssistantExecutionTarget.isGateway, isTrue);
|
||||
|
||||
await tester.tap(
|
||||
find.byKey(const Key('assistant-execution-target-button')),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(
|
||||
find.byKey(const Key('assistant-execution-target-button')),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byKey(const Key('assistant-execution-target-menu-item-agent')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const Key('assistant-execution-target-menu-item-gateway')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const Key('assistant-execution-target-menu-item-agent')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const Key('assistant-execution-target-menu-item-gateway')),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
await tester.tap(
|
||||
find.byKey(const Key('assistant-execution-target-menu-item-agent')),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(controller.currentAssistantExecutionTarget.isAgent, isTrue);
|
||||
expect(
|
||||
controller
|
||||
.providerCatalogForExecutionTarget(AssistantExecutionTarget.agent)
|
||||
.map((provider) => provider.providerId),
|
||||
const <String>['codex', 'opencode'],
|
||||
);
|
||||
expect(
|
||||
controller
|
||||
.providerCatalogForExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
)
|
||||
.map((provider) => provider.providerId),
|
||||
const <String>[kCanonicalGatewayProviderId],
|
||||
);
|
||||
},
|
||||
);
|
||||
expect(controller.currentAssistantExecutionTarget.isGateway, isTrue);
|
||||
expect(
|
||||
controller
|
||||
.providerCatalogForExecutionTarget(AssistantExecutionTarget.agent)
|
||||
.map((provider) => provider.providerId),
|
||||
const <String>['codex', 'opencode'],
|
||||
);
|
||||
expect(
|
||||
controller
|
||||
.providerCatalogForExecutionTarget(AssistantExecutionTarget.gateway)
|
||||
.map((provider) => provider.providerId),
|
||||
const <String>[kCanonicalGatewayProviderId],
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('uses submit button instead of connect action', (tester) async {
|
||||
final controller = AppController(
|
||||
|
||||
@ -994,7 +994,7 @@ void main() {
|
||||
);
|
||||
for (
|
||||
var attempt = 0;
|
||||
attempt < 20 &&
|
||||
attempt < 300 &&
|
||||
controller
|
||||
.requireTaskThreadForSessionInternal('unit-fixture-task-a')
|
||||
.lastArtifactSyncStatus !=
|
||||
|
||||
@ -24,6 +24,7 @@ const List<String> _openClawE2ECanonicalPrompts = <String>[
|
||||
'围绕\n从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进 右侧是当下 \n测试制作视频',
|
||||
'围绕\n\n从单机权限 → 网络边界 → Web安全 → 云身份 → Zero Trust → AI Agent 身份 → AI模型与知识保护 演进 \n\n拆章节 -> 每章调用 Codex -> 每章 GPT images2 生成图 -> 汇总排版 -> 制作视频',
|
||||
];
|
||||
const Duration _openClawE2ESubmitTimeout = Duration(seconds: 10);
|
||||
|
||||
void main() {
|
||||
group('AssistantExecutionTarget', () {
|
||||
@ -1366,7 +1367,9 @@ void main() {
|
||||
expect(request.prompt, contains('XWorkmate task artifact contract:'));
|
||||
expect(
|
||||
request.prompt,
|
||||
contains('export the final deliverables through the current XWorkmate task artifact scope'),
|
||||
contains(
|
||||
'export the final deliverables through the current XWorkmate task artifact scope',
|
||||
),
|
||||
);
|
||||
expect(request.prompt, contains('最后 输出 PDF文件'));
|
||||
},
|
||||
@ -1628,7 +1631,10 @@ void main() {
|
||||
route: GoTaskServiceRoute.externalAcpSingle,
|
||||
),
|
||||
);
|
||||
final controller = _connectedController(fakeGoTaskService, homeDir: localWorkspace.path);
|
||||
final controller = _connectedController(
|
||||
fakeGoTaskService,
|
||||
homeDir: localWorkspace.path,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession(
|
||||
@ -1859,7 +1865,10 @@ void main() {
|
||||
route: GoTaskServiceRoute.externalAcpSingle,
|
||||
),
|
||||
);
|
||||
final controller = _connectedController(fakeGoTaskService, homeDir: localWorkspace.path);
|
||||
final controller = _connectedController(
|
||||
fakeGoTaskService,
|
||||
homeDir: localWorkspace.path,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession(
|
||||
@ -1940,7 +1949,7 @@ void main() {
|
||||
failedThread?.lifecycleState.lastResultCode,
|
||||
'OPENCLAW_NO_EXPORTED_ARTIFACTS',
|
||||
);
|
||||
expect(failedThread?.lastArtifactSyncStatus, 'failed');
|
||||
expect(failedThread?.lastArtifactSyncStatus, 'no-exported-artifacts');
|
||||
|
||||
await controller.sendChatMessage('retry final artifact');
|
||||
|
||||
@ -1954,6 +1963,7 @@ void main() {
|
||||
);
|
||||
|
||||
|
||||
|
||||
test(
|
||||
'sendChatMessage hides OpenClaw artifact guard text from failed results and streaming',
|
||||
() async {
|
||||
@ -1999,7 +2009,10 @@ void main() {
|
||||
route: GoTaskServiceRoute.externalAcpSingle,
|
||||
),
|
||||
);
|
||||
final controller = _connectedController(fakeGoTaskService, homeDir: localWorkspace.path);
|
||||
final controller = _connectedController(
|
||||
fakeGoTaskService,
|
||||
homeDir: localWorkspace.path,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession(
|
||||
@ -2064,7 +2077,10 @@ void main() {
|
||||
route: GoTaskServiceRoute.externalAcpSingle,
|
||||
),
|
||||
);
|
||||
final controller = _connectedController(fakeGoTaskService, homeDir: localWorkspace.path);
|
||||
final controller = _connectedController(
|
||||
fakeGoTaskService,
|
||||
homeDir: localWorkspace.path,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession(
|
||||
@ -2396,7 +2412,10 @@ void main() {
|
||||
}
|
||||
});
|
||||
final fakeGoTaskService = _BlockingGoTaskServiceClient();
|
||||
final controller = _connectedController(fakeGoTaskService, homeDir: localHome.path);
|
||||
final controller = _connectedController(
|
||||
fakeGoTaskService,
|
||||
homeDir: localHome.path,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
const sessionA = 'background-task-a';
|
||||
@ -2518,7 +2537,10 @@ void main() {
|
||||
}
|
||||
});
|
||||
final fakeGoTaskService = _BlockingGoTaskServiceClient();
|
||||
final controller = _connectedController(fakeGoTaskService, homeDir: localHome.path);
|
||||
final controller = _connectedController(
|
||||
fakeGoTaskService,
|
||||
homeDir: localHome.path,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
const prompt = '用户要求我生成一个关于现代AI基础设施的技术营销内容';
|
||||
@ -2686,7 +2708,10 @@ void main() {
|
||||
}
|
||||
});
|
||||
final fakeGoTaskService = _BlockingGoTaskServiceClient();
|
||||
final controller = _connectedController(fakeGoTaskService, homeDir: localHome.path);
|
||||
final controller = _connectedController(
|
||||
fakeGoTaskService,
|
||||
homeDir: localHome.path,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
const prompt = '用户要求我生成一个关于现代AI基础设施的技术营销内容';
|
||||
@ -2780,7 +2805,10 @@ void main() {
|
||||
}
|
||||
});
|
||||
final fakeGoTaskService = _BlockingGoTaskServiceClient();
|
||||
final controller = _connectedController(fakeGoTaskService, homeDir: localHome.path);
|
||||
final controller = _connectedController(
|
||||
fakeGoTaskService,
|
||||
homeDir: localHome.path,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.switchSession('artifact-only-task');
|
||||
@ -2844,7 +2872,10 @@ void main() {
|
||||
}
|
||||
});
|
||||
final fakeGoTaskService = _BlockingGoTaskServiceClient();
|
||||
final controller = _connectedController(fakeGoTaskService, homeDir: localHome.path);
|
||||
final controller = _connectedController(
|
||||
fakeGoTaskService,
|
||||
homeDir: localHome.path,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.switchSession('terminal-failure-task');
|
||||
@ -2921,7 +2952,10 @@ void main() {
|
||||
}
|
||||
});
|
||||
final fakeGoTaskService = _BlockingGoTaskServiceClient();
|
||||
final controller = _connectedController(fakeGoTaskService, homeDir: localHome.path);
|
||||
final controller = _connectedController(
|
||||
fakeGoTaskService,
|
||||
homeDir: localHome.path,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.switchSession('empty-output-task');
|
||||
@ -3233,7 +3267,7 @@ void main() {
|
||||
await expectLater(
|
||||
controller
|
||||
.sendChatMessage(prompts[index])
|
||||
.timeout(const Duration(seconds: 2)),
|
||||
.timeout(_openClawE2ESubmitTimeout),
|
||||
completes,
|
||||
);
|
||||
}
|
||||
@ -3267,7 +3301,10 @@ void main() {
|
||||
'xworkmate-openclaw-five-e2e-',
|
||||
);
|
||||
final fakeGoTaskService = _BlockingGoTaskServiceClient();
|
||||
final controller = _connectedGatewayController(fakeGoTaskService, homeDir: localHome.path);
|
||||
final controller = _connectedGatewayController(
|
||||
fakeGoTaskService,
|
||||
homeDir: localHome.path,
|
||||
);
|
||||
addTearDown(() async {
|
||||
fakeGoTaskService.completeAll();
|
||||
controller.dispose();
|
||||
@ -3286,7 +3323,7 @@ void main() {
|
||||
await expectLater(
|
||||
controller
|
||||
.sendChatMessage(_openClawE2ECanonicalPrompts[index])
|
||||
.timeout(const Duration(seconds: 2)),
|
||||
.timeout(_openClawE2ESubmitTimeout),
|
||||
completes,
|
||||
);
|
||||
}
|
||||
@ -3971,6 +4008,72 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
test('OpenClaw task snapshot failure records a terminal result', () async {
|
||||
final fakeGoTaskService = _RecordingGoTaskServiceClient()
|
||||
..outcomes.add(
|
||||
const GoTaskServiceResult(
|
||||
success: true,
|
||||
message: '',
|
||||
turnId: 'turn-openclaw-poll-failed',
|
||||
raw: <String, dynamic>{
|
||||
'success': true,
|
||||
'status': 'running',
|
||||
'sessionId': 'openclaw-poll-failed-task',
|
||||
'threadId': 'openclaw-poll-failed-task',
|
||||
'turnId': 'turn-openclaw-poll-failed',
|
||||
'runId': 'run-openclaw-poll-failed',
|
||||
'artifactScope':
|
||||
'tasks/openclaw-poll-failed-task/run-openclaw-poll-failed',
|
||||
'artifactDirectory':
|
||||
'/tmp/tasks/openclaw-poll-failed-task/run-openclaw-poll-failed',
|
||||
'gatewayProviderId': 'openclaw',
|
||||
'runtimeBudgetMinutes': 1,
|
||||
},
|
||||
errorMessage: '',
|
||||
resolvedModel: '',
|
||||
route: GoTaskServiceRoute.externalAcpSingle,
|
||||
),
|
||||
)
|
||||
..taskOutcomes.add(
|
||||
const GatewayAcpException(
|
||||
'ACP HTTP connection closed before the OpenClaw task snapshot returned',
|
||||
code: 'ACP_HTTP_CONNECTION_CLOSED',
|
||||
),
|
||||
);
|
||||
final controller = _connectedGatewayController(fakeGoTaskService);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await _selectGatewaySession(controller, 'openclaw-poll-failed-task');
|
||||
|
||||
await expectLater(
|
||||
controller
|
||||
.sendChatMessage('输出 PDF')
|
||||
.timeout(const Duration(seconds: 2)),
|
||||
completes,
|
||||
);
|
||||
|
||||
await Future<void>.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
final failedThread = controller.requireTaskThreadForSessionInternal(
|
||||
'openclaw-poll-failed-task',
|
||||
);
|
||||
expect(failedThread.lifecycleState.status, 'ready');
|
||||
expect(
|
||||
failedThread.lifecycleState.lastResultCode,
|
||||
'ACP_HTTP_CONNECTION_CLOSED',
|
||||
);
|
||||
expect(failedThread.lastArtifactSyncStatus, 'failed');
|
||||
expect(failedThread.openClawTaskAssociation, isNull);
|
||||
expect(
|
||||
controller.assistantSessionHasPendingRun('openclaw-poll-failed-task'),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
controller.chatMessages.map((message) => message.text).join('\n'),
|
||||
contains('ACP_HTTP_CONNECTION_CLOSED'),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'sendChatMessage resumes existing interrupted and error states',
|
||||
() async {
|
||||
@ -4386,7 +4489,8 @@ Future<void> _resilientDelete(Directory dir) async {
|
||||
try {
|
||||
await dir.delete(recursive: true);
|
||||
return;
|
||||
} catch (_) {
|
||||
} catch (error) {
|
||||
debugPrint('Temporary directory delete retry: $error');
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
}
|
||||
}
|
||||
@ -4406,7 +4510,9 @@ AppController _sandboxController({
|
||||
GoTaskServiceClient? goTaskServiceClient,
|
||||
String? homeDir,
|
||||
}) {
|
||||
final actualHome = homeDir ?? Directory.systemTemp.createTempSync('xworkmate-sandbox-home-').path;
|
||||
final actualHome =
|
||||
homeDir ??
|
||||
Directory.systemTemp.createTempSync('xworkmate-sandbox-home-').path;
|
||||
if (homeDir == null) {
|
||||
addTearDown(() async {
|
||||
await _resilientDelete(Directory(actualHome));
|
||||
@ -4429,7 +4535,10 @@ AppController _sandboxController({
|
||||
);
|
||||
}
|
||||
|
||||
AppController _connectedController(GoTaskServiceClient client, {String? homeDir}) {
|
||||
AppController _connectedController(
|
||||
GoTaskServiceClient client, {
|
||||
String? homeDir,
|
||||
}) {
|
||||
return _sandboxController(
|
||||
goTaskServiceClient: client,
|
||||
uiFeatureManifest: _defaultDesktopManifest(),
|
||||
@ -4446,7 +4555,10 @@ AppController _connectedController(GoTaskServiceClient client, {String? homeDir}
|
||||
);
|
||||
}
|
||||
|
||||
AppController _connectedGatewayController(GoTaskServiceClient client, {String? homeDir}) {
|
||||
AppController _connectedGatewayController(
|
||||
GoTaskServiceClient client, {
|
||||
String? homeDir,
|
||||
}) {
|
||||
return _sandboxController(
|
||||
goTaskServiceClient: client,
|
||||
uiFeatureManifest: _defaultDesktopManifest(),
|
||||
@ -4586,6 +4698,7 @@ class _RecordingGoTaskServiceClient implements GoTaskServiceClient {
|
||||
final List<GoTaskServiceUpdate> updatesBeforeNextOutcome =
|
||||
<GoTaskServiceUpdate>[];
|
||||
final List<Object> outcomes = <Object>[];
|
||||
final List<Object> taskOutcomes = <Object>[];
|
||||
Future<void> Function(GoTaskServiceRequest request)? onExecuteTask;
|
||||
|
||||
@override
|
||||
@ -4640,6 +4753,13 @@ class _RecordingGoTaskServiceClient implements GoTaskServiceClient {
|
||||
required OpenClawTaskAssociation association,
|
||||
required GoTaskServiceRoute route,
|
||||
}) async {
|
||||
if (taskOutcomes.isNotEmpty) {
|
||||
final outcome = taskOutcomes.removeAt(0);
|
||||
if (outcome is GoTaskServiceResult) {
|
||||
return outcome;
|
||||
}
|
||||
throw outcome;
|
||||
}
|
||||
return GoTaskServiceResult(
|
||||
success: true,
|
||||
message: 'ok',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user