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.
- package/README.md +4 -4
- package/README.vi.md +9 -9
- package/dist/server/local-server.js +925 -841
- package/dist/setup/shared/windows-launcher-gen.js +225 -0
- package/dist/web/app.js +53 -47
- package/dist/web/styles.css +1 -0
- package/package.json +1 -1
|
@@ -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: '
|
|
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 ||
|
|
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
|
-
['
|
|
220
|
-
['
|
|
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
|
|
323
|
-
const
|
|
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
|
-
|
|
328
|
-
? line('✅', t('Bot
|
|
329
|
-
: line('⚠️', t(
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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 và đ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 rõ lý 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('
|
|
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
|
-
|
|
348
|
-
<p>${t('
|
|
349
|
-
<p class="cu-
|
|
350
|
-
|
|
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, ví 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
|
|
1255
|
-
: t('Bật quyền cho "node"
|
|
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
|
-
|
|
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
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
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 rê chuột, bấm, gõ phím và 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, vì 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('
|
|
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
|
|
1443
|
+
if (!on) showToast(t('Đang bật','Enabling'), t('Đang cấp quyền và 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 ||
|
|
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
|
|
package/dist/web/styles.css
CHANGED
|
@@ -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)}
|