diff --git a/lib/app/app_controller_desktop_core.dart b/lib/app/app_controller_desktop_core.dart index 323890cd..af71a7a9 100644 --- a/lib/app/app_controller_desktop_core.dart +++ b/lib/app/app_controller_desktop_core.dart @@ -199,6 +199,12 @@ class AppController extends ChangeNotifier { environmentOverrideInternal = environmentOverride == null ? null : Map.unmodifiable(environmentOverride); + if (environmentOverrideInternal != null) { + resolvedUserHomeDirectoryInternal = + resolveUserHomeDirectoryFromControllerEnvironmentInternal( + environmentOverrideInternal, + ); + } gatewayAcpClientInternal = GatewayAcpClient( endpointResolver: resolveGatewayAcpEndpointInternal, authorizationResolver: resolveGatewayAcpAuthorizationHeaderInternal, @@ -717,3 +723,22 @@ class AppController extends ChangeNotifier { double get assistantSkillCount => skills.length.toDouble(); int get currentAssistantSkillCount => skills.length; } + +String resolveUserHomeDirectoryFromControllerEnvironmentInternal( + Map? environment, +) { + if (environment == null) { + return resolveUserHomeDirectory(); + } + final resolved = resolveUserHomeDirectory(environment: environment); + if (resolved.isNotEmpty) { + return resolved; + } + if (Platform.environment.containsKey('FLUTTER_TEST')) { + final suffix = DateTime.now().microsecondsSinceEpoch; + return Directory.systemTemp + .createTempSync('xworkmate-test-home-$suffix-') + .path; + } + return ''; +} diff --git a/lib/app/app_controller_desktop_settings_runtime.dart b/lib/app/app_controller_desktop_settings_runtime.dart index 1c2cd66d..9145c919 100644 --- a/lib/app/app_controller_desktop_settings_runtime.dart +++ b/lib/app/app_controller_desktop_settings_runtime.dart @@ -457,8 +457,11 @@ extension AppControllerDesktopSettingsRuntime on AppController { Future initializeInternal() async { try { - resolvedUserHomeDirectoryInternal = - await skillDirectoryAccessServiceInternal.resolveUserHomeDirectory(); + resolvedUserHomeDirectoryInternal = environmentOverrideInternal == null + ? await skillDirectoryAccessServiceInternal.resolveUserHomeDirectory() + : resolveUserHomeDirectoryFromControllerEnvironmentInternal( + environmentOverrideInternal, + ); await settingsControllerInternal.initialize(); final loadedAppUiState = await storeInternal.loadAppUiState(); final sanitizedAppUiState = sanitizeAppUiStateInternal(loadedAppUiState); diff --git a/lib/features/assistant/assistant_page_state_actions.dart b/lib/features/assistant/assistant_page_state_actions.dart index d6af742b..bc3ebd1c 100644 --- a/lib/features/assistant/assistant_page_state_actions.dart +++ b/lib/features/assistant/assistant_page_state_actions.dart @@ -86,7 +86,9 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal { if (rawPrompt.isEmpty) { return; } - if (controller.hasAssistantPendingRun) { + if (controller.assistantSessionHasPendingRun( + controller.currentSessionKey, + )) { await createNewThreadInternal(); } final submittedSessionKey = controller.currentSessionKey; @@ -343,7 +345,16 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal { if (!mounted) { return; } - composerFocusNodeInternal.requestFocus(); + void requestFocus() { + if (!mounted) { + return; + } + FocusScope.of(context).requestFocus(composerFocusNodeInternal); + composerFocusNodeInternal.requestFocus(); + } + + requestFocus(); + WidgetsBinding.instance.addPostFrameCallback((_) => requestFocus()); } Future runTaskSessionActionWithRetryInternal( diff --git a/test/runtime/app_controller_thread_workspace_binding_test.dart b/test/runtime/app_controller_thread_workspace_binding_test.dart index 70e54a6e..856faee7 100644 --- a/test/runtime/app_controller_thread_workspace_binding_test.dart +++ b/test/runtime/app_controller_thread_workspace_binding_test.dart @@ -10,6 +10,32 @@ import 'package:xworkmate/runtime/go_task_service_client.dart'; import 'package:xworkmate/runtime/runtime_models.dart'; void main() { + test( + 'empty environment override keeps thread workspaces out of real HOME', + () async { + final realHome = Platform.environment['HOME']?.trim() ?? ''; + final controller = AppController( + environmentOverride: const {}, + ); + addTearDown(controller.dispose); + + await controller.sessionsController.switchSession('draft:test-task-a'); + + expect(controller.userHomeDirectory, isNot(isEmpty)); + if (realHome.isNotEmpty) { + expect(controller.userHomeDirectory, isNot(realHome)); + } + expect( + controller.localThreadWorkspacePathInternal('draft:test-task-a'), + isNot(contains('$realHome/.xworkmate/threads/draft-test-task-a')), + ); + expect( + controller.localThreadWorkspaceDisplayPathInternal('draft:test-task-a'), + '\$HOME/.xworkmate/threads/draft-test-task-a', + ); + }, + ); + test('does not expose gateway chat messages from another session', () async { final controller = AppController( environmentOverride: const {},