v3.1.0: AutoGLM device control + Hermes agent integration for coding/agentic modes

This commit is contained in:
admin
2026-05-20 13:23:47 +04:00
Unverified
parent f86a5added
commit 50983eb011
12 changed files with 1022 additions and 8 deletions

View File

@@ -8,7 +8,7 @@
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.',
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 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.\n12. CRITICAL: When you have FULLY completed the user\'s entire task (all files written, all builds done, all installs done), output [TASK_COMPLETE] on a line by itself. This is MANDATORY — the system uses it to know you are done.\n13. If your response is cut off or you haven\'t finished all work, do NOT output [TASK_COMPLETE]. The system will automatically continue you.\n14. NEVER output [TASK_COMPLETE] unless the ENTIRE task is truly done. If there are more files to write, commands to run, or builds to perform, keep working.'
agentic: 'You are an autonomous agent with FULL control of an Android device. You have a real terminal, can build APKs, control the device UI via AutoGLM, and use Hermes agent tools.\n\n## File Operations:\n[CREATE_FILE path/to/file.ext]\ncontents\n[/CREATE_FILE]\n\n## Shell:\n[RUN_COMMAND]\ncommand\n[/RUN_COMMAND]\n\n## Build:\n[BUILD_APK project_name]\n[INSTALL_APK /path/to/file.apk]\n\n## AutoGLM Device Control:\n[DEVICE_TAP x y]\n[DEVICE_LONG_PRESS x y]\n[DEVICE_SWIPE startX startY endX endY]\n[DEVICE_TYPE text]\n[DEVICE_PRESS_BACK]\n[DEVICE_PRESS_HOME]\n[DEVICE_PRESS_RECENTS]\n[DEVICE_SCREENSHOT]\n[DEVICE_UI_TREE]\n[DEVICE_CLICK_TEXT button text]\n[DEVICE_CLICK_ID com.example:id/viewId]\n[DEVICE_LAUNCH com.example.app]\n[DEVICE_CURRENT_APP]\n\n## Hermes Agent:\n[HERMES_INSTALL]\n[HERMES_EXEC command]\n\n## Rules:\n1. Use [CREATE_FILE] for files, [BUILD_APK] to compile, [INSTALL_APK] to install\n2. Use [DEVICE_*] for device control — first [DEVICE_UI_TREE] to see screen, then interact\n3. Use [HERMES_EXEC] for Hermes capabilities — web search, terminal, skills, memory\n4. Generate COMPLETE files, never stubs\n5. For Java: package ai.z.app, target SDK 36\n6. Output [TASK_COMPLETE] ONLY when ALL work is done\n7. Never say done unless all files written, builds done, installs done'
};
var BUILD_SCRIPT = [
@@ -1175,6 +1175,9 @@
hasProot: false,
prootPath: '',
nativeLibDir: '',
hermesPath: '',
hermesVenv: '',
autoglmEnabled: false,
commandQueue: []
};
@@ -1423,7 +1426,21 @@
var runCmdRegex = /\[RUN_COMMAND\]\n([\s\S]*?)\[\/RUN_COMMAND\]/gi;
var buildApkRegex = /\[BUILD_APK\s+([^\]]+)\]/gi;
var installApkRegex = /\[INSTALL_APK\s+([^\]]+)\]/gi;
var codeBlockFileRegex = /```(\w+)\s*\n([\s\S]*?)```/gi;
var deviceTapRegex = /\[DEVICE_TAP\s+(\d+)\s+(\d+)\]/gi;
var deviceLongPressRegex = /\[DEVICE_LONG_PRESS\s+(\d+)\s+(\d+)\]/gi;
var deviceSwipeRegex = /\[DEVICE_SWIPE\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\]/gi;
var deviceTypeRegex = /\[DEVICE_TYPE\s+([^\]]+)\]/gi;
var devicePressBackRegex = /\[DEVICE_PRESS_BACK\]/gi;
var devicePressHomeRegex = /\[DEVICE_PRESS_HOME\]/gi;
var devicePressRecentsRegex = /\[DEVICE_PRESS_RECENTS\]/gi;
var deviceScreenshotRegex = /\[DEVICE_SCREENSHOT\]/gi;
var deviceUiTreeRegex = /\[DEVICE_UI_TREE\]/gi;
var deviceClickTextRegex = /\[DEVICE_CLICK_TEXT\s+([^\]]+)\]/gi;
var deviceClickIdRegex = /\[DEVICE_CLICK_ID\s+([^\]]+)\]/gi;
var deviceLaunchRegex = /\[DEVICE_LAUNCH\s+([^\]]+)\]/gi;
var deviceCurrentAppRegex = /\[DEVICE_CURRENT_APP\]/gi;
var hermesInstallRegex = /\[HERMES_INSTALL\]/gi;
var hermesExecRegex = /\[HERMES_EXEC\s+([^\]]+)\]/gi;
var match;
while ((match = createActionRegex.exec(content)) !== null) {
@@ -1438,6 +1455,51 @@
while ((match = installApkRegex.exec(content)) !== null) {
actions.push({ type: 'install_apk', path: match[1].trim() });
}
while ((match = deviceTapRegex.exec(content)) !== null) {
actions.push({ type: 'device_tap', x: parseInt(match[1]), y: parseInt(match[2]) });
}
while ((match = deviceLongPressRegex.exec(content)) !== null) {
actions.push({ type: 'device_long_press', x: parseInt(match[1]), y: parseInt(match[2]) });
}
while ((match = deviceSwipeRegex.exec(content)) !== null) {
actions.push({ type: 'device_swipe', startX: parseInt(match[1]), startY: parseInt(match[2]), endX: parseInt(match[3]), endY: parseInt(match[4]) });
}
while ((match = deviceTypeRegex.exec(content)) !== null) {
actions.push({ type: 'device_type', text: match[1].trim() });
}
while ((match = devicePressBackRegex.exec(content)) !== null) {
actions.push({ type: 'device_press_back' });
}
while ((match = devicePressHomeRegex.exec(content)) !== null) {
actions.push({ type: 'device_press_home' });
}
while ((match = devicePressRecentsRegex.exec(content)) !== null) {
actions.push({ type: 'device_press_recents' });
}
while ((match = deviceScreenshotRegex.exec(content)) !== null) {
actions.push({ type: 'device_screenshot' });
}
while ((match = deviceUiTreeRegex.exec(content)) !== null) {
actions.push({ type: 'device_ui_tree' });
}
while ((match = deviceClickTextRegex.exec(content)) !== null) {
actions.push({ type: 'device_click_text', text: match[1].trim() });
}
while ((match = deviceClickIdRegex.exec(content)) !== null) {
actions.push({ type: 'device_click_id', viewId: match[1].trim() });
}
while ((match = deviceLaunchRegex.exec(content)) !== null) {
actions.push({ type: 'device_launch', pkg: match[1].trim() });
}
while ((match = deviceCurrentAppRegex.exec(content)) !== null) {
actions.push({ type: 'device_current_app' });
}
while ((match = hermesInstallRegex.exec(content)) !== null) {
actions.push({ type: 'hermes_install' });
}
while ((match = hermesExecRegex.exec(content)) !== null) {
actions.push({ type: 'hermes_exec', command: match[1].trim() });
}
while ((match = codeBlockFileRegex.exec(content)) !== null) {
var lang = match[1];
var code = match[2];
@@ -1457,6 +1519,8 @@
var hasCommands = actions.some(function(a) { return a.type === 'run_command'; });
var hasBuild = actions.some(function(a) { return a.type === 'build_apk'; });
var hasInstall = actions.some(function(a) { return a.type === 'install_apk'; });
var hasDevice = actions.some(function(a) { return a.type && a.type.indexOf('device_') === 0; });
var hasHermes = actions.some(function(a) { return a.type && a.type.indexOf('hermes_') === 0; });
var actionBar = document.createElement('div');
actionBar.className = 'msg-actions';
@@ -1606,7 +1670,9 @@
var hasAction = content.indexOf('[CREATE_FILE') >= 0 ||
content.indexOf('[RUN_COMMAND]') >= 0 ||
content.indexOf('[BUILD_APK') >= 0 ||
content.indexOf('[INSTALL_APK') >= 0;
content.indexOf('[INSTALL_APK') >= 0 ||
content.indexOf('[DEVICE_') >= 0 ||
content.indexOf('[HERMES_') >= 0;
var hasCodeBlock = content.indexOf('```') >= 0;
if (!hasCodeBlock && !hasAction && content.length < 300) return true;
if ((content.match(/```/g) || []).length % 2 !== 0) return false;
@@ -2129,6 +2195,24 @@
return false;
}
async function checkAutoGLMStatus() {
var AutoGLM = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AutoGLM;
var statusEl = $('#autoglm-status');
var btn = $('#autoglm-enable-btn');
if (!AutoGLM || !statusEl) return;
try {
var result = await AutoGLM.isEnabled();
if (result.enabled) {
statusEl.innerHTML = '<span style="color:var(--success)">Device control enabled</span>';
if (btn) { btn.textContent = 'Device Control Active'; btn.disabled = true; }
} else {
statusEl.innerHTML = '<span style="color:var(--warning)">Not enabled — tap to open Settings</span>';
}
} catch(e) {
statusEl.innerHTML = '<span style="color:var(--text-secondary)">Checking status...</span>';
}
}
async function checkDevEnvironment() {
if (state.currentMode !== 'coding' && state.currentMode !== 'agentic') return;
@@ -2225,8 +2309,10 @@
var hasBuild = actions.some(function(a) { return a.type === 'build_apk'; });
var hasInstall = actions.some(function(a) { return a.type === 'install_apk'; });
var hasCommands = actions.some(function(a) { return a.type === 'run_command'; });
var hasDevice = actions.some(function(a) { return a.type && a.type.indexOf('device_') === 0; });
var hasHermes = actions.some(function(a) { return a.type && a.type.indexOf('hermes_') === 0; });
if (!hasFiles && !hasBuild && !hasInstall && !hasCommands) return;
if (!hasFiles && !hasBuild && !hasInstall && !hasCommands && !hasDevice && !hasHermes) return;
_agenticRetryCount = 0;
var resultLog = [];
@@ -2257,6 +2343,36 @@
}
}
if (hasDevice) {
for (var d = 0; d < actions.length; d++) {
var act = actions[d];
if (act.type.indexOf('device_') !== 0) continue;
try {
var devResult = await executeDeviceAction(act);
resultLog.push(devResult);
termPrint(devResult, '');
} catch(e) {
resultLog.push('DEVICE_ERROR: ' + e.message);
termPrint('[!] Device: ' + e.message, 'err');
}
}
}
if (hasHermes) {
for (var h = 0; h < actions.length; h++) {
var hAct = actions[h];
if (hAct.type.indexOf('hermes_') !== 0) continue;
try {
var hermesResult = await executeHermesAction(hAct);
resultLog.push(hermesResult);
termPrint(hermesResult, '');
} catch(e) {
resultLog.push('HERMES_ERROR: ' + e.message);
termPrint('[!] Hermes: ' + e.message, 'err');
}
}
}
if (hasBuild) {
showStatusToast('Building APK...', 'info');
var toolsReady = await ensureBuildTools();
@@ -2285,6 +2401,87 @@
}
}
async function executeDeviceAction(action) {
var AutoGLM = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AutoGLM;
if (!AutoGLM) throw new Error('AutoGLM plugin not available');
switch (action.type) {
case 'device_tap':
await AutoGLM.tap({ x: action.x, y: action.y });
return '[DEVICE] Tap(' + action.x + ', ' + action.y + ')';
case 'device_long_press':
await AutoGLM.longPress({ x: action.x, y: action.y });
return '[DEVICE] LongPress(' + action.x + ', ' + action.y + ')';
case 'device_swipe':
await AutoGLM.swipe({ startX: action.startX, startY: action.startY, endX: action.endX, endY: action.endY });
return '[DEVICE] Swipe(' + action.startX + ',' + action.startY + ' -> ' + action.endX + ',' + action.endY + ')';
case 'device_type':
await AutoGLM.typeText({ text: action.text });
return '[DEVICE] Type: "' + action.text + '"';
case 'device_press_back':
await AutoGLM.pressBack();
return '[DEVICE] Press Back';
case 'device_press_home':
await AutoGLM.pressHome();
return '[DEVICE] Press Home';
case 'device_press_recents':
await AutoGLM.pressRecents();
return '[DEVICE] Press Recents';
case 'device_screenshot':
var ssResult = await AutoGLM.takeScreenshot({});
return '[DEVICE] Screenshot: ' + (ssResult.path || 'saved');
case 'device_ui_tree':
var treeResult = await AutoGLM.getUITree({});
var tree = treeResult.tree || '{}';
return '[DEVICE] UI Tree: ' + tree.substring(0, 2000);
case 'device_click_text':
var clickResult = await AutoGLM.clickByText({ text: action.text });
return '[DEVICE] Click "' + action.text + '": ' + (clickResult.ok ? 'OK' : 'not found');
case 'device_click_id':
var idResult = await AutoGLM.clickNode({ viewId: action.viewId });
return '[DEVICE] Click ID "' + action.viewId + '": ' + (idResult.ok ? 'OK' : 'not found');
case 'device_launch':
await AutoGLM.launchApp({ package: action.pkg });
return '[DEVICE] Launch: ' + action.pkg;
case 'device_current_app':
var appResult = await AutoGLM.getCurrentApp({});
return '[DEVICE] Current app: ' + (appResult.package || 'unknown');
default:
return '[DEVICE] Unknown action: ' + action.type;
}
}
async function executeHermesAction(action) {
var Bootstrap = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Bootstrap;
if (!Bootstrap) throw new Error('Bootstrap plugin not available');
switch (action.type) {
case 'hermes_install':
showStatusToast('Installing Hermes agent...', 'info');
var installResult = await Bootstrap.installHermes({});
termState.hermesPath = installResult.path;
termState.hermesVenv = installResult.venv;
return '[HERMES] Installed: ' + installResult.path;
case 'hermes_exec':
if (!termState.hermesVenv) {
try {
var status = await Bootstrap.installHermes({});
termState.hermesPath = status.path;
termState.hermesVenv = status.venv;
} catch(e) {
throw new Error('Hermes not installed: ' + e.message);
}
}
showStatusToast('Hermes: ' + action.command.substring(0, 30), 'info');
var execResult = await Bootstrap.hermesExec({ command: action.command, venv: termState.hermesVenv });
var hermesOutput = execResult.output || '';
if (hermesOutput.length > 3000) hermesOutput = hermesOutput.substring(0, 1500) + '\n...truncated...\n' + hermesOutput.substring(hermesOutput.length - 1000);
return '[HERMES] ' + action.command + '\n' + hermesOutput;
default:
return '[HERMES] Unknown action: ' + action.type;
}
}
async function autoDeployFile(action) {
var path = action.path;
if (!path.startsWith('/')) {
@@ -2644,6 +2841,39 @@
});
}
var autoglmBtn = $('#autoglm-enable-btn');
if (autoglmBtn) {
checkAutoGLMStatus();
autoglmBtn.addEventListener('click', function() {
var AutoGLM = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AutoGLM;
if (AutoGLM) {
AutoGLM.openSettings();
}
});
}
var hermesBtn = $('#hermes-install-btn');
if (hermesBtn) {
hermesBtn.addEventListener('click', async function() {
var Bootstrap = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Bootstrap;
if (!Bootstrap) return;
hermesBtn.disabled = true;
hermesBtn.textContent = 'Installing Hermes...';
try {
var result = await Bootstrap.installHermes({});
hermesBtn.textContent = 'Hermes Installed!';
hermesBtn.disabled = true;
termState.hermesPath = result.path;
termState.hermesVenv = result.venv;
termPrint('[OK] Hermes installed: ' + result.path, 'success');
} catch(e) {
hermesBtn.textContent = 'Install Failed - Retry';
hermesBtn.disabled = false;
termPrint('[!] Hermes install failed: ' + e.message, 'err');
}
});
}
$$('.term-quick-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var cmd = this.dataset.cmd;