From 2f32cf3b9f114c562824c325cf66b9df8b68f7fb Mon Sep 17 00:00:00 2001 From: mithun50 Date: Fri, 27 Mar 2026 07:39:42 +0530 Subject: [PATCH 1/4] fix: Config corruption, gateway.mode, and Node.js version (#83, #88, #90, #93, #94, #87) - Fix model entries written as strings instead of objects in openclaw.json - Auto-set gateway.mode=local in all config write paths - Add config auto-repair on GatewayService.init() - Make bionic bypass installation more resilient with retry logic - Pre-seed openclaw.json with gateway.mode=local during setup - Update Node.js from 22.13.1 to 22.14.0 - Bump version to 1.8.6+17 Co-Authored-By: Mithun Gowda B --- CHANGELOG.md | 17 ++++ .../com/nxg/openclawproot/BootstrapManager.kt | 65 ++++++++++++- flutter_app/lib/constants.dart | 4 +- flutter_app/lib/models/ai_provider.dart | 1 - flutter_app/lib/services/gateway_service.dart | 92 +++++++++++++++++++ .../lib/services/provider_config_service.dart | 28 ++++-- flutter_app/pubspec.yaml | 2 +- 7 files changed, 195 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6d68ae..4e0e612 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## v1.8.6 — Config Repair, Gateway Mode & Node.js Update + +### Bug Fixes + +- **Config Corruption Fix (#83, #88)** — Provider model entries were written as bare strings instead of objects (`{ id: "model-name" }`), causing OpenClaw config validation to reject the file with "expected object, received string". Fixed both the Node.js script path and the direct file I/O fallback in `ProviderConfigService`. Existing corrupted configs are now auto-repaired on gateway init +- **Gateway Start Failure (#93, #90)** — The gateway blocked with "set gateway.mode=local (current: unset)". Now `gateway.mode=local` is set automatically in openclaw.json during provider config saves, gateway config writes, bionic bypass installation, and on startup repair +- **Config Auto-Repair on Init (#88)** — Added `_repairConfigFile()` that runs on every `GatewayService.init()` to fix corrupted model entries and missing `gateway.mode`, preventing the crash-restart loop (5 restarts → stopped) +- **Bionic Bypass Installation Robustness (#94)** — Added retry logic with parent directory creation if the initial `mkdirs()` fails silently on some devices +- **Pre-seed Config on Setup** — `installBionicBypass()` now creates a default `openclaw.json` with `gateway.mode=local` during initial setup, so the gateway works immediately after installation + +### Enhancements + +- **Node.js Updated to 22.14.0** — Upgraded from 22.13.1 to latest 22.x LTS for better stability and compatibility (#87) +- **Removed Outdated Model** — Dropped `claude-3-5-sonnet-20241022` from Anthropic provider defaults + +--- + ## v1.8.4 — Serial, Log Timestamps & ADB Backup ### New Features diff --git a/flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt b/flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt index 623068a..c5cac66 100644 --- a/flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt +++ b/flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt @@ -824,7 +824,15 @@ class BootstrapManager( fun installBionicBypass() { val bypassDir = File("$rootfsDir/root/.openclaw") - bypassDir.mkdirs() + if (!bypassDir.exists()) { + bypassDir.mkdirs() + } + // Verify directory was created — some devices fail silently (#94) + if (!bypassDir.exists()) { + // Retry with parent creation + bypassDir.parentFile?.mkdirs() + bypassDir.mkdir() + } // 1. CWD fix — proot's getcwd() syscall returns ENOSYS on Android 10+. // process.cwd() is called by Node's CJS module resolver and npm. @@ -1239,6 +1247,61 @@ require('/root/.openclaw/proot-compat.js'); if (!existing.contains("bionic-bypass")) { bashrc.appendText("\n# OpenClaw Bionic Bypass\n$exportLine\n") } + + // Pre-seed openclaw.json with gateway.mode=local so the gateway + // doesn't reject startup with "set gateway.mode=local" (#93, #90). + val configFile = File(bypassDir, "openclaw.json") + if (!configFile.exists()) { + configFile.writeText(""" +{ + "gateway": { + "mode": "local" + } +} +""".trimIndent()) + } else { + // Repair existing config: ensure gateway.mode and fix model entries (#83, #88) + try { + val content = configFile.readText() + val org = org.json.JSONObject(content) + var modified = false + if (!org.has("gateway")) { + org.put("gateway", org.json.JSONObject().put("mode", "local")) + modified = true + } else { + val gw = org.getJSONObject("gateway") + if (!gw.has("mode")) { + gw.put("mode", "local") + modified = true + } + } + // Fix model entries: strings → objects with id field (#83, #88) + if (org.has("models")) { + val models = org.optJSONObject("models") + val providers = models?.optJSONObject("providers") + if (providers != null) { + val keys = providers.keys() + while (keys.hasNext()) { + val key = keys.next() + val prov = providers.optJSONObject(key) + val arr = prov?.optJSONArray("models") + if (arr != null) { + for (i in 0 until arr.length()) { + val item = arr.get(i) + if (item is String) { + arr.put(i, org.json.JSONObject().put("id", item)) + modified = true + } + } + } + } + } + } + if (modified) { + configFile.writeText(org.toString(2)) + } + } catch (_: Exception) {} + } } /** diff --git a/flutter_app/lib/constants.dart b/flutter_app/lib/constants.dart index c70fc9b..3beefbd 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.5'; + static const String version = '1.8.6'; static const String packageName = 'com.nxg.openclawproot'; /// Matches ANSI escape sequences (e.g. color codes in terminal output). @@ -33,7 +33,7 @@ class AppConstants { // Node.js binary tarball — downloaded directly by Flutter, extracted by Java. // Bypasses curl/gpg/NodeSource which fail inside proot. - static const String nodeVersion = '22.13.1'; + static const String nodeVersion = '22.14.0'; static const String nodeBaseUrl = 'https://nodejs.org/dist/v$nodeVersion/node-v$nodeVersion-linux-'; diff --git a/flutter_app/lib/models/ai_provider.dart b/flutter_app/lib/models/ai_provider.dart index 094d8a5..d24f8a5 100644 --- a/flutter_app/lib/models/ai_provider.dart +++ b/flutter_app/lib/models/ai_provider.dart @@ -34,7 +34,6 @@ class AiProvider { 'claude-sonnet-4-20250514', 'claude-opus-4-20250514', 'claude-haiku-4-20250506', - 'claude-3-5-sonnet-20241022', ], apiKeyHint: 'sk-ant-...', ); diff --git a/flutter_app/lib/services/gateway_service.dart b/flutter_app/lib/services/gateway_service.dart index eef2dae..a51097f 100644 --- a/flutter_app/lib/services/gateway_service.dart +++ b/flutter_app/lib/services/gateway_service.dart @@ -66,6 +66,10 @@ class GatewayService { } } catch (_) {} + // Repair corrupted config before gateway start (#88). + // This fixes the "Invalid input: expected object, received string" crash loop. + await _repairConfigFile(); + final alreadyRunning = await NativeBridge.isGatewayRunning(); if (alreadyRunning) { // Write allowCommands config so the next gateway restart picks it up, @@ -137,9 +141,18 @@ const p = "/root/.openclaw/openclaw.json"; let c = {}; try { c = JSON.parse(fs.readFileSync(p, "utf8")); } catch {} if (!c.gateway) c.gateway = {}; +if (!c.gateway.mode) c.gateway.mode = "local"; if (!c.gateway.nodes) c.gateway.nodes = {}; c.gateway.nodes.denyCommands = []; c.gateway.nodes.allowCommands = $allowJson; +// Fix config corruption: models entries must be objects, not strings (#83, #88) +if (c.models && c.models.providers) { + for (const [pid, prov] of Object.entries(c.models.providers)) { + if (prov && Array.isArray(prov.models)) { + prov.models = prov.models.map(m => typeof m === "string" ? { id: m } : m); + } + } +} fs.writeFileSync(p, JSON.stringify(c, null, 2)); '''; var prootOk = false; @@ -167,10 +180,14 @@ fs.writeFileSync(p, JSON.stringify(c, null, 2)); } config.putIfAbsent('gateway', () => {}); final gw = config['gateway'] as Map; + // Ensure gateway.mode=local so the gateway starts without --allow-unconfigured (#93, #90) + gw.putIfAbsent('mode', () => 'local'); gw.putIfAbsent('nodes', () => {}); final nodes = gw['nodes'] as Map; nodes['denyCommands'] = []; nodes['allowCommands'] = allowCommands; + // Fix config corruption: models entries must be objects, not strings (#83, #88) + _repairModelEntries(config); configFile.parent.createSync(recursive: true); configFile.writeAsStringSync( const JsonEncoder.withIndent(' ').convert(config), @@ -179,6 +196,81 @@ fs.writeFileSync(p, JSON.stringify(c, null, 2)); } } + /// Repair openclaw.json on disk — fixes corrupted model entries and ensures + /// gateway.mode=local is set. Called on init() before any gateway start (#88). + Future _repairConfigFile() async { + try { + final filesDir = await NativeBridge.getFilesDir(); + final configFile = File('$filesDir/rootfs/ubuntu/root/.openclaw/openclaw.json'); + if (!configFile.existsSync()) return; + final content = configFile.readAsStringSync(); + if (content.isEmpty) return; + + Map config; + try { + config = Map.from(jsonDecode(content) as Map); + } catch (_) { + return; // Unparseable — _writeNodeAllowConfig will recreate it + } + + bool modified = false; + + // Ensure gateway.mode=local (#93, #90) + config.putIfAbsent('gateway', () => {}); + final gw = config['gateway'] as Map; + if (!gw.containsKey('mode')) { + gw['mode'] = 'local'; + modified = true; + } + + // Fix model entries: strings → objects (#83, #88) + final models = config['models'] as Map?; + if (models != null) { + final providers = models['providers'] as Map?; + if (providers != null) { + for (final entry in providers.values) { + if (entry is Map) { + final modelsList = entry['models']; + if (modelsList is List) { + for (int i = 0; i < modelsList.length; i++) { + if (modelsList[i] is String) { + modelsList[i] = {'id': modelsList[i]}; + modified = true; + } + } + } + } + } + } + } + + if (modified) { + configFile.writeAsStringSync( + const JsonEncoder.withIndent(' ').convert(config), + ); + } + } catch (_) {} + } + + /// Fix corrupted model entries: convert bare strings to {id: string} objects (#83, #88). + static void _repairModelEntries(Map config) { + final models = config['models'] as Map?; + if (models == null) return; + final providers = models['providers'] as Map?; + if (providers == null) return; + for (final entry in providers.values) { + if (entry is Map) { + final modelsList = entry['models']; + if (modelsList is List) { + entry['models'] = modelsList.map((m) { + if (m is String) return {'id': m}; + return m; + }).toList(); + } + } + } + } + /// 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 { diff --git a/flutter_app/lib/services/provider_config_service.dart b/flutter_app/lib/services/provider_config_service.dart index 93c1e7d..41f1cfc 100644 --- a/flutter_app/lib/services/provider_config_service.dart +++ b/flutter_app/lib/services/provider_config_service.dart @@ -61,14 +61,14 @@ class ProviderConfigService { required String apiKey, required String model, }) async { - final providerJson = jsonEncode({ - 'apiKey': apiKey, - 'baseUrl': provider.baseUrl, - 'models': [model], - }); - final modelJson = jsonEncode(model); final providerIdJson = jsonEncode(provider.id); + final apiKeyJson = jsonEncode(apiKey); + final baseUrlJson = jsonEncode(provider.baseUrl); + final modelJson = jsonEncode(model); + // Build the provider object with the model as an object containing `id`, + // not a bare string. OpenClaw expects: models: [{ id: "model-name" }]. + // Writing a bare string causes config validation failure (#83, #88). final script = ''' const fs = require("fs"); const p = "$_configPath"; @@ -76,11 +76,17 @@ let c = {}; try { c = JSON.parse(fs.readFileSync(p, "utf8")); } catch {} if (!c.models) c.models = {}; if (!c.models.providers) c.models.providers = {}; -c.models.providers[$providerIdJson] = $providerJson; +c.models.providers[$providerIdJson] = { + apiKey: $apiKeyJson, + baseUrl: $baseUrlJson, + models: [{ id: $modelJson }] +}; if (!c.agents) c.agents = {}; if (!c.agents.defaults) c.agents.defaults = {}; if (!c.agents.defaults.model) c.agents.defaults.model = {}; c.agents.defaults.model.primary = $modelJson; +if (!c.gateway) c.gateway = {}; +if (!c.gateway.mode) c.gateway.mode = "local"; fs.mkdirSync(require("path").dirname(p), { recursive: true }); fs.writeFileSync(p, JSON.stringify(c, null, 2)); '''; @@ -117,13 +123,13 @@ fs.writeFileSync(p, JSON.stringify(c, null, 2)); // Start fresh } - // Merge provider entry + // Merge provider entry — models must be objects with `id`, not bare strings (#83, #88). config['models'] ??= {}; (config['models'] as Map)['providers'] ??= {}; ((config['models'] as Map)['providers'] as Map)[providerId] = { 'apiKey': apiKey, 'baseUrl': baseUrl, - 'models': [model], + 'models': [{'id': model}], }; // Set active model @@ -132,6 +138,10 @@ fs.writeFileSync(p, JSON.stringify(c, null, 2)); ((config['agents'] as Map)['defaults'] as Map)['model'] ??= {}; (((config['agents'] as Map)['defaults'] as Map)['model'] as Map)['primary'] = model; + // Ensure gateway.mode is set (#93, #90) + config['gateway'] ??= {}; + (config['gateway'] as Map)['mode'] ??= 'local'; + const encoder = JsonEncoder.withIndent(' '); await NativeBridge.writeRootfsFile(_configPath, encoder.convert(config)); } diff --git a/flutter_app/pubspec.yaml b/flutter_app/pubspec.yaml index cfa8fde..4c0551d 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.5+16 +version: 1.8.6+17 environment: sdk: '>=3.2.0 <4.0.0' From 9cbfcd19d8b003aa4bcb49f8564b2b466a76b15f Mon Sep 17 00:00:00 2001 From: mithun50 Date: Fri, 27 Mar 2026 08:46:16 +0530 Subject: [PATCH 2/4] fix: Rename shadowed variable to avoid org.json resolution failure The local variable `org` shadowed the `org.json` package, causing `Unresolved reference: json` at compile time. Renamed to `json`. Co-Authored-By: Mithun Gowda B --- .../com/nxg/openclawproot/BootstrapManager.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt b/flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt index c5cac66..448ba5e 100644 --- a/flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt +++ b/flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/BootstrapManager.kt @@ -1263,21 +1263,21 @@ require('/root/.openclaw/proot-compat.js'); // Repair existing config: ensure gateway.mode and fix model entries (#83, #88) try { val content = configFile.readText() - val org = org.json.JSONObject(content) + val json = org.json.JSONObject(content) var modified = false - if (!org.has("gateway")) { - org.put("gateway", org.json.JSONObject().put("mode", "local")) + if (!json.has("gateway")) { + json.put("gateway", org.json.JSONObject().put("mode", "local")) modified = true } else { - val gw = org.getJSONObject("gateway") + val gw = json.getJSONObject("gateway") if (!gw.has("mode")) { gw.put("mode", "local") modified = true } } // Fix model entries: strings → objects with id field (#83, #88) - if (org.has("models")) { - val models = org.optJSONObject("models") + if (json.has("models")) { + val models = json.optJSONObject("models") val providers = models?.optJSONObject("providers") if (providers != null) { val keys = providers.keys() @@ -1298,7 +1298,7 @@ require('/root/.openclaw/proot-compat.js'); } } if (modified) { - configFile.writeText(org.toString(2)) + configFile.writeText(json.toString(2)) } } catch (_: Exception) {} } From d92dd0bc7b4ea0b909a539abdb5c9104d6663f27 Mon Sep 17 00:00:00 2001 From: mithun50 Date: Fri, 27 Mar 2026 16:27:00 +0530 Subject: [PATCH 3/4] chore: Bump npm package to 1.8.6, update deps, require Node >= 22 - Sync package.json version with Flutter app (1.8.6) - Run npm update to refresh dependency tree - Bump engine requirement from Node >= 18 to >= 22 Co-Authored-By: Mithun Gowda B --- package-lock.json | 82 +++++++++++++++++++++++------------------------ package.json | 4 +-- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index d5aff55..afa5a20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openclaw-termux", - "version": "1.5.5", + "version": "1.8.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openclaw-termux", - "version": "1.5.5", + "version": "1.8.6", "hasInstallScript": true, "license": "MIT", "os": [ @@ -74,15 +74,15 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -115,20 +115,20 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -152,9 +152,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { @@ -285,9 +285,9 @@ "license": "MIT" }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -308,9 +308,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -409,9 +409,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -619,25 +619,25 @@ } }, "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", + "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", @@ -656,7 +656,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -855,9 +855,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -1304,9 +1304,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -1431,12 +1431,12 @@ } }, "node_modules/ora/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" diff --git a/package.json b/package.json index 2640f56..d83db3b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openclaw-termux", - "version": "1.7.3", + "version": "1.8.6", "description": "OpenClaw AI Gateway for Android Termux with Bionic Bypass", "main": "lib/index.js", "type": "module", @@ -34,7 +34,7 @@ "url": "https://github.com/mithun50/openclaw-termux/issues" }, "engines": { - "node": ">=18.0.0" + "node": ">=22.0.0" }, "os": [ "android", From 201713fcc7f21b7a7bc1d6ff73c5f197eb4ec9e6 Mon Sep 17 00:00:00 2001 From: mithun50 Date: Fri, 27 Mar 2026 16:35:51 +0530 Subject: [PATCH 4/4] fix: Auto-repair Node.js and OpenClaw on startup instead of re-setup (#97) Expanded splash screen auto-repair to handle missing node binary and openclaw package when rootfs is intact. Previously only bionic-bypass was auto-repaired; now node and openclaw are reinstalled in-place, avoiding the "Setup OpenClaw" prompt after a node upgrade. Co-Authored-By: Mithun Gowda B --- CHANGELOG.md | 2 + flutter_app/lib/screens/splash_screen.dart | 45 +++++++++++++++++++--- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e0e612..a1c982d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,12 @@ - **Config Auto-Repair on Init (#88)** — Added `_repairConfigFile()` that runs on every `GatewayService.init()` to fix corrupted model entries and missing `gateway.mode`, preventing the crash-restart loop (5 restarts → stopped) - **Bionic Bypass Installation Robustness (#94)** — Added retry logic with parent directory creation if the initial `mkdirs()` fails silently on some devices - **Pre-seed Config on Setup** — `installBionicBypass()` now creates a default `openclaw.json` with `gateway.mode=local` during initial setup, so the gateway works immediately after installation +- **Setup Re-prompt After Node Upgrade (#97)** — Expanded auto-repair on splash screen to reinstall Node.js and OpenClaw when their binaries are missing but rootfs is intact, instead of forcing a full re-setup ### Enhancements - **Node.js Updated to 22.14.0** — Upgraded from 22.13.1 to latest 22.x LTS for better stability and compatibility (#87) +- **npm Package Synced to 1.8.6** — Updated package.json version, refreshed dependencies, bumped engine to Node >= 22 - **Removed Outdated Model** — Dropped `claude-3-5-sonnet-20241022` from Anthropic provider defaults --- diff --git a/flutter_app/lib/screens/splash_screen.dart b/flutter_app/lib/screens/splash_screen.dart index 8240a8a..c6b5387 100644 --- a/flutter_app/lib/screens/splash_screen.dart +++ b/flutter_app/lib/screens/splash_screen.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:io'; +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import '../constants.dart'; @@ -114,8 +115,8 @@ class _SplashScreenState extends State setupComplete = false; } - // Auto-repair: if only bionic-bypass is missing, regenerate it - // instead of forcing full re-setup (#70, #73). + // Auto-repair: if the rootfs and bash exist but other components are + // missing, try to repair them instead of forcing full re-setup (#70, #73, #97). if (!setupComplete) { try { final status = await NativeBridge.getBootstrapStatus(); @@ -125,9 +126,43 @@ class _SplashScreenState extends State final openclawOk = status['openclawInstalled'] == true; final bypassOk = status['bypassInstalled'] == true; - if (rootfsOk && bashOk && nodeOk && openclawOk && !bypassOk) { - setState(() => _status = 'Repairing bionic bypass...'); - await NativeBridge.installBionicBypass(); + // Core rootfs must exist — can't repair without it + if (rootfsOk && bashOk) { + // Regenerate bionic bypass if missing + if (!bypassOk) { + setState(() => _status = 'Repairing bionic bypass...'); + await NativeBridge.installBionicBypass(); + } + + // Reinstall node if binary is missing (#97) + if (!nodeOk) { + setState(() => _status = 'Reinstalling Node.js...'); + try { + final arch = await NativeBridge.getArch(); + final nodeTarUrl = AppConstants.getNodeTarballUrl(arch); + final filesDir = await NativeBridge.getFilesDir(); + final nodeTarPath = '$filesDir/tmp/nodejs.tar.xz'; + final dio = Dio(); + await dio.download(nodeTarUrl, nodeTarPath); + await NativeBridge.extractNodeTarball(nodeTarPath); + } catch (_) {} + } + + // Reinstall openclaw if package.json is missing (#97) + if (!openclawOk && nodeOk) { + setState(() => _status = 'Reinstalling OpenClaw...'); + try { + const wrapper = '/root/.openclaw/node-wrapper.js'; + const nodeRun = 'node $wrapper'; + const npmCli = '/usr/local/lib/node_modules/npm/bin/npm-cli.js'; + await NativeBridge.runInProot( + '$nodeRun $npmCli install -g openclaw', + timeout: 1800, + ); + await NativeBridge.createBinWrappers('openclaw'); + } catch (_) {} + } + setupComplete = await NativeBridge.isBootstrapComplete(); } } catch (_) {}