// ignore_for_file: unused_import, unnecessary_import import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'app_metadata.dart'; import 'app_capabilities.dart'; import 'app_store_policy.dart'; import 'ui_feature_manifest.dart'; import '../i18n/app_language.dart'; import '../models/app_models.dart'; import '../runtime/device_identity_store.dart'; import '../runtime/runtime_bootstrap.dart'; import '../runtime/desktop_platform_service.dart'; import '../runtime/gateway_runtime.dart'; import '../runtime/runtime_controllers.dart'; import '../runtime/runtime_models.dart'; import '../runtime/secure_config_store.dart'; import '../runtime/embedded_agent_launch_policy.dart'; import '../runtime/runtime_coordinator.dart'; import '../runtime/gateway_acp_client.dart'; import '../runtime/codex_runtime.dart'; import '../runtime/codex_config_bridge.dart'; import '../runtime/code_agent_node_orchestrator.dart'; import '../runtime/assistant_artifacts.dart'; import '../runtime/desktop_thread_artifact_service.dart'; import '../runtime/mode_switcher.dart'; import '../runtime/agent_registry.dart'; import '../runtime/platform_environment.dart'; import 'app_controller_desktop_core.dart'; import 'app_controller_desktop_navigation.dart'; import 'app_controller_desktop_gateway.dart'; import 'app_controller_desktop_settings.dart'; import 'app_controller_desktop_thread_sessions.dart'; import 'app_controller_desktop_thread_actions.dart'; import 'app_controller_desktop_workspace_execution.dart'; import 'app_controller_desktop_settings_runtime.dart'; import 'app_controller_desktop_thread_storage.dart'; import 'app_controller_desktop_skill_permissions.dart'; import 'app_controller_desktop_runtime_helpers.dart'; class DesktopThreadBindingSnapshotInternal { const DesktopThreadBindingSnapshotInternal({ required this.executionTarget, required this.record, }); final AssistantExecutionTarget executionTarget; final TaskThread? record; } DesktopThreadBindingSnapshotInternal resolveDesktopThreadBindingSnapshotInternal({ required AssistantExecutionTarget defaultExecutionTarget, AssistantExecutionTarget? executionTargetOverride, TaskThread? latestRecord, }) { final resolvedExecutionTarget = executionTargetOverride ?? (latestRecord == null ? defaultExecutionTarget : assistantExecutionTargetFromExecutionMode( latestRecord.executionBinding.executionMode, )); return DesktopThreadBindingSnapshotInternal( executionTarget: resolvedExecutionTarget, record: latestRecord, ); } extension AppControllerDesktopThreadBinding on AppController { String managedLocalThreadWorkspaceSuffixInternal(String sessionKey) => '/.xworkmate/threads/${threadWorkspaceDirectoryNameInternal(sessionKey)}'; bool isManagedLocalThreadWorkspacePathInternal( String path, String sessionKey, ) { final normalizedPath = trimTrailingPathSeparatorInternal(path.trim()); if (normalizedPath.isEmpty) { return false; } final normalizedSuffix = managedLocalThreadWorkspaceSuffixInternal( sessionKey, ); return normalizedPath.endsWith(normalizedSuffix); } String localThreadWorkspacePathInternal(String sessionKey) { final normalizedSessionKey = normalizedAssistantSessionKeyInternal( sessionKey, ); if (!isAppOwnedAssistantSessionKeyInternal(normalizedSessionKey)) { return ''; } final homeDirectory = resolvedUserHomeDirectoryInternal.trim(); if (homeDirectory.isEmpty) { return ''; } final threadWorkspace = '${trimTrailingPathSeparatorInternal(homeDirectory)}/.xworkmate/threads/${threadWorkspaceDirectoryNameInternal(normalizedSessionKey)}'; return ensureLocalWorkspaceDirectoryInternal(threadWorkspace) ? threadWorkspace : ''; } String localThreadWorkspaceDisplayPathInternal(String sessionKey) { final homeDirectory = resolvedUserHomeDirectoryInternal.trim(); if (homeDirectory.isEmpty) { return ''; } final normalizedSessionKey = normalizedAssistantSessionKeyInternal( sessionKey, ); if (!isAppOwnedAssistantSessionKeyInternal(normalizedSessionKey)) { return ''; } return '\$HOME/.xworkmate/threads/${threadWorkspaceDirectoryNameInternal(normalizedSessionKey)}'; } String remoteThreadWorkspacePathInternal( String sessionKey, ThreadOwnerScope ownerScope, ) { final normalizedSessionKey = normalizedAssistantSessionKeyInternal( sessionKey, ); final realm = ownerScope.realm.name; final subjectType = ownerScope.subjectType.name; final subjectId = ownerScope.subjectId.trim(); return '/owners/$realm/$subjectType/$subjectId/threads/$normalizedSessionKey'; } bool isOwnerScopedRemoteWorkspacePathInternal(String path) { final normalizedPath = path.trim(); return normalizedPath.startsWith('/owners/'); } String threadWorkspaceDirectoryNameInternal(String sessionKey) { final normalizedSessionKey = normalizedAssistantSessionKeyInternal( sessionKey, ); final sanitized = normalizedSessionKey .replaceAll(RegExp(r'[^A-Za-z0-9._-]+'), '-') .replaceAll(RegExp(r'-{2,}'), '-') .replaceAll(RegExp(r'^[-.]+|[-.]+$'), ''); return sanitized.isEmpty ? 'thread' : sanitized; } String trimTrailingPathSeparatorInternal(String path) { if (path.endsWith('/') && path.length > 1) { return path.substring(0, path.length - 1); } return path; } bool ensureLocalWorkspaceDirectoryInternal(String path) { final normalizedPath = path.trim(); if (normalizedPath.isEmpty) { return false; } try { Directory(normalizedPath).createSync(recursive: true); } catch (error) { debugPrint('Ensure local thread workspace fallback: $error'); // Best effort only. The caller can still decide whether to fail fast. } return Directory(normalizedPath).existsSync(); } ThreadOwnerScope desktopThreadOwnerScopeFromIdentityInternal( LocalDeviceIdentity identity, ) { return ThreadOwnerScope( realm: ThreadRealm.local, subjectType: ThreadSubjectType.user, subjectId: identity.deviceId, displayName: identity.deviceId, ); } Future ensureDesktopThreadOwnerScopeInternal( String sessionKey, ) async { final normalizedSessionKey = normalizedAssistantSessionKeyInternal( sessionKey, ); final existing = assistantThreadRecordsInternal[normalizedSessionKey]?.ownerScope; if (existing != null && existing.subjectId.trim().isNotEmpty) { return existing; } final identity = await DeviceIdentityStore(storeInternal).loadOrCreate(); return desktopThreadOwnerScopeFromIdentityInternal(identity); } WorkspaceBinding buildDesktopWorkspaceBindingInternal( String sessionKey, { required AssistantExecutionTarget executionTarget, required ThreadOwnerScope ownerScope, WorkspaceBinding? existingBinding, }) { final normalizedSessionKey = normalizedAssistantSessionKeyInternal( sessionKey, ); final localPath = localThreadWorkspacePathInternal(normalizedSessionKey); if (localPath.isEmpty) { final remotePath = remoteThreadWorkspacePathInternal( normalizedSessionKey, ownerScope, ); return WorkspaceBinding( workspaceId: normalizedSessionKey, workspaceKind: WorkspaceKind.remoteFs, workspacePath: remotePath, displayPath: remotePath, writable: existingBinding?.writable ?? true, ); } final displayPath = localThreadWorkspaceDisplayPathInternal( normalizedSessionKey, ); return WorkspaceBinding( workspaceId: normalizedSessionKey, workspaceKind: WorkspaceKind.localFs, workspacePath: localPath, displayPath: displayPath, writable: existingBinding?.writable ?? true, ); } AssistantExecutionTarget resolveDraftThreadExecutionTargetInternal( String sessionKey, { required Iterable supportedTargets, }) { return pickDraftThreadExecutionTargetInternal( currentTarget: assistantExecutionTargetForSession(sessionKey), visibleTargets: visibleAssistantExecutionTargets(supportedTargets), localWorkspaceAvailable: localThreadWorkspacePathInternal( sessionKey, ).trim().isNotEmpty, ); } ExecutionBinding buildDesktopExecutionBindingInternal({ required AssistantExecutionTarget executionTarget, ExecutionBinding? existingBinding, }) { final persistedProviderId = normalizeSingleAgentProviderId( existingBinding?.providerId ?? '', ); final existingTarget = existingBinding == null ? null : assistantExecutionTargetFromExecutionMode( existingBinding.executionMode, ); final selectedProvider = resolveProviderForExecutionTarget( persistedProviderId, executionTarget: executionTarget, defaultToCatalog: existingBinding == null || existingTarget != executionTarget, ); return (existingBinding ?? ExecutionBinding( executionMode: threadExecutionModeFromAssistantExecutionTarget( executionTarget, ), executorId: selectedProvider.providerId, providerId: selectedProvider.providerId, endpointId: '', )) .copyWith( executionMode: threadExecutionModeFromAssistantExecutionTarget( executionTarget, ), executorId: selectedProvider.providerId, providerId: selectedProvider.providerId, providerSource: existingBinding?.providerSource ?? ThreadSelectionSource.inherited, ); } Future ensureDesktopTaskThreadBindingInternal( String sessionKey, { AssistantExecutionTarget? executionTarget, }) async { final normalizedSessionKey = normalizedAssistantSessionKeyInternal( sessionKey, ); final ownerScope = await ensureDesktopThreadOwnerScopeInternal( normalizedSessionKey, ); final latestRecord = taskThreadForSessionInternal(normalizedSessionKey) ?? assistantThreadRecordsInternal[normalizedSessionKey]; final snapshot = resolveDesktopThreadBindingSnapshotInternal( defaultExecutionTarget: settings.assistantExecutionTarget, executionTargetOverride: executionTarget, latestRecord: latestRecord, ); final workspaceBinding = buildDesktopWorkspaceBindingInternal( normalizedSessionKey, executionTarget: snapshot.executionTarget, ownerScope: ownerScope, existingBinding: snapshot.record?.workspaceBinding, ); final currentRecord = taskThreadForSessionInternal(normalizedSessionKey) ?? assistantThreadRecordsInternal[normalizedSessionKey]; final existingLifecycle = currentRecord?.lifecycleState ?? snapshot.record?.lifecycleState ?? const ThreadLifecycleState( archived: false, status: 'ready', lastRunAtMs: null, lastResultCode: null, ); final existingLifecycleStatus = existingLifecycle.status .trim() .toLowerCase(); final lifecycleState = existingLifecycleStatus == 'running' ? existingLifecycle : assistantSessionHasPendingRun(normalizedSessionKey) ? existingLifecycle.copyWith(status: 'running') : existingLifecycle; upsertTaskThreadInternal( normalizedSessionKey, ownerScope: ownerScope, workspaceBinding: workspaceBinding, lastRemoteWorkingDirectory: snapshot.record?.lastRemoteWorkingDirectory?.trim().isNotEmpty == true ? snapshot.record?.lastRemoteWorkingDirectory : remoteThreadWorkspacePathInternal(normalizedSessionKey, ownerScope), lastRemoteWorkspaceRefKind: snapshot.record?.lastRemoteWorkspaceRefKind ?? WorkspaceRefKind.remotePath, executionBinding: buildDesktopExecutionBindingInternal( executionTarget: snapshot.executionTarget, existingBinding: snapshot.record?.executionBinding, ), lifecycleState: lifecycleState, updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(), ); } } AssistantExecutionTarget pickDraftThreadExecutionTargetInternal({ required AssistantExecutionTarget currentTarget, required Iterable visibleTargets, bool? localWorkspaceAvailable, }) { final orderedTargets = [ if (visibleTargets.contains(currentTarget)) currentTarget, ...visibleTargets.where((target) => target != currentTarget), ]; for (final target in orderedTargets) { return target; } return currentTarget; }