v2.0.1: APK verification, stay awake fix, configurable auto-fix retries

This commit is contained in:
admin
2026-05-19 18:04:33 +04:00
Unverified
parent e7015b129a
commit 5572f2cb60
7 changed files with 88 additions and 33 deletions

View File

@@ -631,6 +631,11 @@ data: [DONE]
## Changelog
### v2.0.1 (2026-05-19)
- **APK Build Verification** — confirms APK file exists after build, shows file size
- **Stay Awake Fix** — dual wake locks (screen bright + CPU partial) with 24h timeout keep device fully awake
- **Configurable Auto-Fix Retries** — max retries adjustable from 130 in Settings (default 10, was hardcoded 3)
### v2.0.0 (2026-05-19)
- **Built-in Termux** — full Linux environment inside the app, no external Termux install needed
- One-time ~30MB download of Termux bootstrap (bash, coreutils, apt, 25+ packages)

View File

@@ -7,8 +7,8 @@ android {
applicationId "ai.z.chat"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 10
versionName "2.0.0"
versionCode 11
versionName "2.0.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'

View File

@@ -12,7 +12,8 @@ import com.getcapacitor.annotation.CapacitorPlugin;
@CapacitorPlugin(name = "Wake")
public class WakePlugin extends Plugin {
private PowerManager.WakeLock wakeLock;
private PowerManager.WakeLock screenWakeLock;
private PowerManager.WakeLock cpuWakeLock;
private boolean isHeld = false;
@Override
@@ -22,7 +23,7 @@ public class WakePlugin extends Plugin {
@PluginMethod
public void acquire(PluginCall call) {
if (isHeld && wakeLock != null) {
if (isHeld) {
call.resolve(new JSObject().put("held", true));
return;
}
@@ -33,10 +34,20 @@ public class WakePlugin extends Plugin {
});
PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "zai-chat:wakelock");
wakeLock.acquire(12 * 60 * 60 * 1000L);
isHeld = true;
screenWakeLock = pm.newWakeLock(
PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
"zai-chat:screen"
);
screenWakeLock.acquire(24 * 60 * 60 * 1000L);
cpuWakeLock = pm.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"zai-chat:cpu"
);
cpuWakeLock.acquire(24 * 60 * 60 * 1000L);
isHeld = true;
call.resolve(new JSObject().put("held", true));
} catch (Exception e) {
call.reject("Wake lock failed: " + e.getMessage());
@@ -50,12 +61,17 @@ public class WakePlugin extends Plugin {
getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
});
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
if (screenWakeLock != null && screenWakeLock.isHeld()) {
screenWakeLock.release();
}
wakeLock = null;
isHeld = false;
screenWakeLock = null;
if (cpuWakeLock != null && cpuWakeLock.isHeld()) {
cpuWakeLock.release();
}
cpuWakeLock = null;
isHeld = false;
call.resolve(new JSObject().put("held", false));
} catch (Exception e) {
call.reject("Wake release failed: " + e.getMessage());

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "zai-chat",
"version": "1.3.0",
"version": "2.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "zai-chat",
"version": "1.3.0",
"version": "2.0.1",
"license": "MIT",
"dependencies": {
"@capacitor/android": "^8.3.4",

View File

@@ -1,6 +1,6 @@
{
"name": "zai-chat",
"version": "2.0.0",
"version": "2.0.1",
"description": "Z.AI Chat - Full stack AI chat powered by GLM Coding Plan",
"main": "index.js",
"scripts": {

View File

@@ -256,6 +256,11 @@
</label>
</div>
<span class="input-hint">Prevents screen sleep while agent is working</span>
<div class="input-group" style="margin-top:12px">
<label>Max Auto-Fix Retries: <span id="retries-value">10</span></label>
<input type="range" id="settings-maxretries" min="1" max="30" step="1" value="10">
</div>
<span class="input-hint">How many times AI will auto-retry after build failures</span>
</div>
<div class="settings-section">
<h3>Appearance</h3>
@@ -274,13 +279,22 @@
</div>
<div class="settings-section">
<h3>About</h3>
<p class="about-text">Z.AI Chat v2.0.0</p>
<p class="about-text">Z.AI Chat v2.0.1</p>
<p class="about-text">Built with Z.AI SDK &amp; GLM-5.1</p>
<p class="about-text">Compatible with Android 15/16</p>
</div>
<div class="settings-section">
<h3>Changelog</h3>
<ul class="changelog-list">
<li>
<span class="changelog-version">v2.0.1</span>
<span class="changelog-date">2026-05-19</span>
<ul>
<li><strong>APK Verification</strong> — build output now verified: confirms APK file exists and shows size</li>
<li><strong>Stay Awake Fix</strong> — dual wake locks (screen bright + CPU) keep device fully awake during builds</li>
<li><strong>Configurable Retries</strong> — max auto-fix retries now adjustable (130) in Settings, default 10</li>
</ul>
</li>
<li>
<span class="changelog-version">v2.0.0</span>
<span class="changelog-date">2026-05-19</span>

View File

@@ -30,7 +30,8 @@
streamingResponseDiv: null,
terminalOpen: false,
keepAwake: false,
autoDeploy: true
autoDeploy: true,
maxRetries: 10
};
function $(sel) { return document.querySelector(sel); }
@@ -50,6 +51,7 @@
state.terminalOpen = localStorage.getItem(STORAGE_KEY + 'terminalOpen') === 'true';
state.keepAwake = localStorage.getItem(STORAGE_KEY + 'keepAwake') === 'true';
state.autoDeploy = localStorage.getItem(STORAGE_KEY + 'autoDeploy') !== 'false';
state.maxRetries = parseInt(localStorage.getItem(STORAGE_KEY + 'maxRetries')) || 10;
var convData = localStorage.getItem(STORAGE_KEY + 'conversations');
state.conversations = convData ? JSON.parse(convData) : [];
state.activeConversationId = localStorage.getItem(STORAGE_KEY + 'activeConv') || null;
@@ -70,6 +72,7 @@
localStorage.setItem(STORAGE_KEY + 'terminalOpen', state.terminalOpen.toString());
localStorage.setItem(STORAGE_KEY + 'keepAwake', state.keepAwake.toString());
localStorage.setItem(STORAGE_KEY + 'autoDeploy', state.autoDeploy.toString());
localStorage.setItem(STORAGE_KEY + 'maxRetries', state.maxRetries.toString());
localStorage.setItem(STORAGE_KEY + 'conversations', JSON.stringify(state.conversations));
localStorage.setItem(STORAGE_KEY + 'activeConv', state.activeConversationId || '');
} catch(e) { console.error('Save state error:', e); }
@@ -1415,7 +1418,10 @@
}
var _agenticRetryCount = 0;
var MAX_AGENTIC_RETRIES = 3;
function getMaxRetries() {
return state.maxRetries || 10;
}
async function autoExecuteActions(actions, conv) {
var hasFiles = actions.some(function(a) { return a.type === 'create_file'; });
@@ -1506,8 +1512,8 @@
'AAPT2=$(which aapt2 2>/dev/null) && ' +
'D8=$(which d8 2>/dev/null) && ' +
'ECJ=$(which ecj 2>/dev/null) && ' +
'if [ -z "$AAPT2" ]; then echo "[BUILD FAILED] aapt2 not found. Install Termux: pkg install aapt2"; exit 1; fi && ' +
'if [ -z "$ECJ" ]; then echo "[BUILD FAILED] ecj not found. Install Termux: pkg install ecj"; exit 1; fi && ' +
'if [ -z "$AAPT2" ]; then echo "[BUILD FAILED] aapt2 not found. Install via: pkg install aapt2"; exit 1; fi && ' +
'if [ -z "$ECJ" ]; then echo "[BUILD FAILED] ecj not found. Install via: pkg install ecj"; exit 1; fi && ' +
'mkdir -p build/gen build/classes && ' +
'echo "[*] Compiling resources..." && ' +
'$AAPT2 compile --dir app/src/main/res -o build/compiled_resources.zip 2>&1 && ' +
@@ -1527,7 +1533,7 @@
'else ' +
' DX=$(which dx 2>/dev/null) && ' +
' if [ -n "$DX" ]; then $DX --output build/classes.dex build/classes/ 2>&1; ' +
' else echo "[BUILD FAILED] d8/dx not found. Install Termux: pkg install dx"; exit 1; fi; ' +
' else echo "[BUILD FAILED] d8/dx not found. Install via: pkg install dx"; exit 1; fi; ' +
'fi && ' +
'echo "[*] Packaging..." && ' +
'cd build && ' +
@@ -1546,37 +1552,41 @@
'fi && ' +
'APK_PATH="' + projectDir + '/build/app-signed.apk" && ' +
'APK_SIZE=$(du -h app-signed.apk 2>/dev/null | cut -f1) && ' +
'echo "[BUILD OK] APK: $APK_PATH ($APK_SIZE)" && ' +
'echo $APK_PATH';
'echo "[BUILD OK] APK: $APK_PATH ($APK_SIZE)"';
var result = await shellExec(buildScript, termState.homeDir, false);
var output = result.output || '';
termPrint(output.replace(/\n$/, ''), result.exitCode === 0 ? '' : 'err');
if (output.indexOf('[BUILD OK]') >= 0) {
var apkMatch = output.match(/\[BUILD OK\] APK: ([^\s]+)/);
if (apkMatch) {
termPrint('\nAPK ready: ' + apkMatch[1], 'success');
termPrint('Run: install ' + apkMatch[1], 'info');
var apkPath = projectDir + '/build/app-signed.apk';
var verifyResult = await shellExec('ls -la ' + apkPath + ' 2>&1', termState.homeDir, false);
if (verifyResult.output && verifyResult.output.indexOf('No such file') < 0) {
var sizeMatch = verifyResult.output.match(/(\d+)\s+/);
var sizeInfo = sizeMatch ? ' (' + Math.round(parseInt(sizeMatch[1]) / 1024) + ' KB)' : '';
termPrint('\n[VERIFIED] APK built successfully: ' + apkPath + sizeInfo, 'success');
return '[BUILD OK] ' + apkPath;
} else {
termPrint('\n[VERIFY FAILED] Build claimed success but APK not found at ' + apkPath, 'err');
return '[BUILD FAILED] APK file not found after build. Output:\n' + output.substring(0, 1000);
}
return '[BUILD OK] ' + output.substring(output.indexOf('[BUILD OK]'));
} else if (output.indexOf('[BUILD FAILED]') >= 0) {
return '[BUILD FAILED] ' + output;
} else if (result.exitCode !== 0) {
return '[BUILD FAILED] exit=' + result.exitCode + '\n' + output.substring(0, 1000);
return '[BUILD FAILED] exit=' + result.exitCode + '\n' + output.substring(0, 1500);
}
return output.substring(0, 500);
}
async function agenticRetryOnError(errorOutput, conv) {
_agenticRetryCount++;
if (_agenticRetryCount > MAX_AGENTIC_RETRIES) {
termPrint('\n[!] Max retries reached (' + MAX_AGENTIC_RETRIES + '). Fix manually or ask again.', 'err');
showStatusToast('Build failed after ' + MAX_AGENTIC_RETRIES + ' retries', 'err');
if (_agenticRetryCount > getMaxRetries()) {
termPrint('\n[!] Max retries reached (' + getMaxRetries() + '). Fix manually or ask again.', 'err');
showStatusToast('Build failed after ' + getMaxRetries() + ' retries', 'err');
return;
}
termPrint('\n[!] Build failed. Asking AI to fix (attempt ' + _agenticRetryCount + '/' + MAX_AGENTIC_RETRIES + ')...', 'warning');
termPrint('\n[!] Build failed. Asking AI to fix (attempt ' + _agenticRetryCount + '/' + getMaxRetries() + ')...', 'warning');
showStatusToast('Build failed — AI auto-fixing (attempt ' + _agenticRetryCount + ')...', 'err');
if (!conv || !state.apiKey) return;
@@ -1644,7 +1654,7 @@
if (state.keepAwake) setWakeLock(false);
var fixActions = parseAiActions(state.streamingContent || '');
if (fixActions.length > 0 && _agenticRetryCount <= MAX_AGENTIC_RETRIES) {
if (fixActions.length > 0 && _agenticRetryCount <= getMaxRetries()) {
await autoExecuteActions(fixActions, conv);
}
}
@@ -1982,6 +1992,8 @@
$('#settings-streaming').checked = state.streaming;
$('#settings-autodeploy').checked = state.autoDeploy;
$('#settings-keepawake').checked = state.keepAwake;
$('#settings-maxretries').value = state.maxRetries;
$('#retries-value').textContent = state.maxRetries;
}
function saveSettings() {
@@ -2135,6 +2147,14 @@
saveState();
});
$('#settings-maxretries').addEventListener('input', function() {
$('#retries-value').textContent = this.value;
});
$('#settings-maxretries').addEventListener('change', function() {
state.maxRetries = parseInt(this.value) || 10;
saveState();
});
$('#theme-toggle-header').addEventListener('click', toggleTheme);
$('#settings-darkmode').addEventListener('change', function() {