diff --git a/lib/app/app_controller_desktop_core.dart b/lib/app/app_controller_desktop_core.dart index a7d3eedc..dcf789d8 100644 --- a/lib/app/app_controller_desktop_core.dart +++ b/lib/app/app_controller_desktop_core.dart @@ -137,7 +137,21 @@ class AppController extends ChangeNotifier { GoTaskServiceClient? goTaskServiceClient, MultiAgentMountManager? multiAgentMountManager, }) { - storeInternal = store ?? SecureConfigStore(); + environmentOverrideInternal = environmentOverride == null + ? null + : Map.unmodifiable(environmentOverride); + if (environmentOverrideInternal != null) { + resolvedUserHomeDirectoryInternal = + resolveUserHomeDirectoryFromControllerEnvironmentInternal( + environmentOverrideInternal, + ); + } + storeInternal = + store ?? + createDefaultSecureConfigStoreForControllerEnvironmentInternal( + resolvedUserHomeDirectoryInternal, + environmentOverride: environmentOverrideInternal, + ); uiFeatureManifestInternal = uiFeatureManifest ?? loadRepoUiFeatureManifestSyncInternal(); hostUiFeaturePlatformInternal = Platform.isIOS || Platform.isAndroid @@ -196,15 +210,6 @@ class AppController extends ChangeNotifier { skillDirectoryAccessService ?? createSkillDirectoryAccessService(); singleAgentSharedSkillScanRootOverridesInternal = singleAgentSharedSkillScanRootOverrides?.toList(growable: false); - environmentOverrideInternal = environmentOverride == null - ? null - : Map.unmodifiable(environmentOverride); - if (environmentOverrideInternal != null) { - resolvedUserHomeDirectoryInternal = - resolveUserHomeDirectoryFromControllerEnvironmentInternal( - environmentOverrideInternal, - ); - } gatewayAcpClientInternal = GatewayAcpClient( endpointResolver: resolveGatewayAcpEndpointInternal, authorizationResolver: resolveGatewayAcpAuthorizationHeaderInternal, @@ -739,3 +744,28 @@ String resolveUserHomeDirectoryFromControllerEnvironmentInternal( } return ''; } + +SecureConfigStore +createDefaultSecureConfigStoreForControllerEnvironmentInternal( + String resolvedUserHomeDirectory, { + required Map? environmentOverride, +}) { + if (environmentOverride == null || resolvedUserHomeDirectory.trim().isEmpty) { + return SecureConfigStore(); + } + final supportRoot = + defaultUserSettingsRootPath( + environment: { + ...environmentOverride, + 'HOME': resolvedUserHomeDirectory, + }, + operatingSystem: Platform.operatingSystem, + ) ?? + '${resolvedUserHomeDirectory.trim()}/.xworkmate'; + return SecureConfigStore( + appDataRootPathResolver: () async => supportRoot, + supportRootPathResolver: () async => supportRoot, + secretRootPathResolver: () async => '$supportRoot/secrets', + enableSecureStorage: false, + ); +} diff --git a/lib/app/app_controller_desktop_settings_runtime.dart b/lib/app/app_controller_desktop_settings_runtime.dart index 9145c919..0551bc81 100644 --- a/lib/app/app_controller_desktop_settings_runtime.dart +++ b/lib/app/app_controller_desktop_settings_runtime.dart @@ -471,6 +471,11 @@ extension AppControllerDesktopSettingsRuntime on AppController { await storeInternal.saveAppUiState(sanitizedAppUiState); } final storedAssistantThreads = await storeInternal.loadTaskThreads(); + final sanitizedAssistantThreads = + discardKnownPollutedTestTaskThreadsInternal(storedAssistantThreads); + if (sanitizedAssistantThreads.length != storedAssistantThreads.length) { + await storeInternal.saveTaskThreads(sanitizedAssistantThreads); + } final skippedInvalidThreadRecords = storeInternal.lastSkippedInvalidTaskThreadRecords; startupTaskThreadWarningInternal = skippedInvalidThreadRecords.isEmpty @@ -514,7 +519,7 @@ extension AppControllerDesktopSettingsRuntime on AppController { } catch (_) { // Keep initialization resilient when remote account restore fails. } - restoreAssistantThreadsInternal(storedAssistantThreads); + restoreAssistantThreadsInternal(sanitizedAssistantThreads); await restoreSharedSingleAgentLocalSkillsCacheInternal(); if (disposedInternal) { return; diff --git a/lib/app/app_controller_desktop_thread_storage.dart b/lib/app/app_controller_desktop_thread_storage.dart index 6b88791d..c4f2d265 100644 --- a/lib/app/app_controller_desktop_thread_storage.dart +++ b/lib/app/app_controller_desktop_thread_storage.dart @@ -45,6 +45,31 @@ import 'app_controller_desktop_runtime_helpers.dart'; // ignore_for_file: invalid_use_of_visible_for_testing_member, invalid_use_of_protected_member extension AppControllerDesktopThreadStorage on AppController { + Set knownPollutedTestTaskSessionKeysInternal() => { + 'draft' + ':unit-task-a', + 'draft' + ':test-task-a', + 'test-fixture:unit-task-a', + 'test-fixture:test-task-a', + }; + + bool isKnownPollutedTestTaskSessionKeyInternal(String sessionKey) { + final normalized = normalizedAssistantSessionKeyInternal(sessionKey); + return knownPollutedTestTaskSessionKeysInternal().contains(normalized); + } + + List discardKnownPollutedTestTaskThreadsInternal( + List records, + ) { + return records + .where( + (record) => + !isKnownPollutedTestTaskSessionKeyInternal(record.sessionKey), + ) + .toList(growable: false); + } + Future applyPersistedAiGatewaySettingsInternal( SettingsSnapshot snapshot, ) async { @@ -205,7 +230,14 @@ extension AppControllerDesktopThreadStorage on AppController { ); }) .toList(growable: false); - return state.copyWith(assistantNavigationDestinations: allowedNavigation); + final assistantLastSessionKey = + isKnownPollutedTestTaskSessionKeyInternal(state.assistantLastSessionKey) + ? '' + : state.assistantLastSessionKey; + return state.copyWith( + assistantLastSessionKey: assistantLastSessionKey, + assistantNavigationDestinations: allowedNavigation, + ); } SettingsSnapshot sanitizeOllamaCloudSettingsInternal( @@ -716,6 +748,9 @@ extension AppControllerDesktopThreadStorage on AppController { if (sessionKey.isEmpty) { continue; } + if (isKnownPollutedTestTaskSessionKeyInternal(sessionKey)) { + continue; + } if (!record.workspaceBinding.isComplete) { continue; } diff --git a/test/features/assistant/assistant_artifact_sidebar_test.dart b/test/features/assistant/assistant_artifact_sidebar_test.dart index f6ae4ad1..9de0587e 100644 --- a/test/features/assistant/assistant_artifact_sidebar_test.dart +++ b/test/features/assistant/assistant_artifact_sidebar_test.dart @@ -216,7 +216,7 @@ Widget _buildTestApp({ width: 460, height: 640, child: AssistantArtifactSidebar( - sessionKey: 'draft:unit-task-a', + sessionKey: 'unit-fixture-task-a', threadTitle: 'Thread', workspacePath: '/tmp/thread', workspaceKind: WorkspaceRefKind.localPath, diff --git a/test/features/assistant/assistant_lower_pane_test.dart b/test/features/assistant/assistant_lower_pane_test.dart index d1291b2b..527a27bd 100644 --- a/test/features/assistant/assistant_lower_pane_test.dart +++ b/test/features/assistant/assistant_lower_pane_test.dart @@ -18,7 +18,9 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await tester.pumpWidget( _buildTestApp(child: _buildLowerPane(controller: controller)), @@ -79,7 +81,7 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession('unit-fixture-task-a'); await tester.pumpWidget( _buildTestApp(child: _buildLowerPane(controller: controller)), @@ -113,7 +115,7 @@ void main() { ); final gatewayThread = controller - .requireTaskThreadForSessionInternal('draft:unit-task-a') + .requireTaskThreadForSessionInternal('unit-fixture-task-a') .copyWith( executionBinding: ExecutionBinding( executionMode: threadExecutionModeFromAssistantExecutionTarget( @@ -154,7 +156,7 @@ void main() { await tester.pumpAndSettle(); final agentThread = controller - .requireTaskThreadForSessionInternal('draft:unit-task-a') + .requireTaskThreadForSessionInternal('unit-fixture-task-a') .copyWith( executionBinding: ExecutionBinding( executionMode: threadExecutionModeFromAssistantExecutionTarget( @@ -216,7 +218,7 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession('unit-fixture-task-a'); await tester.pumpWidget( _buildTestApp(child: _buildLowerPane(controller: controller)), @@ -277,12 +279,12 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession('unit-fixture-task-a'); controller.initializeAssistantThreadContext( - 'draft:unit-task-a', + 'unit-fixture-task-a', executionTarget: AssistantExecutionTarget.gateway, messageViewMode: controller.assistantMessageViewModeForSession( - 'draft:unit-task-a', + 'unit-fixture-task-a', ), ); controller.notifyListeners(); @@ -331,12 +333,14 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); controller.initializeAssistantThreadContext( - 'draft:unit-task-a', + 'unit-fixture-task-a', executionTarget: AssistantExecutionTarget.gateway, messageViewMode: controller.assistantMessageViewModeForSession( - 'draft:unit-task-a', + 'unit-fixture-task-a', ), ); controller.notifyListeners(); @@ -367,7 +371,7 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession('unit-fixture-task-a'); var sendCount = 0; diff --git a/test/runtime/app_controller_thread_workspace_binding_test.dart b/test/runtime/app_controller_thread_workspace_binding_test.dart index 520dde17..b4f72dac 100644 --- a/test/runtime/app_controller_thread_workspace_binding_test.dart +++ b/test/runtime/app_controller_thread_workspace_binding_test.dart @@ -8,8 +8,112 @@ import 'package:xworkmate/app/app_controller_desktop_thread_binding.dart'; import 'package:xworkmate/runtime/assistant_artifacts.dart'; import 'package:xworkmate/runtime/go_task_service_client.dart'; import 'package:xworkmate/runtime/runtime_models.dart'; +import 'package:xworkmate/runtime/secure_config_store.dart'; void main() { + test( + 'startup removes known test task pollution and preserves real history', + () async { + final storeRoot = await Directory.systemTemp.createTemp( + 'xworkmate-test-pollution-store-', + ); + addTearDown(() async { + if (await storeRoot.exists()) { + await storeRoot.delete(recursive: true); + } + }); + + final store = _RecordingSecureConfigStore(rootPath: storeRoot.path); + await store.initialize(); + final pollutedSessionKey = _pollutedUnitSessionKey(); + const realSessionKey = 'real-history-session'; + await store.saveTaskThreads([ + _persistedThread( + sessionKey: pollutedSessionKey, + title: 'Unit test fixture', + workspacePath: + '${storeRoot.path}/home/.xworkmate/threads/${_pollutedUnitWorkspaceName()}', + ), + _persistedThread( + sessionKey: realSessionKey, + title: 'Real history task', + workspacePath: + '${storeRoot.path}/home/.xworkmate/threads/real-history-session', + ), + ]); + await store.saveAppUiState( + AppUiState.defaults().copyWith( + assistantLastSessionKey: pollutedSessionKey, + ), + ); + + final controller = AppController( + store: store, + environmentOverride: const {}, + ); + addTearDown(controller.dispose); + + await _waitForControllerInitialization(controller); + + expect( + controller.taskThreadForSessionInternal(pollutedSessionKey), + isNull, + ); + expect( + controller.assistantSessions.map((item) => item.key), + allOf(contains(realSessionKey), isNot(contains(pollutedSessionKey))), + ); + expect(controller.currentSessionKey, isNot(pollutedSessionKey)); + expect(controller.appUiState.assistantLastSessionKey, isEmpty); + expect(store.clearAssistantLocalStateCalled, isFalse); + + final persistedThreadIds = (await store.loadTaskThreads()) + .map((thread) => thread.threadId) + .toList(growable: false); + expect(persistedThreadIds, [realSessionKey]); + expect((await store.loadAppUiState()).assistantLastSessionKey, isEmpty); + }, + ); + + test('source tree does not contain known real draft test fixtures', () async { + final blocked = [ + _pollutedUnitSessionKey(), + _pollutedTestSessionKey(), + _pollutedUnitWorkspaceName(), + _pollutedTestWorkspaceName(), + ]; + final roots = ['lib', 'test', 'scripts', 'docs']; + final violations = []; + for (final root in roots) { + final directory = Directory(root); + if (!await directory.exists()) { + continue; + } + await for (final entity in directory.list(recursive: true)) { + if (entity is! File) { + continue; + } + final path = entity.path; + if (path.contains('/build/') || path.contains('/.dart_tool/')) { + continue; + } + String content; + try { + content = await entity.readAsString(); + } catch (_) { + continue; + } + for (final fixture in blocked) { + if (content.contains(fixture)) { + violations.add('$path contains $fixture'); + } + } + } + } + + expect(violations, isEmpty); + }); + test( 'empty environment override keeps thread workspaces out of real HOME', () async { @@ -19,19 +123,21 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession('unit-fixture-task-a'); expect(controller.userHomeDirectory, isNot(isEmpty)); if (realHome.isNotEmpty) { expect(controller.userHomeDirectory, isNot(realHome)); } expect( - controller.localThreadWorkspacePathInternal('draft:unit-task-a'), - isNot(contains('$realHome/.xworkmate/threads/draft-unit-task-a')), + controller.localThreadWorkspacePathInternal('unit-fixture-task-a'), + isNot(contains('$realHome/.xworkmate/threads/unit-fixture-task-a')), ); expect( - controller.localThreadWorkspaceDisplayPathInternal('draft:unit-task-a'), - '\$HOME/.xworkmate/threads/draft-unit-task-a', + controller.localThreadWorkspaceDisplayPathInternal( + 'unit-fixture-task-a', + ), + '\$HOME/.xworkmate/threads/unit-fixture-task-a', ); }, ); @@ -212,9 +318,9 @@ void main() { }); controller.upsertTaskThreadInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', workspaceBinding: WorkspaceBinding( - workspaceId: 'draft:unit-task-a', + workspaceId: 'unit-fixture-task-a', workspaceKind: WorkspaceKind.localFs, workspacePath: localWorkspace.path, displayPath: localWorkspace.path, @@ -227,21 +333,21 @@ void main() { expect( assistantWorkingDirectoryForSessionRuntimeInternal( controller, - 'draft:unit-task-a', + 'unit-fixture-task-a', ), localWorkspace.path, ); expect( resolveLocalAssistantWorkingDirectoryForSessionRuntimeInternal( controller, - 'draft:unit-task-a', + 'unit-fixture-task-a', ), localWorkspace.path, ); expect( assistantRemoteWorkingDirectoryHintForSessionRuntimeInternal( controller, - 'draft:unit-task-a', + 'unit-fixture-task-a', ), remoteWorkspace.path, ); @@ -298,9 +404,9 @@ void main() { }); controller.upsertTaskThreadInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', workspaceBinding: WorkspaceBinding( - workspaceId: 'draft:unit-task-a', + workspaceId: 'unit-fixture-task-a', workspaceKind: WorkspaceKind.localFs, workspacePath: localWorkspace.path, displayPath: localWorkspace.path, @@ -327,20 +433,20 @@ void main() { ); await controller.persistGoTaskArtifactsForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', result, ); final artifact = File('${localWorkspace.path}/notes/hello.txt'); expect(await artifact.readAsString(), 'artifact body'); await controller.persistGoTaskArtifactsForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', result, ); final versionedArtifact = File('${localWorkspace.path}/notes/hello.v2.txt'); expect(await versionedArtifact.readAsString(), 'artifact body'); final snapshot = await controller.loadAssistantArtifactSnapshot( - sessionKey: 'draft:unit-task-a', + sessionKey: 'unit-fixture-task-a', ); expect(snapshot.resultEntries.map((entry) => entry.relativePath), [ 'notes/hello.v2.txt', @@ -351,7 +457,7 @@ void main() { ); expect( controller - .requireTaskThreadForSessionInternal('draft:unit-task-a') + .requireTaskThreadForSessionInternal('unit-fixture-task-a') .lastArtifactSyncStatus, 'synced', ); @@ -377,9 +483,9 @@ void main() { await staleArtifact.writeAsString('stale task output'); controller.upsertTaskThreadInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', workspaceBinding: WorkspaceBinding( - workspaceId: 'draft:unit-task-a', + workspaceId: 'unit-fixture-task-a', workspaceKind: WorkspaceKind.localFs, workspacePath: localWorkspace.path, displayPath: localWorkspace.path, @@ -406,12 +512,12 @@ void main() { ); await controller.persistGoTaskArtifactsForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', result, ); final snapshot = await controller.loadAssistantArtifactSnapshot( - sessionKey: 'draft:unit-task-a', + sessionKey: 'unit-fixture-task-a', ); final currentRelativePaths = snapshot.resultEntries .map((entry) => entry.relativePath) @@ -432,7 +538,7 @@ void main() { previewable: true, workspacePath: localWorkspace.path, ), - sessionKey: 'draft:unit-task-a', + sessionKey: 'unit-fixture-task-a', ); expect(stalePreview.kind, AssistantArtifactPreviewKind.markdown); expect(stalePreview.content, 'stale task output'); @@ -472,9 +578,9 @@ void main() { }); controller.upsertTaskThreadInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', workspaceBinding: WorkspaceBinding( - workspaceId: 'draft:unit-task-a', + workspaceId: 'unit-fixture-task-a', workspaceKind: WorkspaceKind.localFs, workspacePath: localWorkspace.path, displayPath: localWorkspace.path, @@ -504,7 +610,7 @@ void main() { final clientFactory = _proxiedClientFactory(server.port); await HttpOverrides.runZoned(() async { await controller.persistGoTaskArtifactsForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', result, ); }, createHttpClient: clientFactory); @@ -513,7 +619,7 @@ void main() { expect(await artifact.readAsString(), 'downloaded artifact body'); expect(observedAuthorization, 'Bearer bridge-token'); final snapshot = await controller.loadAssistantArtifactSnapshot( - sessionKey: 'draft:unit-task-a', + sessionKey: 'unit-fixture-task-a', ); expect( snapshot.fileEntries.map((entry) => entry.relativePath), @@ -521,7 +627,7 @@ void main() { ); expect( controller - .requireTaskThreadForSessionInternal('draft:unit-task-a') + .requireTaskThreadForSessionInternal('unit-fixture-task-a') .lastArtifactSyncStatus, 'synced', ); @@ -710,9 +816,9 @@ void main() { } }); controller.upsertTaskThreadInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', workspaceBinding: WorkspaceBinding( - workspaceId: 'draft:unit-task-a', + workspaceId: 'unit-fixture-task-a', workspaceKind: WorkspaceKind.localFs, workspacePath: localWorkspace.path, displayPath: localWorkspace.path, @@ -730,7 +836,7 @@ void main() { 'relativePath': 'reports/resume.bin', 'downloadUrl': 'http://xworkmate-bridge.svc.plus:${server.port}/artifacts/openclaw/download' - '?sessionKey=draft:unit-task-a&runId=run-1&relativePath=reports%2Fresume.bin' + '?sessionKey=unit-fixture-task-a&runId=run-1&relativePath=reports%2Fresume.bin' '&expires=9999999999&sig=test-signature', 'contentType': 'application/octet-stream', 'sizeBytes': body.length, @@ -746,7 +852,7 @@ void main() { final clientFactory = _proxiedClientFactory(server.port); await HttpOverrides.runZoned(() async { await controller.persistGoTaskArtifactsForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', result, ); }, createHttpClient: clientFactory); @@ -759,7 +865,7 @@ void main() { ); expect( controller - .requireTaskThreadForSessionInternal('draft:unit-task-a') + .requireTaskThreadForSessionInternal('unit-fixture-task-a') .lastArtifactSyncStatus, 'synced', ); @@ -814,9 +920,9 @@ void main() { } }); controller.upsertTaskThreadInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', workspaceBinding: WorkspaceBinding( - workspaceId: 'draft:unit-task-a', + workspaceId: 'unit-fixture-task-a', workspaceKind: WorkspaceKind.localFs, workspacePath: localWorkspace.path, displayPath: localWorkspace.path, @@ -846,7 +952,7 @@ void main() { final clientFactory = _proxiedClientFactory(server.port); await HttpOverrides.runZoned(() async { await controller.persistGoTaskArtifactsForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', result, ); }, createHttpClient: clientFactory); @@ -858,7 +964,7 @@ void main() { ); expect( controller - .requireTaskThreadForSessionInternal('draft:unit-task-a') + .requireTaskThreadForSessionInternal('unit-fixture-task-a') .lastArtifactSyncStatus, 'synced', ); @@ -897,9 +1003,9 @@ void main() { } }); controller.upsertTaskThreadInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', workspaceBinding: WorkspaceBinding( - workspaceId: 'draft:unit-task-a', + workspaceId: 'unit-fixture-task-a', workspaceKind: WorkspaceKind.localFs, workspacePath: localWorkspace.path, displayPath: localWorkspace.path, @@ -940,7 +1046,7 @@ void main() { final clientFactory = _proxiedClientFactory(server.port); await HttpOverrides.runZoned(() async { await controller.persistGoTaskArtifactsForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', result, ); }, createHttpClient: clientFactory); @@ -958,7 +1064,7 @@ void main() { isFalse, ); final snapshot = await controller.loadAssistantArtifactSnapshot( - sessionKey: 'draft:unit-task-a', + sessionKey: 'unit-fixture-task-a', ); expect( snapshot.fileEntries.map((entry) => entry.relativePath), @@ -966,7 +1072,7 @@ void main() { ); expect( controller - .requireTaskThreadForSessionInternal('draft:unit-task-a') + .requireTaskThreadForSessionInternal('unit-fixture-task-a') .lastArtifactSyncStatus, 'partial', ); @@ -999,9 +1105,9 @@ void main() { } }); controller.upsertTaskThreadInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', workspaceBinding: WorkspaceBinding( - workspaceId: 'draft:unit-task-a', + workspaceId: 'unit-fixture-task-a', workspaceKind: WorkspaceKind.localFs, workspacePath: localWorkspace.path, displayPath: localWorkspace.path, @@ -1034,7 +1140,7 @@ void main() { final clientFactory = _proxiedClientFactory(server.port); await HttpOverrides.runZoned(() async { await controller.persistGoTaskArtifactsForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', result, ); }, createHttpClient: clientFactory); @@ -1050,7 +1156,7 @@ void main() { expect(leftovers, isEmpty); expect( controller - .requireTaskThreadForSessionInternal('draft:unit-task-a') + .requireTaskThreadForSessionInternal('unit-fixture-task-a') .lastArtifactSyncStatus, 'download-failed', ); @@ -1073,9 +1179,9 @@ void main() { } }); controller.upsertTaskThreadInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', workspaceBinding: WorkspaceBinding( - workspaceId: 'draft:unit-task-a', + workspaceId: 'unit-fixture-task-a', workspaceKind: WorkspaceKind.localFs, workspacePath: localWorkspace.path, displayPath: localWorkspace.path, @@ -1095,13 +1201,13 @@ void main() { ); await controller.persistGoTaskArtifactsForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', result, ); expect(await localWorkspace.list(recursive: true).toList(), isEmpty); final thread = controller.requireTaskThreadForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ); expect(thread.lastArtifactSyncStatus, 'no-exported-artifacts'); expect(thread.lastArtifactSyncAtMs, greaterThan(0)); @@ -1125,9 +1231,9 @@ void main() { final staleArtifact = File('${localWorkspace.path}/old-task-report.md'); await staleArtifact.writeAsString('stale task output'); controller.upsertTaskThreadInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', workspaceBinding: WorkspaceBinding( - workspaceId: 'draft:unit-task-a', + workspaceId: 'unit-fixture-task-a', workspaceKind: WorkspaceKind.localFs, workspacePath: localWorkspace.path, displayPath: localWorkspace.path, @@ -1146,18 +1252,18 @@ void main() { ); await controller.persistGoTaskArtifactsForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', result, ); expect( controller - .requireTaskThreadForSessionInternal('draft:unit-task-a') + .requireTaskThreadForSessionInternal('unit-fixture-task-a') .lastArtifactSyncStatus, 'no-artifacts', ); final snapshot = await controller.loadAssistantArtifactSnapshot( - sessionKey: 'draft:unit-task-a', + sessionKey: 'unit-fixture-task-a', ); expect(snapshot.resultEntries, isEmpty); expect( @@ -1184,9 +1290,9 @@ void main() { }); controller.upsertTaskThreadInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', workspaceBinding: WorkspaceBinding( - workspaceId: 'draft:unit-task-a', + workspaceId: 'unit-fixture-task-a', workspaceKind: WorkspaceKind.localFs, workspacePath: localWorkspace.path, displayPath: localWorkspace.path, @@ -1213,7 +1319,7 @@ void main() { ); await controller.persistGoTaskArtifactsForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', result, ); @@ -1223,7 +1329,7 @@ void main() { ); expect( controller - .requireTaskThreadForSessionInternal('draft:unit-task-a') + .requireTaskThreadForSessionInternal('unit-fixture-task-a') .lastArtifactSyncStatus, 'no-artifacts', ); @@ -1238,3 +1344,66 @@ HttpClient Function(SecurityContext?) _proxiedClientFactory(int port) { var index = 0; return (_) => clients[index++]; } + +String _pollutedUnitSessionKey() => + 'draft' + ':unit-task-a'; +String _pollutedTestSessionKey() => + 'draft' + ':test-task-a'; +String _pollutedUnitWorkspaceName() => + 'draft' + '-unit-task-a'; +String _pollutedTestWorkspaceName() => + 'draft' + '-test-task-a'; + +TaskThread _persistedThread({ + required String sessionKey, + required String title, + required String workspacePath, +}) { + return TaskThread( + threadId: sessionKey, + title: title, + workspaceBinding: WorkspaceBinding( + workspaceId: sessionKey, + workspaceKind: WorkspaceKind.localFs, + workspacePath: workspacePath, + displayPath: workspacePath, + writable: true, + ), + executionBinding: const ExecutionBinding( + executionMode: ThreadExecutionMode.gateway, + executorId: 'openclaw', + providerId: 'openclaw', + endpointId: '', + ), + ); +} + +class _RecordingSecureConfigStore extends SecureConfigStore { + _RecordingSecureConfigStore({required String rootPath}) + : super( + secretRootPathResolver: () async => '$rootPath/secrets', + appDataRootPathResolver: () async => '$rootPath/app-data', + supportRootPathResolver: () async => '$rootPath/support', + enableSecureStorage: false, + ); + + bool clearAssistantLocalStateCalled = false; + + @override + Future clearAssistantLocalState() async { + clearAssistantLocalStateCalled = true; + await super.clearAssistantLocalState(); + } +} + +Future _waitForControllerInitialization(AppController controller) async { + final deadline = DateTime.now().add(const Duration(seconds: 5)); + while (controller.initializing && DateTime.now().isBefore(deadline)) { + await Future.delayed(const Duration(milliseconds: 20)); + } + expect(controller.initializing, isFalse); +} diff --git a/test/runtime/assistant_connection_state_test.dart b/test/runtime/assistant_connection_state_test.dart index 96badfd2..54f2bfa3 100644 --- a/test/runtime/assistant_connection_state_test.dart +++ b/test/runtime/assistant_connection_state_test.dart @@ -43,7 +43,9 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.gateway, ); @@ -75,7 +77,9 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.gateway, ); @@ -108,7 +112,9 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.gateway, ); @@ -271,7 +277,7 @@ void main() { final controller = await _isolatedController(); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession('unit-fixture-task-a'); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.gateway, ); @@ -298,7 +304,7 @@ void main() { final controller = await _isolatedController(); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession('unit-fixture-task-a'); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.gateway, ); @@ -320,7 +326,9 @@ void main() { final controller = await _isolatedController(); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.gateway, ); @@ -350,7 +358,9 @@ void main() { final controller = await _isolatedController(); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.gateway, ); @@ -380,7 +390,9 @@ void main() { final controller = await _isolatedController(); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.gateway, ); @@ -407,7 +419,7 @@ void main() { final controller = await _isolatedController(); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession('unit-fixture-task-a'); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.gateway, ); diff --git a/test/runtime/assistant_execution_target_test.dart b/test/runtime/assistant_execution_target_test.dart index b1eb94d4..d0a19873 100644 --- a/test/runtime/assistant_execution_target_test.dart +++ b/test/runtime/assistant_execution_target_test.dart @@ -88,13 +88,15 @@ void main() { ], ); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.gateway, ); expect( - controller.assistantProviderForSession('draft:unit-task-a'), + controller.assistantProviderForSession('unit-fixture-task-a'), SingleAgentProvider.openclaw, ); }, @@ -125,7 +127,9 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); expect(controller.currentAssistantExecutionTarget.isAgent, isTrue); expect( @@ -138,14 +142,14 @@ void main() { ); final record = controller.requireTaskThreadForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ); expect( record.executionBinding.executionMode, ThreadExecutionMode.gateway, ); expect( - controller.assistantProviderForSession('draft:unit-task-a'), + controller.assistantProviderForSession('unit-fixture-task-a'), SingleAgentProvider.openclaw, ); }, @@ -396,17 +400,19 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.gateway, ); final record = controller.requireTaskThreadForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ); expect( - controller.assistantExecutionTargetForSession('draft:unit-task-a'), + controller.assistantExecutionTargetForSession('unit-fixture-task-a'), AssistantExecutionTarget.gateway, ); expect(record.executionBinding.providerId, isEmpty); @@ -430,13 +436,15 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.gateway, ); final routing = controller.buildExternalAcpRoutingForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ); expect(routing.mode, ExternalCodeAgentAcpRoutingMode.explicit); @@ -565,7 +573,9 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await Future.delayed(const Duration(milliseconds: 200)); expect(controller.assistantProviderCatalog, isEmpty); @@ -627,7 +637,9 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.gateway, ); @@ -738,7 +750,9 @@ void main() { ), ); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.agent, ); @@ -776,16 +790,18 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); expect( controller.hasCommittedUserTurnForGatewaySessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ), isFalse, ); controller.appendLocalSessionMessageInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', GatewayChatMessage( id: 'error-1', role: 'assistant', @@ -802,13 +818,13 @@ void main() { expect( controller.hasCommittedUserTurnForGatewaySessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ), isFalse, ); controller.appendLocalSessionMessageInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', GatewayChatMessage( id: 'assistant-1', role: 'assistant', @@ -825,13 +841,13 @@ void main() { expect( controller.hasCommittedUserTurnForGatewaySessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ), isFalse, ); controller.appendLocalSessionMessageInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', GatewayChatMessage( id: 'user-1', role: 'user', @@ -848,24 +864,24 @@ void main() { expect( controller.hasCommittedUserTurnForGatewaySessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ), isTrue, ); expect( controller.shouldResumeGatewaySessionForNextSendInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ), isTrue, ); controller.upsertTaskThreadInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', lastResultCode: gatewayAcpHttpConnectTimeoutCode, ); expect( controller.shouldResumeGatewaySessionForNextSendInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ), isFalse, ); @@ -877,7 +893,7 @@ void main() { final controller = _connectedController(fakeGoTaskService); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession('unit-fixture-task-a'); await controller.sendChatMessage('first turn'); @@ -899,8 +915,8 @@ void main() { final fakeGoTaskService = _RecordingGoTaskServiceClient() ..updatesBeforeNextOutcome.add( const GoTaskServiceUpdate( - sessionId: 'draft:unit-task-a', - threadId: 'draft:unit-task-a', + sessionId: 'unit-fixture-task-a', + threadId: 'unit-fixture-task-a', turnId: 'turn-1', type: 'delta', text: 'partial output that must not persist', @@ -932,7 +948,9 @@ void main() { addTearDown(controller.dispose); controller.resolvedUserHomeDirectoryInternal = localWorkspace.path; - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.sendChatMessage('first turn'); @@ -940,7 +958,7 @@ void main() { expect(fakeGoTaskService.requests.single.resumeSession, isFalse); expect( controller - .taskThreadForSessionInternal('draft:unit-task-a') + .taskThreadForSessionInternal('unit-fixture-task-a') ?.lifecycleState .status, 'ready', @@ -955,7 +973,7 @@ void main() { ); expect( controller - .taskThreadForSessionInternal('draft:unit-task-a') + .taskThreadForSessionInternal('unit-fixture-task-a') ?.lastArtifactSyncStatus, 'failed', ); @@ -965,19 +983,19 @@ void main() { expect(fakeGoTaskService.requests, hasLength(2)); expect(fakeGoTaskService.requests.last.resumeSession, isTrue); expect( - controller.localSessionMessagesInternal['draft:unit-task-a']!.map( + controller.localSessionMessagesInternal['unit-fixture-task-a']!.map( (message) => message.text, ), contains('全部 6 个文件已生成 ✅'), ); final thread = controller.taskThreadForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ); expect(thread?.lifecycleState.status, 'ready'); expect(thread?.lastArtifactSyncStatus, 'synced'); expect(thread?.lastArtifactSyncAtMs, greaterThan(0)); final workspacePath = controller.assistantWorkspacePathForSession( - 'draft:unit-task-a', + 'unit-fixture-task-a', ); for (final artifact in _generatedArtifactPayloads()) { final relativePath = artifact['relativePath']! as String; @@ -1014,14 +1032,16 @@ void main() { final controller = _connectedController(fakeGoTaskService); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.sendChatMessage('first turn'); expect(fakeGoTaskService.requests, hasLength(1)); expect(fakeGoTaskService.requests.single.resumeSession, isFalse); final failedThread = controller.taskThreadForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ); expect(failedThread?.lifecycleState.status, 'ready'); expect( @@ -1048,7 +1068,7 @@ void main() { 'retried from a confirmed new start', ); final thread = controller.taskThreadForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ); expect(thread?.lifecycleState.status, 'ready'); expect(thread?.lifecycleState.lastResultCode, 'success'); @@ -1071,8 +1091,8 @@ void main() { final fakeGoTaskService = _RecordingGoTaskServiceClient() ..updatesBeforeNextOutcome.add( const GoTaskServiceUpdate( - sessionId: 'draft:unit-task-a', - threadId: 'draft:unit-task-a', + sessionId: 'unit-fixture-task-a', + threadId: 'unit-fixture-task-a', turnId: 'turn-1', type: 'delta', text: 'guard partial output must not persist', @@ -1104,7 +1124,9 @@ void main() { addTearDown(controller.dispose); controller.resolvedUserHomeDirectoryInternal = localWorkspace.path; - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.sendChatMessage('first turn'); await controller.sendChatMessage('follow up'); @@ -1124,7 +1146,7 @@ void main() { ); final thread = controller.taskThreadForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ); expect(thread?.lifecycleState.status, 'ready'); expect(thread?.lastArtifactSyncStatus, 'no-exported-artifacts'); @@ -1148,8 +1170,8 @@ void main() { final fakeGoTaskService = _RecordingGoTaskServiceClient() ..updatesBeforeNextOutcome.add( const GoTaskServiceUpdate( - sessionId: 'draft:unit-task-a', - threadId: 'draft:unit-task-a', + sessionId: 'unit-fixture-task-a', + threadId: 'unit-fixture-task-a', turnId: 'turn-1', type: 'delta', text: guardMessage, @@ -1181,7 +1203,9 @@ void main() { addTearDown(controller.dispose); controller.resolvedUserHomeDirectoryInternal = localWorkspace.path; - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.sendChatMessage('create files'); final transcript = controller.chatMessages @@ -1190,7 +1214,7 @@ void main() { expect(transcript, isNot(contains('未检测到 OpenClaw 本轮导出的实际文件'))); expect(transcript, isNot(contains('口头下载声明'))); final thread = controller.taskThreadForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ); expect(thread?.lifecycleState.lastResultCode, 'artifact_missing'); expect(thread?.lastArtifactSyncStatus, 'no-exported-artifacts'); @@ -1212,8 +1236,8 @@ void main() { final fakeGoTaskService = _RecordingGoTaskServiceClient() ..updatesBeforeNextOutcome.add( const GoTaskServiceUpdate( - sessionId: 'draft:unit-task-a', - threadId: 'draft:unit-task-a', + sessionId: 'unit-fixture-task-a', + threadId: 'unit-fixture-task-a', turnId: 'turn-1', type: 'delta', text: 'handshake partial output must not persist', @@ -1245,14 +1269,16 @@ void main() { addTearDown(controller.dispose); controller.resolvedUserHomeDirectoryInternal = localWorkspace.path; - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.sendChatMessage('first turn'); expect(fakeGoTaskService.requests, hasLength(1)); expect(fakeGoTaskService.requests.single.resumeSession, isFalse); final failedThread = controller.taskThreadForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ); expect(failedThread?.lifecycleState.status, 'ready'); expect( @@ -1276,13 +1302,13 @@ void main() { await _waitForLastChatMessageText(controller, '全部 6 个文件已生成 ✅'); expect(controller.chatMessages.last.text, '全部 6 个文件已生成 ✅'); final thread = controller.taskThreadForSessionInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', ); expect(thread?.lifecycleState.status, 'ready'); expect(thread?.lastArtifactSyncStatus, 'synced'); expect(thread?.lastArtifactSyncAtMs, greaterThan(0)); final workspacePath = controller.assistantWorkspacePathForSession( - 'draft:unit-task-a', + 'unit-fixture-task-a', ); for (final artifact in _generatedArtifactPayloads()) { final relativePath = artifact['relativePath']! as String; @@ -1303,7 +1329,9 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); final userMessage = GatewayChatMessage( id: 'local-user-1', @@ -1329,19 +1357,19 @@ void main() { ); controller.appendLocalSessionMessageInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', userMessage, persistInThreadContext: true, ); controller.appendLocalSessionMessageInternal( - 'draft:unit-task-a', + 'unit-fixture-task-a', assistantMessage, persistInThreadContext: true, ); - controller.assistantThreadMessagesInternal['draft:unit-task-a'] = + controller.assistantThreadMessagesInternal['unit-fixture-task-a'] = List.from( controller - .requireTaskThreadForSessionInternal('draft:unit-task-a') + .requireTaskThreadForSessionInternal('unit-fixture-task-a') .messages, ); diff --git a/test/runtime/assistant_model_display_test.dart b/test/runtime/assistant_model_display_test.dart index de75142c..2e477fa3 100644 --- a/test/runtime/assistant_model_display_test.dart +++ b/test/runtime/assistant_model_display_test.dart @@ -10,7 +10,7 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession('unit-fixture-task-a'); expect(controller.resolvedAssistantModel, isNotEmpty); expect(controller.assistantModelChoices, isEmpty); @@ -30,7 +30,9 @@ void main() { ); addTearDown(controller.dispose); - await controller.sessionsController.switchSession('draft:unit-task-a'); + await controller.sessionsController.switchSession( + 'unit-fixture-task-a', + ); await controller.setAssistantExecutionTarget( AssistantExecutionTarget.gateway, ); diff --git a/test/runtime/gateway_acp_client_auth_test.dart b/test/runtime/gateway_acp_client_auth_test.dart index c6890aa4..841d92d2 100644 --- a/test/runtime/gateway_acp_client_auth_test.dart +++ b/test/runtime/gateway_acp_client_auth_test.dart @@ -575,8 +575,8 @@ void main() { 'jsonrpc': '2.0', 'method': 'session.update', 'params': { - 'sessionId': 'draft:unit-task-a', - 'threadId': 'draft:unit-task-a', + 'sessionId': 'unit-fixture-task-a', + 'threadId': 'unit-fixture-task-a', 'turnId': 'turn-1', 'type': 'status', 'event': 'completed', @@ -592,7 +592,7 @@ void main() { 'relativePath': 'exports/final.md', 'downloadUrl': 'https://xworkmate-bridge.svc.plus/artifacts/openclaw/download' - '?sessionKey=draft:unit-task-a&runId=turn-1&relativePath=exports%2Ffinal.md', + '?sessionKey=unit-fixture-task-a&runId=turn-1&relativePath=exports%2Ffinal.md', 'contentType': 'text/markdown', 'sizeBytes': 42, }, @@ -620,8 +620,8 @@ void main() { final result = await transport.executeTask( const GoTaskServiceRequest( - sessionId: 'draft:unit-task-a', - threadId: 'draft:unit-task-a', + sessionId: 'unit-fixture-task-a', + threadId: 'unit-fixture-task-a', target: AssistantExecutionTarget.gateway, provider: SingleAgentProvider.openclaw, prompt: 'create files', @@ -660,7 +660,7 @@ void main() { final event = jsonEncode({ 'jsonrpc': '2.0', 'method': 'xworkmate.bridge.accepted', - 'params': {'sessionId': 'draft:unit-task-a'}, + 'params': {'sessionId': 'unit-fixture-task-a'}, }); final eventBytes = utf8.encode('data: $event\n\n'); request.response.headers.set( @@ -682,8 +682,8 @@ void main() { 'id': id, 'result': { 'status': 'completed', - 'sessionId': 'draft:unit-task-a', - 'threadId': 'draft:unit-task-a', + 'sessionId': 'unit-fixture-task-a', + 'threadId': 'unit-fixture-task-a', 'task': { 'state': 'completed', 'turnId': 'turn-recovered', @@ -697,7 +697,7 @@ void main() { 'relativePath': 'exports/snapshot.md', 'downloadUrl': 'https://xworkmate-bridge.svc.plus/artifacts/openclaw/download' - '?sessionKey=draft:unit-task-a&runId=turn-recovered&relativePath=exports%2Fsnapshot.md', + '?sessionKey=unit-fixture-task-a&runId=turn-recovered&relativePath=exports%2Fsnapshot.md', 'contentType': 'text/markdown', 'sizeBytes': 64, }, @@ -723,8 +723,8 @@ void main() { final result = await transport.executeTask( const GoTaskServiceRequest( - sessionId: 'draft:unit-task-a', - threadId: 'draft:unit-task-a', + sessionId: 'unit-fixture-task-a', + threadId: 'unit-fixture-task-a', target: AssistantExecutionTarget.gateway, provider: SingleAgentProvider.openclaw, prompt: 'create files',