Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions flutter_app/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

Expand Down
2 changes: 1 addition & 1 deletion flutter_app/lib/constants.dart
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
3 changes: 2 additions & 1 deletion flutter_app/lib/models/gateway_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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),
);
}

Expand Down
2 changes: 1 addition & 1 deletion flutter_app/lib/screens/setup_wizard_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ class _SetupWizardScreenState extends State<SetupWizardScreen> {
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,
),
Expand Down
19 changes: 19 additions & 0 deletions flutter_app/lib/screens/splash_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,25 @@ class _SplashScreenState extends State<SplashScreen>
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) {
Expand Down
42 changes: 39 additions & 3 deletions flutter_app/lib/services/gateway_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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...')],
));

Expand Down Expand Up @@ -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<String?> _readTokenFromConfig() async {
try {
final raw = await NativeBridge.readRootfsFile('root/.openclaw/openclaw.json');
if (raw == null) return null;
final config = jsonDecode(raw) as Map<String, dynamic>;
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("'", "'\\''")}'";
Expand All @@ -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 {
Expand Down Expand Up @@ -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')],
));
}
Expand Down
21 changes: 18 additions & 3 deletions flutter_app/lib/widgets/progress_step.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
),
),
),
],
],
),
),
Expand Down
2 changes: 1 addition & 1 deletion flutter_app/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
Loading