v1.4.0: Agentic feedback loop, Termux integration, workspace context, auto-build pipeline
This commit is contained in:
@@ -631,6 +631,15 @@ data: [DONE]
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### v1.4.0 (2026-05-19)
|
||||||
|
- **Agentic Feedback Loop** — build errors auto-sent back to AI for fixing (up to 3 retries)
|
||||||
|
- **Termux Integration** — auto-detects Termux, uses its `PATH` (aapt2, d8, ecj, tsu/su)
|
||||||
|
- **Workspace Context** — AI sees device info, available tools, project files before coding
|
||||||
|
- **AIDE-style Tool Protocol** — structured `[CREATE_FILE]`, `[RUN_COMMAND]`, `[BUILD_APK]`, `[INSTALL_APK]`
|
||||||
|
- **Auto-Build Pipeline** — aapt2 → ecj → d8 → sign → install chain
|
||||||
|
- **Build Error Auto-Correction** — captures exit codes and compiler errors, feeds back to AI
|
||||||
|
- **Root Detection** — detects `tsu`/`su` for elevated privileges via Termux
|
||||||
|
|
||||||
### v1.3.1 (2026-05-19)
|
### v1.3.1 (2026-05-19)
|
||||||
- **Auto-Execute** — AI actions now deploy files, build, and install APKs automatically (no manual button taps)
|
- **Auto-Execute** — AI actions now deploy files, build, and install APKs automatically (no manual button taps)
|
||||||
- **Keep Screen On** — toggle prevents Android screen sleep while agent is working
|
- **Keep Screen On** — toggle prevents Android screen sleep while agent is working
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ android {
|
|||||||
applicationId "ai.z.chat"
|
applicationId "ai.z.chat"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 8
|
versionCode 9
|
||||||
versionName "1.3.1"
|
versionName "1.4.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||||
|
|||||||
@@ -214,24 +214,52 @@ public class ShellPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String[] buildEnv() {
|
private String[] buildEnv() {
|
||||||
|
String termuxBin = "/data/data/com.termux/files/usr/bin";
|
||||||
|
String termuxPrefix = "/data/data/com.termux/files/usr";
|
||||||
|
boolean hasTermux = new File(termuxBin).isDirectory();
|
||||||
|
|
||||||
String toolsBin = toolsDir + "/bin";
|
String toolsBin = toolsDir + "/bin";
|
||||||
String toolsUsrBin = toolsDir + "/usr/bin";
|
String toolsUsrBin = toolsDir + "/usr/bin";
|
||||||
String appBin = homeDir + "/bin";
|
String appBin = homeDir + "/bin";
|
||||||
String systemPath = System.getenv("PATH");
|
String systemPath = System.getenv("PATH");
|
||||||
String path = appBin + ":" + toolsBin + ":" + toolsUsrBin + ":" + systemPath;
|
|
||||||
|
|
||||||
return new String[]{
|
StringBuilder pathBuilder = new StringBuilder();
|
||||||
"HOME=" + homeDir,
|
pathBuilder.append(appBin).append(":");
|
||||||
"PATH=" + path,
|
pathBuilder.append(toolsBin).append(":");
|
||||||
"PREFIX=" + toolsDir + "/usr",
|
pathBuilder.append(toolsUsrBin).append(":");
|
||||||
"TMPDIR=" + homeDir + "/tmp",
|
if (hasTermux) pathBuilder.append(termuxBin).append(":");
|
||||||
"TERM=xterm-256color",
|
pathBuilder.append(systemPath);
|
||||||
"LANG=en_US.UTF-8",
|
|
||||||
"ANDROID_HOME=" + toolsDir,
|
java.util.List<String> envList = new java.util.ArrayList<>();
|
||||||
"ANDROID_SDK_ROOT=" + toolsDir,
|
envList.add("HOME=" + homeDir);
|
||||||
"JAVA_HOME=" + toolsDir + "/java",
|
envList.add("PATH=" + pathBuilder.toString());
|
||||||
"PROJECTS=" + projectsDir
|
envList.add("PREFIX=" + (hasTermux ? termuxPrefix : toolsDir + "/usr"));
|
||||||
};
|
envList.add("TMPDIR=" + homeDir + "/tmp");
|
||||||
|
envList.add("TERM=xterm-256color");
|
||||||
|
envList.add("LANG=en_US.UTF-8");
|
||||||
|
envList.add("ANDROID_HOME=" + toolsDir);
|
||||||
|
envList.add("ANDROID_SDK_ROOT=" + toolsDir);
|
||||||
|
envList.add("JAVA_HOME=" + toolsDir + "/java");
|
||||||
|
envList.add("PROJECTS=" + projectsDir);
|
||||||
|
if (hasTermux) {
|
||||||
|
envList.add("TERMUX_VERSION=" + getTermuxVersion());
|
||||||
|
envList.add("LD_LIBRARY_PATH=" + termuxPrefix + "/lib");
|
||||||
|
}
|
||||||
|
|
||||||
|
return envList.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTermuxVersion() {
|
||||||
|
try {
|
||||||
|
File versionFile = new File("/data/data/com.termux/files/usr/share/doc/termux/VERSION");
|
||||||
|
if (versionFile.exists()) {
|
||||||
|
java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.FileReader(versionFile));
|
||||||
|
String version = reader.readLine();
|
||||||
|
reader.close();
|
||||||
|
return version != null ? version : "unknown";
|
||||||
|
}
|
||||||
|
} catch (Exception e) {}
|
||||||
|
return "installed";
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, String> toEnvMap(String[] envPairs) {
|
private Map<String, String> toEnvMap(String[] envPairs) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "zai-chat",
|
"name": "zai-chat",
|
||||||
"version": "1.3.1",
|
"version": "1.4.0",
|
||||||
"description": "Z.AI Chat - Full stack AI chat powered by GLM Coding Plan",
|
"description": "Z.AI Chat - Full stack AI chat powered by GLM Coding Plan",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -272,13 +272,26 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>About</h3>
|
<h3>About</h3>
|
||||||
<p class="about-text">Z.AI Chat v1.3.1</p>
|
<p class="about-text">Z.AI Chat v1.4.0</p>
|
||||||
<p class="about-text">Built with Z.AI SDK & GLM-5.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>
|
<p class="about-text">Compatible with Android 15/16</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>Changelog</h3>
|
<h3>Changelog</h3>
|
||||||
<ul class="changelog-list">
|
<ul class="changelog-list">
|
||||||
|
<li>
|
||||||
|
<span class="changelog-version">v1.4.0</span>
|
||||||
|
<span class="changelog-date">2026-05-19</span>
|
||||||
|
<ul>
|
||||||
|
<li>Agentic feedback loop — build errors auto-sent back to AI for fixing (up to 3 retries)</li>
|
||||||
|
<li>Termux integration — auto-detects Termux on device, uses its PATH (aapt2, d8, ecj, tsu)</li>
|
||||||
|
<li>Workspace context — AI sees device info, available tools, and project files</li>
|
||||||
|
<li>AIDE-style tool protocol — upgraded agentic prompt with structured tool use</li>
|
||||||
|
<li>Auto-build pipeline — full aapt2→ecj→d8→sign→install chain</li>
|
||||||
|
<li>Build error detection — captures exit codes, feeds errors back for auto-correction</li>
|
||||||
|
<li>Termux root support — detects tsu/su for elevated privileges</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="changelog-version">v1.3.1</span>
|
<span class="changelog-version">v1.3.1</span>
|
||||||
<span class="changelog-date">2026-05-19</span>
|
<span class="changelog-date">2026-05-19</span>
|
||||||
|
|||||||
240
www/js/app.js
240
www/js/app.js
@@ -8,7 +8,7 @@
|
|||||||
chat: 'You are a helpful, knowledgeable AI assistant. Be concise and accurate.',
|
chat: 'You are a helpful, knowledgeable AI assistant. Be concise and accurate.',
|
||||||
coding: 'You are an expert coding assistant. Write clean, efficient, well-documented code. Always use markdown code blocks with language tags. Explain your approach briefly before and after code. Handle edge cases and errors properly.',
|
coding: 'You are an expert coding assistant. Write clean, efficient, well-documented code. Always use markdown code blocks with language tags. Explain your approach briefly before and after code. Handle edge cases and errors properly.',
|
||||||
brainstorm: 'You are a creative brainstorming partner. Generate diverse ideas, explore unconventional angles, build on concepts, and help evaluate trade-offs. Think freely and expansively. Present ideas in organized lists or tables when appropriate.',
|
brainstorm: 'You are a creative brainstorming partner. Generate diverse ideas, explore unconventional angles, build on concepts, and help evaluate trade-offs. Think freely and expansively. Present ideas in organized lists or tables when appropriate.',
|
||||||
agentic: 'You are an autonomous coding agent with direct terminal access on this Android device. You can write files, compile code, build APKs, and install apps locally. Use these tool formats:\n\n[CREATE_FILE path/to/file.ext]\nfile contents here\n[/CREATE_FILE]\n\n[RUN_COMMAND]\nshell command here\n[/RUN_COMMAND]\n\n[BUILD_APK project_name]\nbuilds Android project into installable APK\n[/BUILD_APK]\n\n[INSTALL_APK /path/to/file.apk]\ninstalls APK on this device\n[/INSTALL_APK]\n\nYou have access to: aapt2 (resource compiler), d8 (dex compiler), ecj (Java compiler), apksigner, and standard shell tools. Always: 1) Write all source files 2) Build step by step 3) Sign the APK 4) Offer to install it. When the user asks you to build an app, generate ALL files needed, build, sign, and provide the installable APK.'
|
agentic: 'You are an autonomous coding agent with FULL control of an Android device. You have a real terminal with shell access, can read/write any file, build APKs, install apps, and execute any command.\n\n## Your Tools (use these EXACT formats):\n\n### Write a file:\n[CREATE_FILE path/to/file.ext]\nfile contents here\n[/CREATE_FILE]\n\n### Run a shell command:\n[RUN_COMMAND]\ncommand here\n[/RUN_COMMAND]\n\n### Build Android APK:\n[BUILD_APK project_name]\n\n### Install APK on device:\n[INSTALL_APK /path/to/file.apk]\n\n### List files:\n[RUN_COMMAND]\nfind . -type f | head -50\n[/RUN_COMMAND]\n\n## IMPORTANT RULES:\n1. ALWAYS use [CREATE_FILE] for EVERY file — the system auto-saves them to the device\n2. ALWAYS use [BUILD_APK] after writing files — the system auto-compiles\n3. ALWAYS use [INSTALL_APK] after building — the system auto-installs\n4. NEVER say "I installed it" unless you used [INSTALL_APK] — the system executes your tags automatically\n5. If a build fails, you will see the error output — FIX the code and try again\n6. Generate COMPLETE files — never use "// ... existing code ..."\n7. For Java: use package ai.z.app, target SDK 36, compile SDK 36\n8. You can run ANY shell command: ls, cat, mkdir, chmod, cp, grep, find, etc.\n9. If Termux tools are available, use: aapt2, d8, ecj, javac, apksigner\n10. Write ALL files first, THEN build, THEN install. Always in that order.\n11. When the user asks to build an app, generate EVERY file needed for a complete working app.'
|
||||||
};
|
};
|
||||||
|
|
||||||
var state = {
|
var state = {
|
||||||
@@ -471,6 +471,12 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
var systemPrompt = MODE_PROMPTS[state.currentMode] || MODE_PROMPTS.chat;
|
var systemPrompt = MODE_PROMPTS[state.currentMode] || MODE_PROMPTS.chat;
|
||||||
|
|
||||||
|
if (Shell && (state.currentMode === 'agentic' || state.currentMode === 'coding')) {
|
||||||
|
var wsCtx = await getWorkspaceContext();
|
||||||
|
if (wsCtx) systemPrompt += '\n\n## Current Device Context:\n' + wsCtx;
|
||||||
|
}
|
||||||
|
|
||||||
var apiMessages = [{ role: 'system', content: systemPrompt }];
|
var apiMessages = [{ role: 'system', content: systemPrompt }];
|
||||||
conv.messages.forEach(function(m) {
|
conv.messages.forEach(function(m) {
|
||||||
if (m.role === 'user' || (m.role === 'assistant' && !m._streaming)) {
|
if (m.role === 'user' || (m.role === 'assistant' && !m._streaming)) {
|
||||||
@@ -1117,6 +1123,27 @@
|
|||||||
try { return await Installer.getDeviceInfo(); } catch(e) { return {}; }
|
try { return await Installer.getDeviceInfo(); } catch(e) { return {}; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getWorkspaceContext() {
|
||||||
|
var ctx = '';
|
||||||
|
try {
|
||||||
|
var info = await getDeviceInfo();
|
||||||
|
ctx += 'Device: ' + (info.manufacturer || '') + ' ' + (info.model || '') + ', Android ' + (info.release || '') + ' (SDK ' + (info.sdk || '') + '), ABI: ' + (info.abi || '') + '\n';
|
||||||
|
|
||||||
|
var envResult = await shellExec('echo "HOME=$HOME CWD=$(pwd) TERMUX=${TERMUX_VERSION:-none}"', termState.cwd || termState.homeDir, false);
|
||||||
|
ctx += 'Environment: ' + (envResult.output || '').trim() + '\n';
|
||||||
|
|
||||||
|
var toolCheck = await shellExec('which aapt2 d8 ecj javac apksigner zipalign 2>/dev/null; echo "---"; which tsu su 2>/dev/null || echo "no-root"', termState.homeDir, false);
|
||||||
|
ctx += 'Available tools: ' + (toolCheck.output || '').trim().replace(/\n---\n/, '\nRoot access: ') + '\n';
|
||||||
|
|
||||||
|
var projectDir = termState.projectsDir || (termState.homeDir + '/projects');
|
||||||
|
var lsResult = await shellExec('find ' + projectDir + ' -type f 2>/dev/null | head -30 || echo "no-projects"', termState.homeDir, false);
|
||||||
|
if (lsResult.output && lsResult.output.indexOf('no-projects') < 0 && lsResult.output.trim()) {
|
||||||
|
ctx += 'Project files:\n' + lsResult.output.trim() + '\n';
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
return ctx || null;
|
||||||
|
}
|
||||||
|
|
||||||
function termPrint(text, className) {
|
function termPrint(text, className) {
|
||||||
var output = $('#term-output');
|
var output = $('#term-output');
|
||||||
if (!output) return;
|
if (!output) return;
|
||||||
@@ -1384,6 +1411,9 @@
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _agenticRetryCount = 0;
|
||||||
|
var MAX_AGENTIC_RETRIES = 3;
|
||||||
|
|
||||||
async function autoExecuteActions(actions, conv) {
|
async function autoExecuteActions(actions, conv) {
|
||||||
var hasFiles = actions.some(function(a) { return a.type === 'create_file'; });
|
var hasFiles = actions.some(function(a) { return a.type === 'create_file'; });
|
||||||
var hasBuild = actions.some(function(a) { return a.type === 'build_apk'; });
|
var hasBuild = actions.some(function(a) { return a.type === 'build_apk'; });
|
||||||
@@ -1392,25 +1422,43 @@
|
|||||||
|
|
||||||
if (!hasFiles && !hasBuild && !hasInstall && !hasCommands) return;
|
if (!hasFiles && !hasBuild && !hasInstall && !hasCommands) return;
|
||||||
|
|
||||||
|
_agenticRetryCount = 0;
|
||||||
|
var resultLog = [];
|
||||||
|
|
||||||
var fileCount = actions.filter(function(a) { return a.type === 'create_file'; }).length;
|
var fileCount = actions.filter(function(a) { return a.type === 'create_file'; }).length;
|
||||||
if (fileCount > 0) {
|
if (fileCount > 0) {
|
||||||
showStatusToast('Auto-deploying ' + fileCount + ' file' + (fileCount > 1 ? 's' : '') + '...', 'info');
|
showStatusToast('Deploying ' + fileCount + ' file' + (fileCount > 1 ? 's' : '') + '...', 'info');
|
||||||
await deployActions(actions);
|
for (var i = 0; i < actions.length; i++) {
|
||||||
|
if (actions[i].type === 'create_file') {
|
||||||
|
var deployOk = await autoDeployFile(actions[i]);
|
||||||
|
resultLog.push(deployOk);
|
||||||
|
}
|
||||||
|
}
|
||||||
showStatusToast(fileCount + ' file' + (fileCount > 1 ? 's' : '') + ' deployed', 'success');
|
showStatusToast(fileCount + ' file' + (fileCount > 1 ? 's' : '') + ' deployed', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasCommands) {
|
if (hasCommands) {
|
||||||
for (var i = 0; i < actions.length; i++) {
|
for (var c = 0; c < actions.length; c++) {
|
||||||
if (actions[i].type === 'run_command') {
|
if (actions[c].type === 'run_command') {
|
||||||
showStatusToast('Running: ' + actions[i].command.substring(0, 40) + '...', 'info');
|
showStatusToast('Running: ' + actions[c].command.substring(0, 40), 'info');
|
||||||
await termQueueCommand(actions[i].command);
|
var cmdResult = await shellExec(actions[c].command, termState.cwd, false);
|
||||||
|
resultLog.push('CMD: ' + actions[c].command.substring(0, 60) + '\nexit: ' + (cmdResult.exitCode !== undefined ? cmdResult.exitCode : '?') + '\n' + (cmdResult.output || '').substring(0, 500));
|
||||||
|
if (cmdResult.output) {
|
||||||
|
termPrint('$ ' + actions[c].command, 'cmd');
|
||||||
|
termPrint(cmdResult.output.replace(/\n$/, ''), cmdResult.exitCode === 0 ? '' : 'err');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasBuild) {
|
if (hasBuild) {
|
||||||
showStatusToast('Building APK...', 'info');
|
showStatusToast('Building APK...', 'info');
|
||||||
await buildFromActions(actions);
|
var buildResult = await autoBuildApk(actions);
|
||||||
|
resultLog.push(buildResult);
|
||||||
|
if (buildResult.indexOf('[BUILD FAILED]') >= 0) {
|
||||||
|
await agenticRetryOnError(buildResult, conv);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasInstall) {
|
if (hasInstall) {
|
||||||
@@ -1418,11 +1466,187 @@
|
|||||||
if (actions[j].type === 'install_apk') {
|
if (actions[j].type === 'install_apk') {
|
||||||
showStatusToast('Installing APK...', 'info');
|
showStatusToast('Installing APK...', 'info');
|
||||||
await installApk(actions[j].path);
|
await installApk(actions[j].path);
|
||||||
|
resultLog.push('INSTALL: ' + actions[j].path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function autoDeployFile(action) {
|
||||||
|
var path = action.path;
|
||||||
|
if (!path.startsWith('/')) {
|
||||||
|
path = (termState.projectsDir || termState.homeDir + '/projects') + '/' + path;
|
||||||
|
}
|
||||||
|
var dir = path.substring(0, path.lastIndexOf('/'));
|
||||||
|
await shellMkdirs(dir);
|
||||||
|
var ok = await shellWriteFile(path, action.content);
|
||||||
|
if (ok) {
|
||||||
|
return '[OK] ' + path + ' (' + action.content.length + ' bytes)';
|
||||||
|
} else {
|
||||||
|
return '[FAIL] ' + path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function autoBuildApk(actions) {
|
||||||
|
var projectDir = termState.projectsDir || (termState.homeDir + '/projects');
|
||||||
|
|
||||||
|
for (var i = 0; i < actions.length; i++) {
|
||||||
|
if (actions[i].type === 'create_file') {
|
||||||
|
await autoDeployFile(actions[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
termPrint('\n--- Building APK ---', 'info');
|
||||||
|
|
||||||
|
var buildScript =
|
||||||
|
'cd ' + projectDir + ' && ' +
|
||||||
|
'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 && ' +
|
||||||
|
'mkdir -p build/gen build/classes && ' +
|
||||||
|
'echo "[*] Compiling resources..." && ' +
|
||||||
|
'$AAPT2 compile --dir app/src/main/res -o build/compiled_resources.zip 2>&1 && ' +
|
||||||
|
'echo "[*] Linking APK..." && ' +
|
||||||
|
'$AAPT2 link -o build/app.unsigned.apk ' +
|
||||||
|
' -I $(dirname $(which aapt2))/../share/aapt2/android.jar ' +
|
||||||
|
' --manifest app/src/main/AndroidManifest.xml ' +
|
||||||
|
' -R build/compiled_resources.zip ' +
|
||||||
|
' --java build/gen 2>&1 && ' +
|
||||||
|
'echo "[*] Compiling Java..." && ' +
|
||||||
|
'find app/src/main/java -name "*.java" > build/sources.txt 2>/dev/null && ' +
|
||||||
|
'ANDROID_JAR=$(dirname $(which aapt2))/../share/aapt2/android.jar && ' +
|
||||||
|
'$ECJ -source 11 -target 11 -classpath $ANDROID_JAR -d build/classes @build/sources.txt 2>&1 && ' +
|
||||||
|
'echo "[*] DEX..." && ' +
|
||||||
|
'if [ -n "$D8" ]; then ' +
|
||||||
|
' $D8 --output build/ build/classes/**/*.class 2>&1; ' +
|
||||||
|
'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; ' +
|
||||||
|
'fi && ' +
|
||||||
|
'echo "[*] Packaging..." && ' +
|
||||||
|
'cd build && ' +
|
||||||
|
'if [ -f classes.dex ]; then cp classes.dex .; fi && ' +
|
||||||
|
'mkdir -p apk_staging && cd apk_staging && ' +
|
||||||
|
'unzip -o ../app.unsigned.apk 2>&1 && ' +
|
||||||
|
'cp ../classes.dex . 2>/dev/null; cp ../build/classes.dex . 2>/dev/null && ' +
|
||||||
|
'zip -r ../app.unaligned.apk . 2>&1 && cd .. && rm -rf apk_staging && ' +
|
||||||
|
'echo "[*] Signing..." && ' +
|
||||||
|
'APKSIGNER=$(which apksigner 2>/dev/null) && ' +
|
||||||
|
'if [ -n "$APKSIGNER" ]; then ' +
|
||||||
|
' $APKSIGNER sign --ks /dev/null --ks-pass pass:dummy app.unaligned.apk 2>&1 || ' +
|
||||||
|
' cp app.unaligned.apk app-signed.apk; ' +
|
||||||
|
'else ' +
|
||||||
|
' cp app.unaligned.apk app-signed.apk; ' +
|
||||||
|
'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';
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
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 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');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
termPrint('\n[!] Build failed. Asking AI to fix (attempt ' + _agenticRetryCount + '/' + MAX_AGENTIC_RETRIES + ')...', 'warning');
|
||||||
|
showStatusToast('Build failed — AI auto-fixing (attempt ' + _agenticRetryCount + ')...', 'err');
|
||||||
|
|
||||||
|
if (!conv || !state.apiKey) return;
|
||||||
|
|
||||||
|
var fixMessage = 'The build failed with this error:\n\n```\n' +
|
||||||
|
errorOutput.substring(0, 2000) +
|
||||||
|
'\n```\n\nPlease fix the code and rebuild. Write ALL corrected files and use [BUILD_APK] again.';
|
||||||
|
|
||||||
|
if (state.keepAwake) setWakeLock(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var conv2 = getConversation(conv.id) || conv;
|
||||||
|
conv2.messages.push({ role: 'user', content: fixMessage });
|
||||||
|
saveState();
|
||||||
|
appendMessage('user', fixMessage);
|
||||||
|
|
||||||
|
state.isGenerating = true;
|
||||||
|
state.streamingConvId = conv2.id;
|
||||||
|
state.streamingContent = '';
|
||||||
|
updateSendButton();
|
||||||
|
showThinking();
|
||||||
|
|
||||||
|
var systemPrompt = MODE_PROMPTS.agentic;
|
||||||
|
var apiMessages = [{ role: 'system', content: systemPrompt }];
|
||||||
|
conv2.messages.forEach(function(m) {
|
||||||
|
if (m.role === 'user' || (m.role === 'assistant' && !m._streaming)) {
|
||||||
|
apiMessages.push({ role: m.role, content: m.content });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var requestBody = {
|
||||||
|
model: state.model,
|
||||||
|
messages: apiMessages,
|
||||||
|
temperature: state.temperature,
|
||||||
|
max_tokens: state.maxTokens,
|
||||||
|
stream: state.streaming
|
||||||
|
};
|
||||||
|
|
||||||
|
removeThinking();
|
||||||
|
var responseDiv = appendMessage('assistant', '');
|
||||||
|
state.streamingResponseDiv = responseDiv;
|
||||||
|
|
||||||
|
if (state.streaming) {
|
||||||
|
await streamResponseWithRetry(requestBody, responseDiv, conv2);
|
||||||
|
} else {
|
||||||
|
var res = await apiRequestWithRetry(requestBody);
|
||||||
|
updateStreamingMessage(responseDiv, res.choices[0].message.content);
|
||||||
|
state.streamingContent = res.choices[0].message.content;
|
||||||
|
conv2.messages.push({ role: 'assistant', content: res.choices[0].message.content });
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixContent = state.streamingContent || '';
|
||||||
|
if (fixContent) {
|
||||||
|
conv2.messages.push({ role: 'assistant', content: fixContent });
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
termPrint('[!] Auto-fix failed: ' + err.message, 'err');
|
||||||
|
} finally {
|
||||||
|
state.isGenerating = false;
|
||||||
|
state.abortController = null;
|
||||||
|
state.streamingConvId = null;
|
||||||
|
state.streamingResponseDiv = null;
|
||||||
|
updateSendButton();
|
||||||
|
saveState();
|
||||||
|
if (state.keepAwake) setWakeLock(false);
|
||||||
|
|
||||||
|
var fixActions = parseAiActions(state.streamingContent || '');
|
||||||
|
if (fixActions.length > 0 && _agenticRetryCount <= MAX_AGENTIC_RETRIES) {
|
||||||
|
await autoExecuteActions(fixActions, conv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function buildFromActions(actions) {
|
async function buildFromActions(actions) {
|
||||||
showScreen('terminal');
|
showScreen('terminal');
|
||||||
termPrint('\n--- Building APK ---', 'info');
|
termPrint('\n--- Building APK ---', 'info');
|
||||||
|
|||||||
Reference in New Issue
Block a user