create-openclaw-bot 5.16.9 → 5.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,225 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Double-click launchers for a NATIVE bot on Windows.
5
+ *
6
+ * Why these exist instead of a Scheduled Task: `openclaw daemon install` creates a task with
7
+ * `LogonType: Interactive`, which means the gateway is tied to whoever's login session started
8
+ * it. Two consequences measured on win_kha (10/09/2026):
9
+ * • anything started over SSH dies the moment the session closes (LastTaskResult 0xC000013A);
10
+ * • a task set to run at boot is refused outright, because "at startup" and "only while
11
+ * someone is logged in" contradict each other (0x800710E0).
12
+ * Making it run without a login needs the account password stored in the task — Task Scheduler
13
+ * rejected that too ("The user account is unknown, the password is incorrect"). So the owner
14
+ * drives it by hand, exactly like the .command files on macOS.
15
+ *
16
+ * Every trap below cost a round trip with a customer waiting, so they are encoded here:
17
+ *
18
+ * 1. `.cmd` MUST be CRLF. LF-only files break on Windows.
19
+ * 2. Never name a launcher after the command it calls. `9router.cmd` in the project dir plus
20
+ * `cd /d <project>` makes `call 9router` re-enter the same file — BATCH RECURSION, and the
21
+ * process dies before doing anything.
22
+ * 3. Never append to a fixed log file. 9router holds its log open for its whole lifetime, so
23
+ * the SECOND run cannot open it and exits with "The process cannot access the file because
24
+ * it is being used by another process" — invisible on the first run, which is why it is easy
25
+ * to ship broken.
26
+ * 4. 9router shows a `Choose Interface` menu when its stdout looks like a real console. Started
27
+ * hidden, nobody can answer it, so it hangs and never opens its port. `> NUL` is what tells
28
+ * it to run unattended. Symptom without it: works when run by hand, silently dead in the
29
+ * background — same command.
30
+ * 5. `Start-Process -WindowStyle Hidden` does NOT hide a console a .cmd opens for itself. Only
31
+ * WScript.Shell.Run(..., 0, False) truly hides it. Leaving a visible window is not cosmetic:
32
+ * it IS the bot, and closing it kills the bot.
33
+ * 6. Anchor the working directory. Agents may carry relative workspace paths, which resolve
34
+ * against the launcher's cwd — move the files into a folder and those bots die with
35
+ * WORKSPACE_VANISHED while the others keep working.
36
+ */
37
+
38
+ const CRLF = (s) => s.replace(/\r?\n/g, '\r\n');
39
+
40
+ /** Hidden-launch helper. WScript is the only reliable way to start a .cmd with no window. */
41
+ function buildRunHiddenVbs() {
42
+ return CRLF(
43
+ 'Set sh = CreateObject("WScript.Shell")\n' +
44
+ 'sh.Run "cmd /c """ & WScript.Arguments(0) & """", 0, False\n',
45
+ );
46
+ }
47
+
48
+ /** Starts 9router. Named start-9router.cmd on purpose — see trap 2. */
49
+ function build9RouterCmd({ projectDir, routerPort }) {
50
+ return CRLF(
51
+ '@echo off\n' +
52
+ 'rem Ten tep KHONG duoc la "9router.cmd": trung ten thi lenh ben duoi goi lai chinh no.\n' +
53
+ 'rem "> NUL" la bat buoc: neu dau ra la man hinh that, 9router bay menu "Choose Interface"\n' +
54
+ 'rem roi cho nguoi chon va khong bao gio mo cong.\n' +
55
+ `cd /d ${projectDir}\n` +
56
+ `set "DATA_DIR=${projectDir}\\.9router"\n` +
57
+ `call 9router -n -l -H 127.0.0.1 -p ${routerPort} --skip-update > NUL 2>&1\n`,
58
+ );
59
+ }
60
+
61
+ /**
62
+ * The node host that gives the bot screen control.
63
+ *
64
+ * Two things here are not obvious and both cost a day:
65
+ * - It runs against `<project>\.openclaw-node`, NOT the bot's state dir. Pointed at the bot's, it
66
+ * loads the bot's plugins too, and zalo-mod's dashboard port is already held by the gateway, so
67
+ * the node dies on `listen EADDRINUSE 127.0.0.1:18790` before publishing computer.act.
68
+ * - It is launched as a .cmd through wscript like everything else on Windows. Spawning the
69
+ * `openclaw.cmd` shim from Node with detached+shell fails outright with `spawn EINVAL`.
70
+ */
71
+ function buildNodeHostCmd({ projectDir, gatewayPort, gatewayToken }) {
72
+ return CRLF(
73
+ '@echo off\n' +
74
+ 'rem Node host cho tinh nang dieu khien may (tool computer/screen cua OpenClaw).\n' +
75
+ `cd /d ${projectDir}\n` +
76
+ 'set "HOME=%USERPROFILE%"\n' +
77
+ `set "OPENCLAW_HOME=${projectDir}\\.openclaw-node"\n` +
78
+ `set "OPENCLAW_STATE_DIR=${projectDir}\\.openclaw-node"\n` +
79
+ (gatewayToken ? `set "OPENCLAW_GATEWAY_TOKEN=${gatewayToken}"\n` : '') +
80
+ '"%ProgramFiles%\\nodejs\\node.exe" ' +
81
+ '"%APPDATA%\\npm\\node_modules\\openclaw\\dist\\index.js" ' +
82
+ `node run --host 127.0.0.1 --port ${gatewayPort} --no-tls\n`,
83
+ );
84
+ }
85
+
86
+ /** Starts the gateway. Calls node directly - see the --task-supervisor note below. */
87
+ function buildGatewayCmd({ projectDir, gatewayPort }) {
88
+ return CRLF(
89
+ '@echo off\n' +
90
+ 'rem Goi thang node, KHONG qua gateway.cmd cua openclaw: tep do chay kem --task-supervisor,\n' +
91
+ 'rem no di tim Scheduled Task de ban giao; khong co task thi no de them tien trinh + cua so.\n' +
92
+ 'rem Khong redirect ra tep co dinh (xem trap 3) — openclaw tu ghi nhat ky trong %TEMP%\\openclaw.\n' +
93
+ `cd /d ${projectDir}\n` +
94
+ 'set "HOME=%USERPROFILE%"\n' +
95
+ `set "OPENCLAW_GATEWAY_PORT=${gatewayPort}"\n` +
96
+ `set "OPENCLAW_PORT=${gatewayPort}"\n` +
97
+ '"%ProgramFiles%\\nodejs\\node.exe" --max-old-space-size=8192 ' +
98
+ `"%APPDATA%\\npm\\node_modules\\openclaw\\dist\\index.js" gateway --port ${gatewayPort}\n`,
99
+ );
100
+ }
101
+
102
+ function buildSetupUiCmd({ projectDir, setupPort }) {
103
+ return CRLF(
104
+ '@echo off\n' +
105
+ 'rem Ten tep nhat ky kem gio, de lan chay sau khong dung vao tep dang bi giu (trap 3).\n' +
106
+ `cd /d ${projectDir}\n` +
107
+ 'for /f "tokens=1-4 delims=/: " %%a in ("%TIME%") do set "T=%%a%%b%%c"\n' +
108
+ `call npx create-openclaw-bot --host=127.0.0.1 --port=${setupPort} --no-open --project-dir=${projectDir}` +
109
+ ` > "${projectDir}\\setup-ui-%T%.log" 2>&1\n`,
110
+ );
111
+ }
112
+
113
+ function buildStartBotCmd({ projectDir, gatewayPort, routerPort }) {
114
+ const dashPort = gatewayPort + 1;
115
+ return CRLF(
116
+ '@echo off\n' +
117
+ 'title Khoi dong bot OpenClaw\n' +
118
+ 'color 0A\n' +
119
+ `cd /d ${projectDir}\n` +
120
+ 'echo.\n' +
121
+ 'echo ============================================\n' +
122
+ `echo KHOI DONG BOT -- ${projectDir}\n` +
123
+ 'echo ============================================\n' +
124
+ 'echo.\n' +
125
+ 'echo Dang bat 9Router...\n' +
126
+ `powershell -NoProfile -Command "if (-not (Get-NetTCPConnection -LocalPort ${routerPort} -State Listen -EA SilentlyContinue)) { Start-Process wscript -ArgumentList '${projectDir}\\run-hidden.vbs','${projectDir}\\start-9router.cmd' -WindowStyle Hidden }" >nul 2>&1\n` +
127
+ 'echo Cho 9Router san sang...\n' +
128
+ `powershell -NoProfile -Command "for($i=0;$i -lt 20;$i++){ try{ Invoke-WebRequest 'http://127.0.0.1:${routerPort}/' -UseBasicParsing -TimeoutSec 3 | Out-Null; break } catch { Start-Sleep -Seconds 2 } }"\n` +
129
+ 'echo Dang bat bot (gateway)...\n' +
130
+ `powershell -NoProfile -Command "if (-not (Get-NetTCPConnection -LocalPort ${gatewayPort} -State Listen -EA SilentlyContinue)) { Start-Process wscript -ArgumentList '${projectDir}\\run-hidden.vbs','${projectDir}\\gateway-start.cmd' -WindowStyle Hidden }" >nul 2>&1\n` +
131
+ 'echo Cho bot san sang (co the mat 30-60 giay)...\n' +
132
+ 'echo.\n' +
133
+ `powershell -NoProfile -Command "$ok=$false; for($i=0;$i -lt 40;$i++){ try{ Invoke-WebRequest 'http://127.0.0.1:${gatewayPort}/health' -UseBasicParsing -TimeoutSec 3 | Out-Null; $ok=$true; break } catch { Start-Sleep -Seconds 3 } }; if($ok){ Write-Host ' [OK] Bot da chay.' -ForegroundColor Green } else { Write-Host ' [LOI] Bot chua len.' -ForegroundColor Red }"\n` +
134
+ 'echo.\n' +
135
+ 'echo --- Trang thai ---\n' +
136
+ `powershell -NoProfile -Command "foreach($x in @(@('Bot (gateway)','http://127.0.0.1:${gatewayPort}/health'),@('9Router','http://127.0.0.1:${routerPort}/'),@('Bang dieu khien Zalo','http://127.0.0.1:${dashPort}/dashboard'))){ try{ (Invoke-WebRequest $x[1] -UseBasicParsing -TimeoutSec 6) | Out-Null; Write-Host (' {0,-22} DANG CHAY' -f $x[0]) -ForegroundColor Green } catch { Write-Host (' {0,-22} TAT' -f $x[0]) -ForegroundColor Red } }"\n` +
137
+ 'echo.\n' +
138
+ 'echo Bot chay AN, khong co cua so nao de lo tay dong.\n' +
139
+ 'echo Bot chi tat khi ban DANG XUAT / TAT may, hoac bam "3 - DUNG BOT".\n' +
140
+ 'echo.\n' +
141
+ 'timeout /t 12 >nul\n',
142
+ );
143
+ }
144
+
145
+ function buildOpenUiCmd({ projectDir, setupPort }) {
146
+ return CRLF(
147
+ '@echo off\n' +
148
+ 'title Mo giao dien bot\n' +
149
+ 'color 0B\n' +
150
+ `cd /d ${projectDir}\n` +
151
+ 'echo.\n' +
152
+ 'echo ============================================\n' +
153
+ 'echo MO GIAO DIEN QUAN TRI BOT\n' +
154
+ 'echo ============================================\n' +
155
+ 'echo.\n' +
156
+ `powershell -NoProfile -Command "if (-not (Get-NetTCPConnection -LocalPort ${setupPort} -State Listen -EA SilentlyContinue)) { Write-Host ' Dang bat giao dien...'; Start-Process wscript -ArgumentList '${projectDir}\\run-hidden.vbs','${projectDir}\\setup-ui.cmd' -WindowStyle Hidden } else { Write-Host ' Giao dien dang chay san.' }"\n` +
157
+ 'echo Cho giao dien san sang...\n' +
158
+ `powershell -NoProfile -Command "for($i=0;$i -lt 30;$i++){ try{ Invoke-WebRequest 'http://127.0.0.1:${setupPort}/' -UseBasicParsing -TimeoutSec 3 | Out-Null; break } catch { Start-Sleep -Seconds 2 } }"\n` +
159
+ `start "" http://127.0.0.1:${setupPort}\n` +
160
+ 'echo.\n' +
161
+ 'echo Da mo trinh duyet. Dong cua so nay duoc roi.\n' +
162
+ 'timeout /t 8 >nul\n',
163
+ );
164
+ }
165
+
166
+ function buildStopBotCmd({ gatewayPort, routerPort }) {
167
+ return CRLF(
168
+ '@echo off\n' +
169
+ 'title Dung bot OpenClaw\n' +
170
+ 'color 0C\n' +
171
+ 'echo.\n' +
172
+ 'echo Dang dung bot va 9Router...\n' +
173
+ 'powershell -NoProfile -Command "Get-CimInstance Win32_Process -Filter \\"Name=\'node.exe\'\\" | Where-Object { $_.CommandLine -like \'*openclaw*gateway*\' -or $_.CommandLine -like \'*9router*\' } | ForEach-Object { Write-Host (\' dung PID \' + $_.ProcessId); Stop-Process -Id $_.ProcessId -Force -EA SilentlyContinue }"\n' +
174
+ 'echo.\n' +
175
+ `powershell -NoProfile -Command "Start-Sleep -Seconds 3; foreach($x in @(@('Bot',${gatewayPort}),@('9Router',${routerPort}))){ $c=Get-NetTCPConnection -LocalPort $x[1] -State Listen -EA SilentlyContinue; if($c){ Write-Host (' {0,-10} VAN CHAY' -f $x[0]) -ForegroundColor Yellow } else { Write-Host (' {0,-10} da tat' -f $x[0]) -ForegroundColor Green } }"\n` +
176
+ 'echo.\n' +
177
+ 'pause\n',
178
+ );
179
+ }
180
+
181
+ function buildReadme({ projectDir, gatewayPort, routerPort, setupPort }) {
182
+ return CRLF(
183
+ 'BOT OPENCLAW TREN MAY NAY - HUONG DAN NHANH\n' +
184
+ '===========================================\n\n' +
185
+ 'Ba tep .cmd, bam dup la chay. Khong can go lenh.\n\n' +
186
+ ' 1 - KHOI DONG BOT.cmd Bat bot len. Bam sau moi lan khoi dong may.\n' +
187
+ ' 2 - MO GIAO DIEN.cmd Mo trang quan tri bot trong trinh duyet.\n' +
188
+ ' 3 - DUNG BOT.cmd Tat bot (chi dung khi can).\n\n' +
189
+ 'QUAN TRONG\n' +
190
+ '----------\n' +
191
+ '- Bot chay AN hoan toan: khong co cua so den nao nam tren man hinh.\n' +
192
+ '- Bot KHONG tu chay khi bat may. Sau moi lan khoi dong lai may,\n' +
193
+ ' bam "1 - KHOI DONG BOT" mot lan la xong.\n' +
194
+ '- Bot se tat khi ban DANG XUAT hoac TAT may.\n' +
195
+ '- Dat cac tep nay o dau cung duoc, chung deu neo ve ' + projectDir + '.\n\n' +
196
+ 'Cong dang dung:\n' +
197
+ ` ${gatewayPort} bot (gateway)\n` +
198
+ ` ${gatewayPort + 1} bang dieu khien Zalo\n` +
199
+ ` ${routerPort} 9Router\n` +
200
+ ` ${setupPort} giao dien quan tri\n`,
201
+ );
202
+ }
203
+
204
+ /** All launcher files for a native Windows project, as { relativeName: content }. */
205
+ function buildWindowsLaunchers({ projectDir, gatewayPort = 18789, routerPort = 20128, setupPort = 51789, gatewayToken = '' }) {
206
+ const opts = { projectDir, gatewayPort, routerPort, setupPort, gatewayToken };
207
+ return {
208
+ 'run-hidden.vbs': buildRunHiddenVbs(),
209
+ 'start-9router.cmd': build9RouterCmd(opts),
210
+ 'gateway-start.cmd': buildGatewayCmd(opts),
211
+ 'node-host.cmd': buildNodeHostCmd(opts),
212
+ 'setup-ui.cmd': buildSetupUiCmd(opts),
213
+ '0 - DOC TRUOC.txt': buildReadme(opts),
214
+ '1 - KHOI DONG BOT.cmd': buildStartBotCmd(opts),
215
+ '2 - MO GIAO DIEN.cmd': buildOpenUiCmd(opts),
216
+ '3 - DUNG BOT.cmd': buildStopBotCmd(opts),
217
+ };
218
+ }
219
+
220
+ /** Which of the generated files belong on the Desktop rather than in the project folder. */
221
+ const WINDOWS_DESKTOP_LAUNCHERS = ['0 - DOC TRUOC.txt', '1 - KHOI DONG BOT.cmd', '2 - MO GIAO DIEN.cmd', '3 - DUNG BOT.cmd'];
222
+
223
+ const api = { buildWindowsLaunchers, WINDOWS_DESKTOP_LAUNCHERS };
224
+ if (typeof globalThis !== 'undefined') globalThis.__openclawWindowsLaunchers = api;
225
+ if (typeof exports !== 'undefined') Object.assign(exports, api);
package/dist/web/app.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const $ = (sel) => document.querySelector(sel);
2
- const state = { tab: 'dashboard', system: null, install: null, files: [], catalog: { skills: [], plugins: [] }, logs: [], zaloLoginOpen: false, zaloLoginLines: [], zaloQrDataUrl: '', lang: localStorage.getItem('openclaw-lang') || 'vi', theme: localStorage.getItem('openclaw-theme') || 'dark', tz: localStorage.getItem('openclaw-tz') || 'Asia/Ho_Chi_Minh', navCollapsed: localStorage.getItem('openclaw-nav')==='1', os: null, mode: null, donateOpen: false, botModalOpen: false, botEditId: '', installModalOpen: false, fbPluginModalOpen: false, installTab: 'docker', installDraft: null, pathModal: null, confirmModal: null, botChannel: 'telegram', botPane: 'list', activeBotId: '', selectedFile: '', botMessage: '', projectConnectMessage: '', pendingProjectDir: '', selectedProjectDir: '', featureFlags: {}, featureInstalled: {}, featureLoading: {}, featureLocked: {}, zaloBackend: '', zaloHealth: null, openDirs: {} };
2
+ const state = { tab: 'dashboard', system: null, install: null, files: [], catalog: { skills: [], plugins: [] }, logs: [], zaloLoginOpen: false, zaloLoginLines: [], zaloQrDataUrl: '', lang: localStorage.getItem('openclaw-lang') || 'vi', theme: localStorage.getItem('openclaw-theme') || 'dark', tz: localStorage.getItem('openclaw-tz') || 'Asia/Ho_Chi_Minh', navCollapsed: localStorage.getItem('openclaw-nav')==='1', os: null, mode: null, donateOpen: false, botModalOpen: false, botEditId: '', installModalOpen: false, fbPluginModalOpen: false, installTab: 'native', installDraft: null, pathModal: null, confirmModal: null, botChannel: 'telegram', botPane: 'list', activeBotId: '', selectedFile: '', botMessage: '', projectConnectMessage: '', pendingProjectDir: '', selectedProjectDir: '', featureFlags: {}, featureInstalled: {}, featureLoading: {}, featureLocked: {}, zaloBackend: '', zaloHealth: null, openDirs: {} };
3
3
  const SVG_CDN = 'https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons';
4
4
  const OS_OPTIONS = [
5
5
  { id: 'win', title: 'Windows', subtitle: 'Auto-detected desktop', icon: `${SVG_CDN}/windows/default.svg`, badge: 'Desktop' },
@@ -212,19 +212,24 @@ function installModal() {
212
212
  const sys = state.system || {};
213
213
  const draft = refreshInstallDraft();
214
214
  const os = draft.os || state.os || sys?.os || 'win';
215
- const mode = draft.mode || state.installTab || state.mode || sys?.recommendedMode || 'docker';
215
+ const mode = draft.mode || state.installTab || 'native';
216
216
  const pathExample = os === 'win' ? 'C:\\openclaw-setup' : os === 'macos' ? '/Users/you/openclaw-setup' : '/home/you/openclaw-setup';
217
217
  const osChoices = OS_OPTIONS.map(o => [o.id, t(o.title, o.title), trChoice(o).subtitle]);
218
+ // Docker is closed for NEW projects: openclaw >=2026.9 writes config through fs-safe, which
219
+ // fstat()s the file after an atomic rename \u2014 a check that cannot pass through a Docker Desktop
220
+ // bind mount on Windows, so the first image rebuild after that takes the gateway down for good
221
+ // (measured on win_kha, 09/09/2026). Existing docker projects keep running and move to native on
222
+ // their next update; the tile stays visible but disabled so the reason is on screen.
218
223
  const modeChoices = [
219
- ['docker', 'Docker', t('\u0043ontainer c\u00f4 l\u1eadp, an to\u00e0n nh\u1ea5t', 'Isolated containers, safest default')],
220
- ['native', 'Native', t('Ch\u1ea1y th\u1eb3ng tr\u00ean m\u00e1y n\u00e0y \u2014 kh\u00f4ng c\u1ea7n Docker, \u0111i\u1ec1u khi\u1ec3n \u0111\u01b0\u1ee3c app', 'Runs on this machine \u2014 no Docker, can drive desktop apps')],
224
+ ['native', 'Native', t('Ch\u1ea1y th\u1eb3ng tr\u00ean m\u00e1y n\u00e0y \u2014 kh\u00f4ng c\u1ea7n Docker, \u0111i\u1ec1u khi\u1ec3n \u0111\u01b0\u1ee3c app', 'Runs on this machine \u2014 no Docker, can drive desktop apps'), false],
225
+ ['docker', 'Docker', t('\u0110\u00e3 ng\u1eebng \u2014 OpenClaw 2026.9 kh\u00f4ng ghi \u0111\u01b0\u1ee3c c\u1ea5u h\u00ecnh qua Docker tr\u00ean Windows', 'Retired \u2014 OpenClaw 2026.9 cannot write config through Docker on Windows'), true],
221
226
  ];
222
227
  return `<div class="modal-backdrop install-backdrop" data-install-modal="close">
223
228
  <section class="donate-modal install-modal" role="dialog" aria-modal="true" aria-label="${t('T\u1ea1o Project','Create Project')}" onclick="event.stopPropagation()">
224
229
  <button class="modal-x" data-install-modal="close" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
225
230
  <div class="donate-head"><span aria-hidden="true">+</span><div><p>${t('T\u1ea1o Project','Create Project')}</p><h2>${t('T\u1ea1o Project','Create Project')}</h2><small>${t('Ch\u1ecdn s\u1eb5n ch\u1ebf \u0111\u1ed9 \u1edf tab tr\u00ean. B\u00ean d\u01b0\u1edbi ch\u1ec9 c\u1ea7n ch\u1ecdn OS v\u00e0 nh\u1eadp ho\u1eb7c ch\u1ecdn \u0111\u01b0\u1eddng d\u1eabn project.','Mode stays in the tabs above. Below, choose OS and enter or pick the project path.')}</small></div></div>
226
231
  <form id="install-form" class="install-form">
227
- <div class="install-tabs">${modeChoices.map(([id,label,desc]) => `<button type="button" class="install-tab ${mode===id?'is-active':''}" data-install-set="mode" data-value="${id}"><strong>${escapeHtml(label)}</strong><small>${escapeHtml(desc)}</small></button>`).join('')}</div>
232
+ <div class="install-tabs">${modeChoices.map(([id,label,desc,locked]) => `<button type="button" class="install-tab ${mode===id?'is-active':''}${locked?' is-locked':''}" data-install-set="mode" data-value="${id}"${locked?' disabled aria-disabled="true"':''}><strong>${escapeHtml(label)}${locked?' 🔒':''}</strong><small>${escapeHtml(desc)}</small></button>`).join('')}</div>
228
233
  <div class="install-grid install-grid--compact">
229
234
  <div class="field wide"><span>${t('H\u1ec7 \u0111i\u1ec1u h\u00e0nh','Operating system')}</span>${pillGroup('os', os, osChoices)}<small>${t('\u0110\u00e3 ch\u1ecdn s\u1eb5n theo m\u00e1y \u0111ang ch\u1ea1y','Preselected from the current machine')}</small></div>
230
235
  <label class="field wide"><span>${t('Đường dẫn project','Project path')}</span><input name="projectDir" placeholder="${escapeHtml(pathExample)}" value="${escapeHtml(draft.projectDir || pathExample)}" /><small>${t('Ví dụ: C:\\openclaw-setup hoặc /home/you/openclaw-setup. Bạn có thể tự sửa tên folder bot thành tên bất kỳ.','Example: C:\\openclaw-setup or /home/you/openclaw-setup. You can rename folder bot to any name.')}</small></label>
@@ -319,47 +324,53 @@ function openPathModal({ title, message, value = '', placeholder = '', field2 =
319
324
  * one thing only the operator can do — grant the OS screen permissions from the system settings.
320
325
  */
321
326
  function openComputerUseModal(r = {}) {
322
- const codex = r.codex || {};
323
- const appOk = codex.app && codex.app.present;
324
- const cli = (r.commands || []).includes('codex');
327
+ const cu = r.computerUse || {};
328
+ const isMac = /mac/i.test(state.system?.os || state.os || '');
325
329
  const line = (icon, text) => `<li><span aria-hidden="true">${icon}</span><div>${text}</div></li>`;
330
+
331
+ // OpenClaw's own `computer` tool is the real thing here: screenshot, move, click, type, drag.
332
+ // Codex is only a fallback for machines that have the ChatGPT app, so it must not lead - the
333
+ // old copy said "mouse/keyboard control is macOS-only", which stopped being true once the
334
+ // cua-computer plugin shipped for Windows.
326
335
  const statusItems = [
327
- cli
328
- ? line('✅', t('Bot giao được việc cho <code>codex</code> Codex tự nhìn màn hình, click, gõ phím rồi trả kết quả về.', 'The bot can hand jobs to <code>codex</code> Codex looks at the screen, clicks and types, then reports back.'))
329
- : line('⚠️', t('Không tìm thấy CLI <code>codex</code> cài app ChatGPT/Codex rồi bật lại quyền này.', 'No <code>codex</code> CLI found install the ChatGPT/Codex app, then re-enable this.')),
330
- codex.pluginInstalled
331
- ? line('✅', t(`Computer Use trong app Codex đã ${codex.installedNow ? 'được cài' : 'sẵn sàng'}${codex.mcpRepaired ? ' (đã sửa khai báo MCP cũ)' : ''}.`, `Computer Use in the Codex app is ${codex.installedNow ? 'installed' : 'ready'}${codex.mcpRepaired ? ' (stale MCP entry repaired)' : ''}.`))
332
- : line('⚠️', t(`Chưa bật được Computer Use trong app Codex: ${escapeHtml(codex.error || 'không rõ lý do')}`, `Could not enable Computer Use in the Codex app: ${escapeHtml(codex.error || 'unknown reason')}`)),
333
- appOk
334
- ? line('✅', t('Đã thấy ứng dụng ChatGPT/Codex — <b>nhớ để app đang chạy</b>.', 'Found the ChatGPT/Codex desktop app — <b>keep it running</b>.'))
335
- : line('⚠️', t('CHƯA thấy ứng dụng ChatGPT/Codex — cài rồi mở lên, không có nó thì không điều khiển GUI được.', 'No ChatGPT/Codex desktop app found — install and open it, GUI control needs it.')),
336
- (r.granted && r.granted.length)
337
- ? line('✅', t(`Đã cấp quyền chạy: ${escapeHtml(r.granted.join(', '))}.`, `Granted: ${escapeHtml(r.granted.join(', '))}.`))
336
+ cu.ok && cu.enabled
337
+ ? line('✅', t('Bot <b>nhìn điều khiển được máy này</b>: chụp màn hình, rê chuột, bấm chuột, gõ phím, kéo thả.', 'Your bot <b>can see and drive this machine</b>: screenshot, move, click, type, drag.'))
338
+ : line('⚠️', t(`Chưa bật được điều khiển máy: ${escapeHtml(cu.error || 'không do')}`, `Could not enable computer control: ${escapeHtml(cu.error || 'unknown reason')}`)),
339
+ // No app list any more: the bot opens things the way a person does - Start menu, type the
340
+ // name, Enter - so it is not limited to whatever we managed to enumerate.
341
+ cu.ok && cu.enabled
342
+ ? line('✅', t('Mở được <b>mọi ứng dụng đang cài</b> trên máy, kể cả app mới cài hôm qua.', 'Can open <b>any installed app</b>, including one installed yesterday.'))
338
343
  : '',
339
344
  ].filter(Boolean).join('');
345
+
346
+ // Windows needs no privacy prompts; macOS does, and only from System Settings.
347
+ const permBlock = isMac ? `
348
+ <h4>${t('Cấp quyền màn hình cho máy','Grant the OS screen permissions')}</h4>
349
+ <p>${t('macOS chỉ cấp <b>Screen Recording</b> và <b>Accessibility</b> từ System Settings. Bấm nút dưới rồi bật cho <code>node</code>.','macOS only grants <b>Screen Recording</b> and <b>Accessibility</b> from System Settings. Click below, then tick <code>node</code>.')}</p>
350
+ <p class="cu-perm-row">
351
+ <button class="secondary cu-btn" type="button" data-host-perm="screen">${t('Chụp/quay màn hình','Screen recording')}</button>
352
+ <button class="secondary cu-btn" type="button" data-host-perm="accessibility">${t('Accessibility (chuột/bàn phím)','Accessibility (mouse/keys)')}</button>
353
+ </p>` : '';
354
+
340
355
  state.confirmModal = {
341
356
  icon: '🖥️',
342
357
  eyebrow: t('Điều khiển máy','PC control'),
343
- title: t('Xong còn 1 bước bạn tự làm','Done one step left for you'),
358
+ title: cu.ok && cu.enabled ? t('Đã bật điều khiển máy','Computer control is on') : t('Bật chưa xong','Not fully enabled'),
344
359
  message: t('Model chính của bot vẫn là smart-route, không đổi.','Your bot keeps smart-route as its primary model.'),
345
360
  bodyHtml: `
346
361
  <ul class="cu-status">${statusItems}</ul>
347
- <h4>${t('Cấp quyền màn hình cho máy','Grant the OS screen permissions')}</h4>
348
- <p>${t('macOS chỉ cấp <b>Screen Recording</b> / <b>Accessibility</b> từ System Settings bấm nút dưới, rồi bật cho <code>node</code> app Codex.','macOS only grants <b>Screen Recording</b> / <b>Accessibility</b> from System Settings click below, then tick <code>node</code> and the Codex app.')}</p>
349
- <p class="cu-perm-row">
350
- <button class="secondary cu-btn" type="button" data-host-perm="screen">${t('Chụp/quay màn hình','Screen recording')}</button>
351
- <button class="secondary cu-btn" type="button" data-host-perm="accessibility">${t('Accessibility (chuột/bàn phím)','Accessibility (mouse/keys)')}</button>
352
- </p>
353
- <p class="cu-note">${t('Cách dùng: nhắn bot bình thường, ví dụ “mở TeamViewer và đọc giúp mật khẩu trên màn hình” — bot tự giao cho Codex rồi báo kết quả về.','How to use it: just ask your bot normally, e.g. “open TeamViewer and read the password on screen” — it hands the job to Codex and reports back.')}</p>
354
- <p class="cu-note">${t('Việc giao cho Codex chạy bằng gói ChatGPT đã đăng nhập (tốn quota gói đó); chat thường vẫn đi qua các model free của <code>smart-route</code>. Điều khiển chuột/bàn phím hiện chỉ có trên macOS.','Jobs handed to Codex run on the signed-in ChatGPT plan (they spend that quota); ordinary chat still uses the free <code>smart-route</code> models. Mouse/keyboard control is macOS-only for now.')}</p>
362
+ ${permBlock}
363
+ <p class="cu-note">${t('Cách dùng: nhắn bot bình thường, dụ “chụp màn hình cho anh xem” hoặc “mở TeamViewer rồi đọc mật khẩu trên màn hình”.','How to use it: just ask your bot normally, e.g. "take a screenshot" or "open TeamViewer and read the password on screen".')}</p>
364
+ <p class="cu-note">${t('Chỉ dùng được trên máy có màn hình. Máy chủ VPS không màn hình thì không áp dụng. Tắt nút này là thu hồi lại toàn bộ quyền trên.','Only works on a machine with a screen. A headless VPS cannot use this. Turning the switch off revokes all of it.')}</p>
365
+ <p class="cu-note">${t('<b>Sau khi khởi động lại máy hoặc đăng xuất Windows, phần điều khiển máy sẽ tắt.</b> Mở lại giao diện này và bật lại nút là xong. Bot vẫn chat bình thường, chỉ riêng phần nhìn và điều khiển màn hình là cần bật lại.','<b>Restarting or signing out of Windows turns computer control off.</b> Open this screen and switch it back on. Chat keeps working meanwhile; only seeing and driving the screen needs re-enabling.')}</p>
355
366
  `,
356
367
  okText: t('Đã hiểu','Got it'),
357
368
  okDanger: false,
358
369
  hideCancel: true,
359
370
  onConfirm: () => { state.confirmModal = null; render(); },
360
371
  };
361
- render();
362
372
  }
373
+
363
374
  async function pickFolderPathShared() {
364
375
  try {
365
376
  const picked = await api('/api/project/pick-folder', { method: 'POST', body: {} });
@@ -1251,8 +1262,8 @@ function wireTab() {
1251
1262
  const granted = r.screen && r.screen.supported ? r.screen.granted : null;
1252
1263
  showToast(t('Đã mở cài đặt quyền','Opened settings'),
1253
1264
  granted === true ? t('Quyền chụp/quay màn hình: đã có. Bật thêm Accessibility nếu cần gõ/click.','Screen recording: already granted. Also enable Accessibility for typing/clicking.')
1254
- : granted === false ? t('Chưa có quyền bật cho "node" (và app Codex) trong danh sách vừa mở, rồi restart bot.','Not granted yet tick "node" (and the Codex app) in the list that just opened, then restart the bot.')
1255
- : t('Bật quyền cho "node" và app Codex trong danh sách vừa mở.','Tick "node" and the Codex app in the list that just opened.'),
1265
+ : granted === false ? t('Chưa có quyền. Bật cho "node" trong danh sách vừa mở, rồi khởi động lại bot.','Not granted yet. Tick "node" in the list that just opened, then restart the bot.')
1266
+ : t('Bật quyền cho "node" trong danh sách vừa mở.','Tick "node" in the list that just opened.'),
1256
1267
  granted === false ? 'error' : 'success', 8000);
1257
1268
  } catch (err) { showToast(t('Thất bại','Failed'), err.message, 'error'); }
1258
1269
  }));
@@ -1405,23 +1416,18 @@ document.querySelectorAll('[data-project-pick-folder]').forEach(btn => btn.oncli
1405
1416
  try { cur = await api('/api/host/control' + projectQuery({})); }
1406
1417
  catch (err) { return showToast(t('Thất bại','Failed'), err.message, 'error'); }
1407
1418
  const on = !!cur.enabled;
1408
- const apps = cur.apps || [];
1409
- const grants = cur.grants || [];
1410
- const chips = (items) => items.map((i) => `<code>${escapeHtml(i)}</code>`).join(' ');
1411
- const scripts = grants.filter((g) => ['node', 'npx', 'codex'].includes(g));
1412
- // One row per capability instead of a wall of prose — the operator is granting real access to
1419
+ // One row per capability instead of a wall of prose - the operator is granting real access to
1413
1420
  // their machine and should be able to see, at a glance, exactly what each row means.
1414
- const rows = [
1415
- { icon: '🗂', title: t('Mở ứng dụng','Open apps'), desc: apps.length ? chips(apps) : t('chưa được app nào','no apps detected') },
1416
- ...(cur.native ? [
1417
- { icon: '📸', title: t('Chụp & quay màn hình','Screen capture & recording'), desc: t('bot nhìn được màn hình khi bạn nhờ','the bot can see your screen when you ask') },
1418
- { icon: '🖱', title: t('Điều khiển chuột/bàn phím','Mouse & keyboard control'), desc: t('bot giao việc cho Codex CLI (dùng gói ChatGPT đã đăng nhập); macOS','the bot hands the job to the Codex CLI on your ChatGPT plan; macOS only') },
1419
- { icon: '⚙️', title: t('Chạy script','Run scripts'), desc: `${chips(scripts.length ? scripts : ['node'])} <b>${t('chạy được tuỳ ý trên máy này','arbitrary code on this machine')}</b>` },
1420
- ] : []),
1421
- ];
1421
+ // There is no app allow-list to show: the bot drives the screen, so it reaches anything the
1422
+ // person sitting at this machine could reach. Say that plainly rather than implying limits.
1423
+ const rows = cur.native ? [
1424
+ { icon: '📸', title: t('Nhìn màn hình','See the screen'), desc: t('bot chụp được màn hình khi bạn nhờ','the bot can screenshot your screen when you ask') },
1425
+ { icon: '🖱', title: t('Điều khiển chuột/bàn phím','Mouse & keyboard control'), desc: t('bot chuột, bấm, phím kéo thả trên máy này','the bot moves the mouse, clicks, types and drags on this machine') },
1426
+ { icon: '🗂', title: t('Mở ứng dụng','Open apps'), desc: t('mọi app đang cài, bot thao tác như người ngồi trước máy','any installed app, because it operates the machine like a person would') },
1427
+ ] : [];
1422
1428
  const bodyHtml = on ? '' : `
1423
1429
  <ul class="cu-status grant-list">${rows.map((r) => `<li><span aria-hidden="true">${r.icon}</span><div><b>${r.title}</b><br>${r.desc}</div></li>`).join('')}</ul>
1424
- <p class="cu-note">${t('Danh sách sửa trong <code>.openclaw/host-control.json</code>. Chỉ dùng trên máy có màn hình (không áp dụng VPS headless).','Edit the list in <code>.openclaw/host-control.json</code>. Desktop only (not a headless VPS).')}</p>`;
1430
+ <p class="cu-note">${t('Chỉ dùng trên máy có màn hình (không áp dụng VPS headless).','Desktop only (not a headless VPS).')}</p>`;
1425
1431
  state.confirmModal = {
1426
1432
  icon: '🖥️',
1427
1433
  eyebrow: t('Điều khiển máy','PC control'),
@@ -1434,7 +1440,7 @@ document.querySelectorAll('[data-project-pick-folder]').forEach(btn => btn.oncli
1434
1440
  okDanger: on,
1435
1441
  onConfirm: async () => {
1436
1442
  state.confirmModal = null; render();
1437
- if (!on) showToast(t('Đang bật','Enabling'), t('Đang cấp quyền & cài Computer Use (có thể mất ~30s)…','Granting access & installing Computer Use (may take ~30s)…'), 'success');
1443
+ if (!on) showToast(t('Đang bật','Enabling'), t('Đang cấp quyền khởi động node điều khiển (có thể mất ~30s)…','Granting access and starting the control node (may take ~30s)…'), 'success');
1438
1444
  try {
1439
1445
  const r = await api('/api/host/control', { method: 'POST', body: { enabled: !on, projectDir: activeProjectDir() } });
1440
1446
  if (!on && r.started && r.started.ok === false && r.started.reason) {
@@ -1546,7 +1552,7 @@ document.querySelectorAll('[data-project-pick-folder]').forEach(btn => btn.oncli
1546
1552
  });
1547
1553
  $('#install')?.addEventListener('click', () => {
1548
1554
  state.installModalOpen = true;
1549
- state.installTab = document.querySelector('input[name=mode]:checked')?.value || state.mode || state.system?.recommendedMode || 'docker';
1555
+ state.installTab = document.querySelector('input[name=mode]:checked')?.value || 'native';
1550
1556
  const os = document.querySelector('input[name=os]:checked')?.value || state.os || state.system?.os || 'win';
1551
1557
  const defaultDir = os === 'win' ? 'E:\\bot' : os === 'macos' ? '/Users/you/openclaw-bot' : '/home/you/openclaw-bot';
1552
1558
 
@@ -2060,3 +2060,4 @@ body:has(.modal-backdrop) .bottom{display:none!important}
2060
2060
  .feature-switch span{width:42px;height:24px}
2061
2061
  .feature-switch span:after{top:2px;left:2px;width:18px;height:18px}
2062
2062
  .feature-switch input:checked + span:after{left:22px}
2063
+ .install-tab.is-locked{opacity:.45;cursor:not-allowed;filter:grayscale(1)}.install-tab.is-locked:hover{transform:none;border-color:var(--hair)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-openclaw-bot",
3
- "version": "5.16.9",
3
+ "version": "5.17.1",
4
4
  "description": "Interactive CLI installer for OpenClaw Bot",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {