fix(assistant): rebind stale thread workspaces
This commit is contained in:
parent
7187c4843a
commit
acaa8e8908
@ -1344,6 +1344,70 @@ class AppController extends ChangeNotifier {
|
||||
normalizedRef == settings.workspacePath.trim();
|
||||
}
|
||||
|
||||
bool _usesDefaultThreadWorkspaceRefFromAnotherRoot(
|
||||
String sessionKey, {
|
||||
AssistantExecutionTarget? executionTarget,
|
||||
String? workspaceRef,
|
||||
WorkspaceRefKind? workspaceRefKind,
|
||||
}) {
|
||||
final normalizedSessionKey = _normalizedAssistantSessionKey(sessionKey);
|
||||
final resolvedTarget =
|
||||
executionTarget ??
|
||||
assistantExecutionTargetForSession(normalizedSessionKey);
|
||||
if (resolvedTarget == AssistantExecutionTarget.remote) {
|
||||
return false;
|
||||
}
|
||||
final normalizedRef = workspaceRef?.trim() ?? '';
|
||||
if (normalizedRef.isEmpty || workspaceRefKind != WorkspaceRefKind.localPath) {
|
||||
return false;
|
||||
}
|
||||
final expectedDefault = _defaultWorkspaceRefForSession(
|
||||
normalizedSessionKey,
|
||||
).trim();
|
||||
if (expectedDefault.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
final normalizedPath = _trimTrailingPathSeparator(
|
||||
normalizedRef.replaceAll('\\', '/'),
|
||||
);
|
||||
final normalizedExpected = _trimTrailingPathSeparator(
|
||||
expectedDefault.replaceAll('\\', '/'),
|
||||
);
|
||||
if (normalizedPath == normalizedExpected) {
|
||||
return false;
|
||||
}
|
||||
if (normalizedSessionKey == 'main') {
|
||||
return normalizedPath == SettingsSnapshot.defaults().workspacePath;
|
||||
}
|
||||
final expectedSuffix =
|
||||
'/.xworkmate/threads/${_threadWorkspaceDirectoryName(normalizedSessionKey)}';
|
||||
return normalizedPath.endsWith(expectedSuffix);
|
||||
}
|
||||
|
||||
bool _shouldMigrateWorkspaceRef(
|
||||
String sessionKey, {
|
||||
AssistantExecutionTarget? executionTarget,
|
||||
String? workspaceRef,
|
||||
WorkspaceRefKind? workspaceRefKind,
|
||||
}) {
|
||||
final normalizedRef = workspaceRef?.trim() ?? '';
|
||||
if (normalizedRef.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
return _usesLegacySharedWorkspaceRef(
|
||||
sessionKey,
|
||||
executionTarget: executionTarget,
|
||||
workspaceRef: normalizedRef,
|
||||
workspaceRefKind: workspaceRefKind,
|
||||
) ||
|
||||
_usesDefaultThreadWorkspaceRefFromAnotherRoot(
|
||||
sessionKey,
|
||||
executionTarget: executionTarget,
|
||||
workspaceRef: normalizedRef,
|
||||
workspaceRefKind: workspaceRefKind,
|
||||
);
|
||||
}
|
||||
|
||||
WorkspaceRefKind _defaultWorkspaceRefKindForTarget(
|
||||
AssistantExecutionTarget target,
|
||||
) {
|
||||
@ -1373,7 +1437,7 @@ class AppController extends ChangeNotifier {
|
||||
if (existing != null &&
|
||||
existingWorkspaceRef.isNotEmpty &&
|
||||
existing.workspaceRefKind == nextWorkspaceRefKind &&
|
||||
!_usesLegacySharedWorkspaceRef(
|
||||
!_shouldMigrateWorkspaceRef(
|
||||
normalizedSessionKey,
|
||||
executionTarget: resolvedTarget,
|
||||
workspaceRef: existingWorkspaceRef,
|
||||
@ -2704,8 +2768,7 @@ class AppController extends ChangeNotifier {
|
||||
_resolvedUserHomeDirectory = await _skillDirectoryAccessService
|
||||
.resolveUserHomeDirectory();
|
||||
await _settingsController.initialize();
|
||||
_restoreAssistantThreads(await _store.loadAssistantThreadRecords());
|
||||
await _restoreSharedSingleAgentLocalSkillsCache();
|
||||
final storedAssistantThreads = await _store.loadAssistantThreadRecords();
|
||||
if (_disposed) {
|
||||
return;
|
||||
}
|
||||
@ -2737,6 +2800,11 @@ class AppController extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_restoreAssistantThreads(storedAssistantThreads);
|
||||
await _restoreSharedSingleAgentLocalSkillsCache();
|
||||
if (_disposed) {
|
||||
return;
|
||||
}
|
||||
_lastObservedSettingsSnapshot = settings;
|
||||
_modelsController.restoreFromSettings(settings.aiGateway);
|
||||
_multiAgentOrchestrator.updateConfig(settings.multiAgent);
|
||||
@ -3559,8 +3627,7 @@ class AppController extends ChangeNotifier {
|
||||
}
|
||||
final titleFromSettings = assistantCustomTaskTitle(sessionKey);
|
||||
final shouldMigrateWorkspaceRef =
|
||||
record.workspaceRef.trim().isEmpty ||
|
||||
_usesLegacySharedWorkspaceRef(
|
||||
_shouldMigrateWorkspaceRef(
|
||||
sessionKey,
|
||||
executionTarget:
|
||||
record.executionTarget ?? settings.assistantExecutionTarget,
|
||||
|
||||
@ -231,4 +231,62 @@ void main() {
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'AppController rebinds default thread workspaces after bootstrap updates the workspace root',
|
||||
() async {
|
||||
SharedPreferences.setMockInitialValues(<String, Object>{});
|
||||
final tempDirectory = await Directory.systemTemp.createTemp(
|
||||
'xworkmate-thread-workspace-bootstrap-migrate-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await tempDirectory.exists()) {
|
||||
try {
|
||||
await tempDirectory.delete(recursive: true);
|
||||
} catch (_) {}
|
||||
}
|
||||
});
|
||||
final store = SecureConfigStore(
|
||||
enableSecureStorage: false,
|
||||
databasePathResolver: () async => '${tempDirectory.path}/settings.db',
|
||||
fallbackDirectoryPathResolver: () async => tempDirectory.path,
|
||||
);
|
||||
await store.initialize();
|
||||
await store.saveSettingsSnapshot(SettingsSnapshot.defaults());
|
||||
await store.saveAssistantThreadRecords(<AssistantThreadRecord>[
|
||||
AssistantThreadRecord(
|
||||
sessionKey: 'draft:artifact-thread',
|
||||
messages: const <GatewayChatMessage>[],
|
||||
updatedAtMs: 1,
|
||||
title: 'Artifact Thread',
|
||||
archived: false,
|
||||
executionTarget: AssistantExecutionTarget.singleAgent,
|
||||
messageViewMode: AssistantMessageViewMode.rendered,
|
||||
workspaceRef: '/opt/data/.xworkmate/threads/draft-artifact-thread',
|
||||
workspaceRefKind: WorkspaceRefKind.localPath,
|
||||
),
|
||||
]);
|
||||
|
||||
final controller = AppController(store: store);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
final deadline = DateTime.now().add(const Duration(seconds: 5));
|
||||
while (controller.initializing) {
|
||||
if (DateTime.now().isAfter(deadline)) {
|
||||
fail('controller did not initialize in time');
|
||||
}
|
||||
await Future<void>.delayed(const Duration(milliseconds: 20));
|
||||
}
|
||||
|
||||
expect(controller.settings.workspacePath, isNot('/opt/data'));
|
||||
final migratedWorkspace = controller.assistantWorkspaceRefForSession(
|
||||
'draft:artifact-thread',
|
||||
);
|
||||
expect(
|
||||
migratedWorkspace,
|
||||
'${controller.settings.workspacePath}/.xworkmate/threads/draft-artifact-thread',
|
||||
);
|
||||
expect(Directory(migratedWorkspace).existsSync(), isTrue);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user