v2.0.1: APK verification, stay awake fix, configurable auto-fix retries
This commit is contained in:
@@ -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 1–30 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)
|
||||
|
||||
@@ -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:!*~'
|
||||
|
||||
@@ -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
4
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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 & 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 (1–30) in Settings, default 10</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<span class="changelog-version">v2.0.0</span>
|
||||
<span class="changelog-date">2026-05-19</span>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user