From db23cbeecbf8efb61b78b7defaf669d8bc9b5506 Mon Sep 17 00:00:00 2001 From: mithun50 Date: Sun, 22 Mar 2026 09:58:02 +0530 Subject: [PATCH 1/3] fix: Resolve multiple bugs (#69, #70, #73, #74, #82, #83) - Fix usb_serial jcenter() build failure by replacing deprecated repos with mavenCentral() in subproject afterEvaluate (#69) - Fix setup progress bar stalling at 0% during extraction and npm install by using indeterminate animation, show percentage when real progress is available, fix off-by-one in step completion check (#83) - Fix token mismatch by reading actual token from openclaw.json config as source of truth, clear stale token on gateway start, add clearDashboardUrl to state copyWith (#74, #82) - Auto-repair missing bionic-bypass.js after app update instead of forcing full re-setup (#70, #73) Co-Authored-By: Mithun Gowda B --- flutter_app/android/build.gradle | 11 +++++ flutter_app/lib/models/gateway_state.dart | 3 +- .../lib/screens/setup_wizard_screen.dart | 2 +- flutter_app/lib/screens/splash_screen.dart | 19 +++++++++ flutter_app/lib/services/gateway_service.dart | 42 +++++++++++++++++-- flutter_app/lib/widgets/progress_step.dart | 21 ++++++++-- 6 files changed, 90 insertions(+), 8 deletions(-) diff --git a/flutter_app/android/build.gradle b/flutter_app/android/build.gradle index dfd81c4..fe44816 100644 --- a/flutter_app/android/build.gradle +++ b/flutter_app/android/build.gradle @@ -22,6 +22,17 @@ subprojects { subprojects { project.evaluationDependsOn(":app") + + // Fix plugins that still reference the deprecated jcenter() repository (#69). + // Force all subprojects to use mavenCentral() instead. + project.afterEvaluate { + project.repositories { + mavenCentral() + } + project.repositories.removeIf { repo -> + repo instanceof MavenArtifactRepository && repo.url.toString().contains("jcenter") + } + } } tasks.register("clean", Delete) { diff --git a/flutter_app/lib/models/gateway_state.dart b/flutter_app/lib/models/gateway_state.dart index 8f29ff9..18ea1d2 100644 --- a/flutter_app/lib/models/gateway_state.dart +++ b/flutter_app/lib/models/gateway_state.dart @@ -28,13 +28,14 @@ class GatewayState { DateTime? startedAt, bool clearStartedAt = false, String? dashboardUrl, + bool clearDashboardUrl = false, }) { return GatewayState( status: status ?? this.status, logs: logs ?? this.logs, errorMessage: clearError ? null : (errorMessage ?? this.errorMessage), startedAt: clearStartedAt ? null : (startedAt ?? this.startedAt), - dashboardUrl: dashboardUrl ?? this.dashboardUrl, + dashboardUrl: clearDashboardUrl ? null : (dashboardUrl ?? this.dashboardUrl), ); } diff --git a/flutter_app/lib/screens/setup_wizard_screen.dart b/flutter_app/lib/screens/setup_wizard_screen.dart index e3d396a..dc18c5d 100644 --- a/flutter_app/lib/screens/setup_wizard_screen.dart +++ b/flutter_app/lib/screens/setup_wizard_screen.dart @@ -178,7 +178,7 @@ class _SetupWizardScreenState extends State { stepNumber: num, label: state.step == step ? state.message : label, isActive: state.step == step, - isComplete: state.stepNumber > step.index + 1 || state.isComplete, + isComplete: state.stepNumber > step.index || state.isComplete, hasError: state.hasError && state.step == step, progress: state.step == step ? state.progress : null, ), diff --git a/flutter_app/lib/screens/splash_screen.dart b/flutter_app/lib/screens/splash_screen.dart index 48771f4..8240a8a 100644 --- a/flutter_app/lib/screens/splash_screen.dart +++ b/flutter_app/lib/screens/splash_screen.dart @@ -114,6 +114,25 @@ class _SplashScreenState extends State setupComplete = false; } + // Auto-repair: if only bionic-bypass is missing, regenerate it + // instead of forcing full re-setup (#70, #73). + if (!setupComplete) { + try { + final status = await NativeBridge.getBootstrapStatus(); + final rootfsOk = status['rootfsExists'] == true; + final bashOk = status['binBashExists'] == true; + final nodeOk = status['nodeInstalled'] == true; + final openclawOk = status['openclawInstalled'] == true; + final bypassOk = status['bypassInstalled'] == true; + + if (rootfsOk && bashOk && nodeOk && openclawOk && !bypassOk) { + setState(() => _status = 'Repairing bionic bypass...'); + await NativeBridge.installBionicBypass(); + setupComplete = await NativeBridge.isBootstrapComplete(); + } + } catch (_) {} + } + if (!mounted) return; if (setupComplete) { diff --git a/flutter_app/lib/services/gateway_service.dart b/flutter_app/lib/services/gateway_service.dart index a66adcc..eef2dae 100644 --- a/flutter_app/lib/services/gateway_service.dart +++ b/flutter_app/lib/services/gateway_service.dart @@ -71,10 +71,16 @@ class GatewayService { // Write allowCommands config so the next gateway restart picks it up, // and in case the running gateway supports config hot-reload. await _writeNodeAllowConfig(); + // Prefer token from config file over stale SharedPreferences value (#74, #82). + final configToken = await _readTokenFromConfig(); + final effectiveUrl = configToken != null + ? 'http://localhost:18789/#token=$configToken' + : savedUrl; + if (configToken != null) prefs.dashboardUrl = effectiveUrl; _startingAt = DateTime.now(); _updateState(_state.copyWith( status: GatewayStatus.starting, - dashboardUrl: savedUrl, + dashboardUrl: effectiveUrl, logs: [..._state.logs, _ts('[INFO] Gateway process detected, reconnecting...')], )); @@ -173,6 +179,19 @@ fs.writeFileSync(p, JSON.stringify(c, null, 2)); } } + /// Read the actual gateway auth token from openclaw.json config file (#74, #82). + /// This is the source of truth — more reliable than regex-scraping stdout. + Future _readTokenFromConfig() async { + try { + final raw = await NativeBridge.readRootfsFile('root/.openclaw/openclaw.json'); + if (raw == null) return null; + final config = jsonDecode(raw) as Map; + final token = config['gateway']?['auth']?['token']; + if (token is String && token.isNotEmpty) return token; + } catch (_) {} + return null; + } + /// Escape a string for use as a single-quoted shell argument. static String _shellEscape(String s) { return "'${s.replaceAll("'", "'\\''")}'"; @@ -183,15 +202,17 @@ fs.writeFileSync(p, JSON.stringify(c, null, 2)); if (_startInProgress) return; _startInProgress = true; + // Clear any stale token from a previous session (#74, #82). + // The fresh token will be captured from gateway stdout once it starts. final prefs = PreferencesService(); await prefs.init(); - final savedUrl = prefs.dashboardUrl; + prefs.dashboardUrl = null; _updateState(_state.copyWith( status: GatewayStatus.starting, clearError: true, + clearDashboardUrl: true, logs: [..._state.logs, _ts('[INFO] Starting gateway...')], - dashboardUrl: savedUrl, )); try { @@ -280,9 +301,24 @@ fs.writeFileSync(p, JSON.stringify(c, null, 2)); .timeout(const Duration(seconds: 3)); if (response.statusCode < 500 && _state.status != GatewayStatus.running) { + // Read the actual token from openclaw.json — source of truth (#74, #82). + // This ensures the displayed token always matches the gateway's config, + // even if the stdout regex didn't capture it. + String? configUrl = _state.dashboardUrl; + try { + final token = await _readTokenFromConfig(); + if (token != null) { + configUrl = 'http://localhost:18789/#token=$token'; + final prefs = PreferencesService(); + await prefs.init(); + prefs.dashboardUrl = configUrl; + } + } catch (_) {} + _updateState(_state.copyWith( status: GatewayStatus.running, startedAt: DateTime.now(), + dashboardUrl: configUrl, logs: [..._state.logs, _ts('[INFO] Gateway is healthy')], )); } diff --git a/flutter_app/lib/widgets/progress_step.dart b/flutter_app/lib/widgets/progress_step.dart index 882fb2e..adc6593 100644 --- a/flutter_app/lib/widgets/progress_step.dart +++ b/flutter_app/lib/widgets/progress_step.dart @@ -34,13 +34,16 @@ class ProgressStep extends StatelessWidget { circleChild = const Icon(Icons.check, color: Colors.white, size: 16); } else if (isActive) { circleColor = theme.colorScheme.primary; + // Use indeterminate (spinning) when progress is 0 so the UI doesn't + // appear frozen during long-running steps (#83). + final effectiveProgress = (progress != null && progress! > 0.0) ? progress : null; circleChild = SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, - value: progress, + value: effectiveProgress, ), ); } else { @@ -83,15 +86,27 @@ class ProgressStep extends StatelessWidget { : theme.colorScheme.onSurfaceVariant, ), ), - if (isActive && progress != null) + if (isActive && progress != null) ...[ Padding( padding: const EdgeInsets.only(top: 4), child: LinearProgressIndicator( - value: progress, + // Show indeterminate animation when progress is 0 (#83) + value: progress! > 0.0 ? progress : null, minHeight: 4, borderRadius: BorderRadius.circular(2), ), ), + if (progress! > 0.0) + Padding( + padding: const EdgeInsets.only(top: 2), + child: Text( + '${(progress! * 100).toInt()}%', + style: theme.textTheme.labelSmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ), + ], ], ), ), From d253e3b3b08f234e73e69df869de4d37398a4766 Mon Sep 17 00:00:00 2001 From: mithun50 Date: Sun, 22 Mar 2026 10:00:03 +0530 Subject: [PATCH 2/3] chore: Bump version to 1.8.5+16 Co-Authored-By: Mithun Gowda B --- flutter_app/lib/constants.dart | 2 +- flutter_app/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter_app/lib/constants.dart b/flutter_app/lib/constants.dart index 3928574..c70fc9b 100644 --- a/flutter_app/lib/constants.dart +++ b/flutter_app/lib/constants.dart @@ -1,6 +1,6 @@ class AppConstants { static const String appName = 'OpenClaw'; - static const String version = '1.8.4'; + static const String version = '1.8.5'; static const String packageName = 'com.nxg.openclawproot'; /// Matches ANSI escape sequences (e.g. color codes in terminal output). diff --git a/flutter_app/pubspec.yaml b/flutter_app/pubspec.yaml index 72e19c3..cfa8fde 100644 --- a/flutter_app/pubspec.yaml +++ b/flutter_app/pubspec.yaml @@ -1,7 +1,7 @@ name: openclaw description: OpenClaw AI Gateway for Android - standalone, no Termux required. publish_to: 'none' -version: 1.8.4+15 +version: 1.8.5+16 environment: sdk: '>=3.2.0 <4.0.0' From 02b7da07c327e6db180f4c53e65273bca0e45c0d Mon Sep 17 00:00:00 2001 From: mithun50 Date: Sun, 22 Mar 2026 10:07:05 +0530 Subject: [PATCH 3/3] fix: Use jcenter() fallback in allprojects instead of afterEvaluate (#69) The afterEvaluate approach fails because evaluationDependsOn(":app") already evaluates the project. Add jcenter() directly to allprojects repositories as a fallback for usb_serial and other plugins that still need it. Co-Authored-By: Mithun Gowda B --- flutter_app/android/build.gradle | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/flutter_app/android/build.gradle b/flutter_app/android/build.gradle index fe44816..d7add95 100644 --- a/flutter_app/android/build.gradle +++ b/flutter_app/android/build.gradle @@ -2,6 +2,10 @@ allprojects { repositories { google() mavenCentral() + // Fallback for plugins that still reference jcenter() (#69). + // usb_serial 0.5.x requires jcenter for dependency resolution. + //noinspection GradleDeprecated + jcenter() } } @@ -22,17 +26,6 @@ subprojects { subprojects { project.evaluationDependsOn(":app") - - // Fix plugins that still reference the deprecated jcenter() repository (#69). - // Force all subprojects to use mavenCentral() instead. - project.afterEvaluate { - project.repositories { - mavenCentral() - } - project.repositories.removeIf { repo -> - repo instanceof MavenArtifactRepository && repo.url.toString().contains("jcenter") - } - } } tasks.register("clean", Delete) {