fix: remove polluted test task sessions
This commit is contained in:
parent
7ff53a1246
commit
484b10ecf5
@ -137,7 +137,21 @@ class AppController extends ChangeNotifier {
|
||||
GoTaskServiceClient? goTaskServiceClient,
|
||||
MultiAgentMountManager? multiAgentMountManager,
|
||||
}) {
|
||||
storeInternal = store ?? SecureConfigStore();
|
||||
environmentOverrideInternal = environmentOverride == null
|
||||
? null
|
||||
: Map<String, String>.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<String, String>.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<String, String>? environmentOverride,
|
||||
}) {
|
||||
if (environmentOverride == null || resolvedUserHomeDirectory.trim().isEmpty) {
|
||||
return SecureConfigStore();
|
||||
}
|
||||
final supportRoot =
|
||||
defaultUserSettingsRootPath(
|
||||
environment: <String, String>{
|
||||
...environmentOverride,
|
||||
'HOME': resolvedUserHomeDirectory,
|
||||
},
|
||||
operatingSystem: Platform.operatingSystem,
|
||||
) ??
|
||||
'${resolvedUserHomeDirectory.trim()}/.xworkmate';
|
||||
return SecureConfigStore(
|
||||
appDataRootPathResolver: () async => supportRoot,
|
||||
supportRootPathResolver: () async => supportRoot,
|
||||
secretRootPathResolver: () async => '$supportRoot/secrets',
|
||||
enableSecureStorage: false,
|
||||
);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<String> knownPollutedTestTaskSessionKeysInternal() => <String>{
|
||||
'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<TaskThread> discardKnownPollutedTestTaskThreadsInternal(
|
||||
List<TaskThread> records,
|
||||
) {
|
||||
return records
|
||||
.where(
|
||||
(record) =>
|
||||
!isKnownPollutedTestTaskSessionKeyInternal(record.sessionKey),
|
||||
)
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
Future<void> 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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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(<TaskThread>[
|
||||
_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 <String, String>{},
|
||||
);
|
||||
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, <String>[realSessionKey]);
|
||||
expect((await store.loadAppUiState()).assistantLastSessionKey, isEmpty);
|
||||
},
|
||||
);
|
||||
|
||||
test('source tree does not contain known real draft test fixtures', () async {
|
||||
final blocked = <String>[
|
||||
_pollutedUnitSessionKey(),
|
||||
_pollutedTestSessionKey(),
|
||||
_pollutedUnitWorkspaceName(),
|
||||
_pollutedTestWorkspaceName(),
|
||||
];
|
||||
final roots = <String>['lib', 'test', 'scripts', 'docs'];
|
||||
final violations = <String>[];
|
||||
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), <String>[
|
||||
'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<void> clearAssistantLocalState() async {
|
||||
clearAssistantLocalStateCalled = true;
|
||||
await super.clearAssistantLocalState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _waitForControllerInitialization(AppController controller) async {
|
||||
final deadline = DateTime.now().add(const Duration(seconds: 5));
|
||||
while (controller.initializing && DateTime.now().isBefore(deadline)) {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 20));
|
||||
}
|
||||
expect(controller.initializing, isFalse);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
@ -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<void>.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<GatewayChatMessage>.from(
|
||||
controller
|
||||
.requireTaskThreadForSessionInternal('draft:unit-task-a')
|
||||
.requireTaskThreadForSessionInternal('unit-fixture-task-a')
|
||||
.messages,
|
||||
);
|
||||
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
@ -575,8 +575,8 @@ void main() {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'session.update',
|
||||
'params': <String, dynamic>{
|
||||
'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(<String, dynamic>{
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'xworkmate.bridge.accepted',
|
||||
'params': <String, dynamic>{'sessionId': 'draft:unit-task-a'},
|
||||
'params': <String, dynamic>{'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': <String, dynamic>{
|
||||
'status': 'completed',
|
||||
'sessionId': 'draft:unit-task-a',
|
||||
'threadId': 'draft:unit-task-a',
|
||||
'sessionId': 'unit-fixture-task-a',
|
||||
'threadId': 'unit-fixture-task-a',
|
||||
'task': <String, dynamic>{
|
||||
'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',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user