create-openclaw-bot 5.16.1 → 5.16.3
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
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*Run one command → open the dashboard → your bot is live. Windows · macOS · Linux · VPS — Docker-powered, auto-installed for you.*
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.16.
|
|
10
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.16.3-0EA5E9?style=for-the-badge" alt="Version 5.16.3" /></a>
|
|
11
11
|
<a href="https://github.com/tuanminhhole/openclaw-setup?tab=MIT-1-ov-file"><img src="https://img.shields.io/badge/LICENSE-MIT-success?style=for-the-badge" alt="MIT License" /></a>
|
|
12
12
|
<a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/v/create-openclaw-bot?style=for-the-badge&label=CLI&color=2563EB&logo=npm&logoColor=white" alt="NPM Version" /></a>
|
|
13
13
|
<a href="https://github.com/tuanminhhole/openclaw-setup/stargazers"><img src="https://img.shields.io/github/stars/tuanminhhole/openclaw-setup?style=for-the-badge&color=eab308&logo=github&logoColor=white" alt="GitHub Stars" /></a>
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
-
## 🆕 What's New in v5.16.
|
|
40
|
+
## 🆕 What's New in v5.16.3
|
|
41
41
|
|
|
42
42
|
- 🖥️ **"Control PC" — let the bot open Chrome & apps on your machine**: a new toggle on each bot card turns on the ability for the bot to open Chrome or an allow-listed app (TeamViewer, Zalo, …) on the computer running it. **Off by default**, token-gated, allow-list only, desktop-only.
|
|
43
43
|
- 🌐 **Browsing works out of the box on every OS**: the bot drives your real Chrome on a desktop (your logged-in profile, so sites don't flag it) and starts its own headless Chromium on a server — no more "no browser available" just because Chrome wasn't running.
|
package/README.vi.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*Chạy một lệnh → mở dashboard → bot lên sóng. Windows · macOS · Linux · VPS — chạy trên Docker, tự cài giúp bạn.*
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.16.
|
|
10
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.16.3-0EA5E9?style=for-the-badge" alt="Version 5.16.3" /></a>
|
|
11
11
|
<a href="https://github.com/tuanminhhole/openclaw-setup?tab=MIT-1-ov-file"><img src="https://img.shields.io/badge/LICENSE-MIT-success?style=for-the-badge" alt="MIT License" /></a>
|
|
12
12
|
<a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/v/create-openclaw-bot?style=for-the-badge&label=CLI&color=2563EB&logo=npm&logoColor=white" alt="NPM Version" /></a>
|
|
13
13
|
<a href="https://github.com/tuanminhhole/openclaw-setup/stargazers"><img src="https://img.shields.io/github/stars/tuanminhhole/openclaw-setup?style=for-the-badge&color=eab308&logo=github&logoColor=white" alt="GitHub Stars" /></a>
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
-
## 🆕 Có gì mới trong v5.16.
|
|
40
|
+
## 🆕 Có gì mới trong v5.16.3
|
|
41
41
|
|
|
42
42
|
- 🖥️ **"Điều khiển máy" — cho bot mở Chrome & ứng dụng trên máy bạn**: nút mới trên thẻ mỗi bot, bật lên là bot mở được Chrome hoặc app trong danh sách cho phép (TeamViewer, Zalo, …) trên máy đang chạy bot. **Mặc định TẮT**, cần token, chỉ giới hạn danh sách bạn khai, chỉ dùng trên máy có màn hình.
|
|
43
43
|
- 🌐 **Duyệt web chạy ngay trên mọi hệ điều hành**: máy để bàn thì bot dùng **Chrome thật** (profile đã đăng nhập, web ít nghi là bot), server thì tự mở Chromium headless — hết cảnh "không có trình duyệt" chỉ vì chưa mở Chrome.
|
|
@@ -37,8 +37,39 @@ const CHROME_PROFILE_CACHE_DIRS = [
|
|
|
37
37
|
'extensions_crx_cache', 'optimization_guide_model_store', 'blob_storage',
|
|
38
38
|
];
|
|
39
39
|
|
|
40
|
+
// openclaw 2026.8.x replaced exec-approvals.json with shared SQLite state and BLOCKS every
|
|
41
|
+
// message dispatch while the legacy file exists ("ExecApprovalsMigrationRequiredError" —
|
|
42
|
+
// measured on vps_c-thu 02/09/2026: the bot went silent with zero model calls, and doctor
|
|
43
|
+
// refuses to migrate the file itself). The exec policy already lives in openclaw.json
|
|
44
|
+
// (tools.exec), so on 2026.8+ we must neither write nor keep this file.
|
|
45
|
+
function specMajorMinor(spec) {
|
|
46
|
+
const m = String(spec || '').match(/(\d{4})\.(\d+)/);
|
|
47
|
+
return m ? Number(m[1]) * 100 + Number(m[2]) : 0;
|
|
48
|
+
}
|
|
49
|
+
let hostOcMajorMinorCache = null;
|
|
50
|
+
async function hostOpenclawMajorMinor() {
|
|
51
|
+
if (hostOcMajorMinorCache !== null) return hostOcMajorMinorCache;
|
|
52
|
+
const r = await runCapture('openclaw', ['--version'], { shell: false }).catch(() => null);
|
|
53
|
+
hostOcMajorMinorCache = specMajorMinor(r && (r.stdout || r.stderr));
|
|
54
|
+
return hostOcMajorMinorCache;
|
|
55
|
+
}
|
|
56
|
+
async function projectOpenclawMajorMinor(projectDir) {
|
|
57
|
+
// Docker projects run the pinned image spec; native projects run the host install.
|
|
58
|
+
if (isNativeProject(projectDir)) return (await hostOpenclawMajorMinor()) || specMajorMinor(OPENCLAW_NPM_SPEC);
|
|
59
|
+
return specMajorMinor(OPENCLAW_NPM_SPEC);
|
|
60
|
+
}
|
|
61
|
+
|
|
40
62
|
async function syncExecApprovals(projectDir, cfg) {
|
|
41
63
|
const openclawHome = join(projectDir, '.openclaw');
|
|
64
|
+
if ((await projectOpenclawMajorMinor(projectDir)) >= 202608) {
|
|
65
|
+
for (const f of [join(openclawHome, 'exec-approvals.json'), join(openclawHome, '.openclaw', 'exec-approvals.json')]) {
|
|
66
|
+
if (existsSync(f)) {
|
|
67
|
+
await fsp.rename(f, `${f}.bak-legacy-${Date.now()}`).catch(() => {});
|
|
68
|
+
sendLog(`[migrate] parked legacy exec approvals ${f} (openclaw >=2026.8 blocks dispatch while it exists)`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
42
73
|
const agentMetas = (cfg.agents?.list || []).map((a) => ({ agentId: a.id }));
|
|
43
74
|
const approvals = buildExecApprovalsJson({ agentMetas });
|
|
44
75
|
|
|
@@ -1404,7 +1435,39 @@ function ensureConfigShape(cfg) {
|
|
|
1404
1435
|
cfg.agents.defaults = cfg.agents.defaults || { model: { primary: DEFAULT_MODEL, fallbacks: [] } };
|
|
1405
1436
|
cfg.agents.defaults.model = cfg.agents.defaults.model || { primary: DEFAULT_MODEL, fallbacks: [] };
|
|
1406
1437
|
if (!cfg.agents.defaults.model.primary || cfg.agents.defaults.model.primary === '9router/smart-route' || cfg.agents.defaults.model.primary === 'openai/smart-route') cfg.agents.defaults.model.primary = DEFAULT_MODEL;
|
|
1407
|
-
|
|
1438
|
+
// openclaw 2026.8.x keys agents by `agents.entries` and its strict schema REJECTS
|
|
1439
|
+
// `agents.list` in the FILE (measured on vps_c-thu: doctor moves list→entries, then the
|
|
1440
|
+
// whole UI shows 0 bots because everything here reads .list). Bridge the two shapes:
|
|
1441
|
+
// hydrate a hidden .list view from entries, and expose .entries as a getter built from
|
|
1442
|
+
// .list — so JSON.stringify writes only schema-valid entries while every read/mutation
|
|
1443
|
+
// path in this file keeps working on .list unchanged. Configs that still use a real
|
|
1444
|
+
// list (openclaw ≤2026.7 projects) keep the old behavior untouched.
|
|
1445
|
+
const rawEntries = (cfg.agents.entries && typeof cfg.agents.entries === 'object' && !Array.isArray(cfg.agents.entries))
|
|
1446
|
+
? cfg.agents.entries : null;
|
|
1447
|
+
if (rawEntries) {
|
|
1448
|
+
const seeded = Array.isArray(cfg.agents.list) && cfg.agents.list.length
|
|
1449
|
+
? cfg.agents.list
|
|
1450
|
+
: Object.entries(rawEntries).map(([id, v]) => ({ id, ...(v && typeof v === 'object' ? v : {}) }));
|
|
1451
|
+
Object.defineProperty(cfg.agents, 'list', { value: seeded, enumerable: false, writable: true, configurable: true });
|
|
1452
|
+
Object.defineProperty(cfg.agents, 'entries', {
|
|
1453
|
+
enumerable: true,
|
|
1454
|
+
configurable: true,
|
|
1455
|
+
get() {
|
|
1456
|
+
const out = {};
|
|
1457
|
+
for (const a of (cfg.agents.list || [])) {
|
|
1458
|
+
if (a && a.id) { const { id, ...rest } = a; out[id] = rest; }
|
|
1459
|
+
}
|
|
1460
|
+
return out;
|
|
1461
|
+
},
|
|
1462
|
+
set(v) {
|
|
1463
|
+
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
1464
|
+
cfg.agents.list = Object.entries(v).map(([id, x]) => ({ id, ...(x && typeof x === 'object' ? x : {}) }));
|
|
1465
|
+
}
|
|
1466
|
+
},
|
|
1467
|
+
});
|
|
1468
|
+
} else {
|
|
1469
|
+
cfg.agents.list = Array.isArray(cfg.agents.list) ? cfg.agents.list : [];
|
|
1470
|
+
}
|
|
1408
1471
|
for (const agent of cfg.agents.list) {
|
|
1409
1472
|
if (agent && typeof agent === 'object') {
|
|
1410
1473
|
delete agent.channel;
|
|
@@ -2389,7 +2452,7 @@ async function startZaloConnectLogin(projectDir, accountId = 'default') {
|
|
|
2389
2452
|
const check = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', '[ -d "${OPENCLAW_HOME:-/home/node/project/.openclaw}/extensions/zalo-connect" ] && echo OK || echo MISSING'], { cwd: projectDir, shell: false }).catch(() => ({ stdout: 'ERR' }));
|
|
2390
2453
|
if (String(check.stdout || '').trim() === 'MISSING') {
|
|
2391
2454
|
sendLog(`[zalo-connect] Plugin missing — installing ${ZALO_CONNECT_PLUGIN_SPEC}...`);
|
|
2392
|
-
const installCmd = `cd /home/node/project && openclaw plugins install ${ZALO_CONNECT_PLUGIN_SPEC} --force --
|
|
2455
|
+
const installCmd = `cd /home/node/project && (openclaw plugins install ${ZALO_CONNECT_PLUGIN_SPEC} --force --accept-capabilities || openclaw plugins install ${ZALO_CONNECT_PLUGIN_SPEC} --force ${LEGACY_CLAWHUB_FLAG}) 2>&1`;
|
|
2393
2456
|
const inst = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', installCmd], { cwd: projectDir, shell: false });
|
|
2394
2457
|
const instOut = `${inst.stdout}\n${inst.stderr}`;
|
|
2395
2458
|
for (const line of instOut.split(/\r?\n/).filter(Boolean)) sendLog(`[zalo-connect] ${line}`);
|
|
@@ -2768,7 +2831,12 @@ function nativeServiceLabel(projectDir) {
|
|
|
2768
2831
|
/** The env every native CLI call needs (mirrors the docker runtime env in docker-gen.js). */
|
|
2769
2832
|
function nativeEnv(projectDir, extra = {}) {
|
|
2770
2833
|
const dir = projectDir || state.projectDir || '';
|
|
2771
|
-
|
|
2834
|
+
// openclaw 2026.8.x fs-safe refuses atomic writes through a symlinked state dir
|
|
2835
|
+
// ("Atomic replace parent must be a real directory") — hand it the real path. Native
|
|
2836
|
+
// projects on 2026.8.x keep real state at ~/.openclaw with the project dir symlinked
|
|
2837
|
+
// (daemon install rejects a custom OPENCLAW_HOME), so this path IS a symlink there.
|
|
2838
|
+
let home = join(dir, '.openclaw');
|
|
2839
|
+
try { home = fs.realpathSync(home); } catch {}
|
|
2772
2840
|
const meta = readNativeMeta(dir) || {};
|
|
2773
2841
|
const gatewayPort = String(meta.gatewayPort || state.gatewayPort || NATIVE_DEFAULT_GATEWAY_PORT);
|
|
2774
2842
|
const label = nativeServiceLabel(dir);
|
|
@@ -2806,6 +2874,27 @@ function ocCapture(projectDir, args, opts = {}) {
|
|
|
2806
2874
|
return runCapture(a.cmd, a.args, { shell: false, ...a.opts, ...opts, env: { ...(a.opts.env || {}), ...(opts.env || {}) } });
|
|
2807
2875
|
}
|
|
2808
2876
|
|
|
2877
|
+
// openclaw 2026.8.x renamed the ClawHub consent flags — plugins: --accept-capabilities,
|
|
2878
|
+
// skills: --acknowledge-install-policy-warning. 2026.7 and older only know
|
|
2879
|
+
// --acknowledge-clawhub-risk. Callers pass the NEW flag; when the CLI rejects it
|
|
2880
|
+
// ("does not recognize option") retry once with the legacy flag so an updated setup can
|
|
2881
|
+
// still manage projects running an older openclaw.
|
|
2882
|
+
const LEGACY_CLAWHUB_FLAG = '--acknowledge-clawhub-risk';
|
|
2883
|
+
const NEW_CONSENT_FLAGS = ['--accept-capabilities', '--acknowledge-install-policy-warning'];
|
|
2884
|
+
function legacyConsentArgs(args) {
|
|
2885
|
+
return args.map((a) => (NEW_CONSENT_FLAGS.includes(a) ? LEGACY_CLAWHUB_FLAG : a));
|
|
2886
|
+
}
|
|
2887
|
+
function flagNotRecognized(out) {
|
|
2888
|
+
const text = typeof out === 'string' ? out : `${out?.stdout || ''}\n${out?.stderr || ''}\n${out?.message || ''}`;
|
|
2889
|
+
return /does not recognize option/i.test(text);
|
|
2890
|
+
}
|
|
2891
|
+
/** ocCapture with the 2026.7↔2026.8 consent-flag fallback. */
|
|
2892
|
+
async function ocCaptureInstall(projectDir, args, opts = {}) {
|
|
2893
|
+
const out = await ocCapture(projectDir, args, opts);
|
|
2894
|
+
if (!flagNotRecognized(out)) return out;
|
|
2895
|
+
return ocCapture(projectDir, legacyConsentArgs(args), opts);
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2809
2898
|
/** Probe the managed gateway's own /health until it answers. */
|
|
2810
2899
|
async function waitForNativeGatewayHealthy(projectDir, timeoutMs = 120000) {
|
|
2811
2900
|
const meta = readNativeMeta(projectDir) || {};
|
|
@@ -3191,7 +3280,7 @@ async function ensureNativePlugins(projectDir, { restart = false } = {}) {
|
|
|
3191
3280
|
if (existsSync(dir)) continue;
|
|
3192
3281
|
const spec = id === ZALO_PLUGIN_ID ? ZALO_CONNECT_PLUGIN_SPEC : pluginInstallSpec(id);
|
|
3193
3282
|
sendLog(`[native] plugin ${id} missing; installing ${spec}`);
|
|
3194
|
-
const out = await
|
|
3283
|
+
const out = await ocCaptureInstall(projectDir, ['plugins', 'install', spec, '--force', '--accept-capabilities'], { timeout: 300000 });
|
|
3195
3284
|
const text = `${out.stdout || ''}\n${out.stderr || ''}`;
|
|
3196
3285
|
for (const line of text.split(/\r?\n/).map((l) => l.trimEnd()).filter(Boolean)) sendLog(`[native] ${line}`);
|
|
3197
3286
|
if (existsSync(dir) || /installed plugin/i.test(text)) installed.push(id);
|
|
@@ -3258,6 +3347,15 @@ async function startNative9Router(projectDir, { restart = false } = {}) {
|
|
|
3258
3347
|
sendLog(`[native] 9router already listening on ${routerPort}`);
|
|
3259
3348
|
return routerPort;
|
|
3260
3349
|
}
|
|
3350
|
+
// Linux: run 9router as a systemd USER unit, not a detached child. startDetached leaves the
|
|
3351
|
+
// process inside THIS server's cgroup — restarting the setup-ui service (or rebooting the VPS)
|
|
3352
|
+
// silently killed 9router and the bot lost its model (measured on vps_c-thu, 02/09/2026).
|
|
3353
|
+
// macOS/Windows keep the detached process: no deployed native host runs there yet, and each
|
|
3354
|
+
// would need its own service wrapper (launchd/Task Scheduler).
|
|
3355
|
+
if (process.platform === 'linux') {
|
|
3356
|
+
const ok = await installNative9RouterUnit(projectDir, routerPort, dataDir).catch((e) => { sendLog(`[native] 9router unit skipped: ${e.message}`); return false; });
|
|
3357
|
+
if (ok) return routerPort;
|
|
3358
|
+
}
|
|
3261
3359
|
startDetached('9router', ['-n', '-l', '-H', '127.0.0.1', '-p', String(routerPort), '--skip-update'], {
|
|
3262
3360
|
cwd: projectDir,
|
|
3263
3361
|
env: nativeEnv(projectDir, { DATA_DIR: dataDir }),
|
|
@@ -3265,6 +3363,92 @@ async function startNative9Router(projectDir, { restart = false } = {}) {
|
|
|
3265
3363
|
return routerPort;
|
|
3266
3364
|
}
|
|
3267
3365
|
|
|
3366
|
+
async function installNative9RouterUnit(projectDir, routerPort, dataDir) {
|
|
3367
|
+
const bin = resolveBinPath('9router');
|
|
3368
|
+
const binDir = bin.includes('/') ? dirname(bin) : '';
|
|
3369
|
+
const unitDir = join(os.homedir(), '.config', 'systemd', 'user');
|
|
3370
|
+
await fsp.mkdir(unitDir, { recursive: true });
|
|
3371
|
+
const pathLine = [binDir, '/usr/local/sbin', '/usr/local/bin', '/usr/sbin', '/usr/bin', '/sbin', '/bin'].filter(Boolean).join(':');
|
|
3372
|
+
const unit = [
|
|
3373
|
+
'[Unit]',
|
|
3374
|
+
`Description=9Router LLM proxy (127.0.0.1:${routerPort})`,
|
|
3375
|
+
'After=network-online.target',
|
|
3376
|
+
'Wants=network-online.target',
|
|
3377
|
+
'StartLimitIntervalSec=0',
|
|
3378
|
+
'',
|
|
3379
|
+
'[Service]',
|
|
3380
|
+
'Type=simple',
|
|
3381
|
+
`WorkingDirectory=${projectDir}`,
|
|
3382
|
+
`ExecStart=${bin.includes('/') ? bin : '/usr/bin/env 9router'} -n -l -H 127.0.0.1 -p ${routerPort} --skip-update`,
|
|
3383
|
+
'Restart=always',
|
|
3384
|
+
'RestartSec=5',
|
|
3385
|
+
`Environment=HOME=${os.homedir()}`,
|
|
3386
|
+
`Environment=DATA_DIR=${dataDir}`,
|
|
3387
|
+
`Environment=PATH=${pathLine}`,
|
|
3388
|
+
'',
|
|
3389
|
+
'[Install]',
|
|
3390
|
+
'WantedBy=default.target',
|
|
3391
|
+
].join('\n');
|
|
3392
|
+
await fsp.writeFile(join(unitDir, 'openclaw-9router.service'), `${unit}\n`, 'utf8');
|
|
3393
|
+
await run('systemctl', ['--user', 'daemon-reload'], {}).catch(() => {});
|
|
3394
|
+
const res = await runCapture('systemctl', ['--user', 'enable', '--now', 'openclaw-9router.service'], { shell: false, timeout: 30000 });
|
|
3395
|
+
if (res.code !== 0) throw new Error(String(res.stderr || res.stdout || 'systemctl enable failed').trim().slice(0, 160));
|
|
3396
|
+
sendLog('[native] 9router installed as systemd user unit openclaw-9router.service (survives UI restarts and reboots)');
|
|
3397
|
+
return true;
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
/**
|
|
3401
|
+
* openclaw 2026.8.x `daemon install` only manages the service when the state dir is the
|
|
3402
|
+
* canonical <account home>/.openclaw — a custom OPENCLAW_HOME is refused, and its fs-safe
|
|
3403
|
+
* layer refuses atomic writes through a symlinked state dir ("parent must be a real
|
|
3404
|
+
* directory"). Measured on vps_c-thu 02/09/2026. So for 2026.8+ the REAL state lives at
|
|
3405
|
+
* ~/.openclaw and <project>/.openclaw becomes a symlink to it (junction on Windows, so no
|
|
3406
|
+
* admin rights needed). nativeEnv() realpaths the symlink, so every other CLI call keeps
|
|
3407
|
+
* working. One native project per OS account — which is how every deployed host runs.
|
|
3408
|
+
* Returns the env for `daemon install` (plain HOME, no OPENCLAW_* overrides), or null when
|
|
3409
|
+
* the runtime is older than 2026.8 and nothing should change.
|
|
3410
|
+
*/
|
|
3411
|
+
async function prepareNativeStateHome(projectDir) {
|
|
3412
|
+
if ((await hostOpenclawMajorMinor()) < 202608) return null;
|
|
3413
|
+
const home = os.homedir();
|
|
3414
|
+
const homeState = join(home, '.openclaw');
|
|
3415
|
+
const projState = join(projectDir, '.openclaw');
|
|
3416
|
+
const installEnv = { ...nativeEnv(projectDir) };
|
|
3417
|
+
delete installEnv.OPENCLAW_HOME;
|
|
3418
|
+
delete installEnv.OPENCLAW_STATE_DIR;
|
|
3419
|
+
installEnv.HOME = home;
|
|
3420
|
+
if (resolve(projState) === resolve(homeState)) return installEnv;
|
|
3421
|
+
const linkType = process.platform === 'win32' ? 'junction' : 'dir';
|
|
3422
|
+
const projIsLink = (() => { try { return fs.lstatSync(projState).isSymbolicLink(); } catch { return false; } })();
|
|
3423
|
+
if (projIsLink) {
|
|
3424
|
+
// Already migrated (or hand-fixed): just make sure the link lands on the home state.
|
|
3425
|
+
try {
|
|
3426
|
+
if (resolve(fs.realpathSync(projState)) === resolve(homeState)) return installEnv;
|
|
3427
|
+
} catch {}
|
|
3428
|
+
throw new Error(`${projState} là symlink nhưng không trỏ về ${homeState} — kiểm tay trước khi cài service`);
|
|
3429
|
+
}
|
|
3430
|
+
const homeExists = existsSync(homeState) || (() => { try { fs.lstatSync(homeState); return true; } catch { return false; } })();
|
|
3431
|
+
if (homeExists) {
|
|
3432
|
+
const homeIsLink = (() => { try { return fs.lstatSync(homeState).isSymbolicLink(); } catch { return false; } })();
|
|
3433
|
+
if (homeIsLink) {
|
|
3434
|
+
// Reverse layout from an earlier hand-fix attempt: drop the link, the real dir moves in below.
|
|
3435
|
+
fs.unlinkSync(homeState);
|
|
3436
|
+
} else if (existsSync(join(homeState, 'openclaw.json'))) {
|
|
3437
|
+
// A real, populated state dir that is not this project's — do not touch someone's data.
|
|
3438
|
+
throw new Error(`${homeState} đã chứa state của một project khác — 2026.8 chỉ hỗ trợ MỘT project native mỗi tài khoản`);
|
|
3439
|
+
} else {
|
|
3440
|
+
// Stray/partial dir (stale CLI runs create these): park it, keep nothing in the way.
|
|
3441
|
+
const parked = `${homeState}.bak-stray-${Date.now()}`;
|
|
3442
|
+
await fsp.rename(homeState, parked);
|
|
3443
|
+
sendLog(`[native] parked stray state dir ${homeState} -> ${parked}`);
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
await fsp.rename(projState, homeState);
|
|
3447
|
+
fs.symlinkSync(homeState, projState, linkType);
|
|
3448
|
+
sendLog(`[native] state moved to ${homeState}; ${projState} is now a symlink (openclaw >=2026.8 layout)`);
|
|
3449
|
+
return installEnv;
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3268
3452
|
/**
|
|
3269
3453
|
* Bring a native project up end to end: 9router → smart-route sync → resolve its API key into
|
|
3270
3454
|
* openclaw.json → install the gateway as a managed service. Order matters: the gateway must boot
|
|
@@ -3318,7 +3502,16 @@ async function startNativeRuntime({ projectDir, osChoice = '', gatewayPort, rout
|
|
|
3318
3502
|
// equivalent of docker's `restart: always`. --force so re-running install updates the port.
|
|
3319
3503
|
const env = nativeEnv(projectDir);
|
|
3320
3504
|
await ensureSystemdLinger();
|
|
3321
|
-
|
|
3505
|
+
// openclaw 2026.8.x refuses `daemon install` with a custom OPENCLAW_HOME ("service
|
|
3506
|
+
// management skipped: non-default state dir"). Move the real state to ~/.openclaw and
|
|
3507
|
+
// leave a symlink (junction on Windows) at <project>/.openclaw, then install with the
|
|
3508
|
+
// plain account HOME. Older runtimes keep the original env untouched.
|
|
3509
|
+
const daemonEnv = await prepareNativeStateHome(projectDir).catch((e) => { sendLog(`[native] state-home prep skipped: ${e.message}`); return null; });
|
|
3510
|
+
if (daemonEnv) {
|
|
3511
|
+
await run('openclaw', ['daemon', 'install', '--force', '--port', String(gwPort)], { cwd: projectDir, env: daemonEnv });
|
|
3512
|
+
} else {
|
|
3513
|
+
await run('openclaw', ['daemon', 'install', '--force', '--port', String(gwPort)], { cwd: projectDir, env });
|
|
3514
|
+
}
|
|
3322
3515
|
await hardenNativeServiceRestarts(projectDir).catch((e) => sendLog(`[native] service hardening skipped: ${e.message}`));
|
|
3323
3516
|
await clearNativeServiceFailure(projectDir).catch(() => {});
|
|
3324
3517
|
// `daemon install` already STARTED the unit, and that first boot begins the state migrations that
|
|
@@ -5326,6 +5519,23 @@ async function discoverDockerBotProjectRoots() {
|
|
|
5326
5519
|
// launcher's parent — that covers the folders users actually pick (e.g. ~/openclaw-native, D:\bot)
|
|
5327
5520
|
// without a full filesystem walk. Mirrors discoverDockerBotProjectRoots so discoverProjects can
|
|
5328
5521
|
// surface native projects even when this install has no saved state for them.
|
|
5522
|
+
// A directory whose .openclaw merely HOLDS another project's state is not a project of its
|
|
5523
|
+
// own. openclaw 2026.8.x native layout puts the real state at ~/.openclaw with the project
|
|
5524
|
+
// dir symlinked to it — without this filter the account home shows up as a phantom "root"
|
|
5525
|
+
// project tab after every refresh (measured on vps_c-thu). The native marker's `label` was
|
|
5526
|
+
// written from the ORIGINAL project dir, so a mismatch identifies the phantom.
|
|
5527
|
+
function isPhantomStateDirProject(dir) {
|
|
5528
|
+
try {
|
|
5529
|
+
const meta = readNativeMeta(dir);
|
|
5530
|
+
if (!meta || !meta.label) return false;
|
|
5531
|
+
// Compare against the label DERIVED from the directory name — nativeServiceLabel()
|
|
5532
|
+
// itself returns meta.label first, which would make this check always pass.
|
|
5533
|
+
const derived = `ai.openclaw.gateway.${slugify(basename(dir || 'openclaw'), 'bot')}`;
|
|
5534
|
+
return meta.label !== derived;
|
|
5535
|
+
} catch {}
|
|
5536
|
+
return false;
|
|
5537
|
+
}
|
|
5538
|
+
|
|
5329
5539
|
async function discoverNativeProjectRoots(rootProjectDir) {
|
|
5330
5540
|
const roots = new Set();
|
|
5331
5541
|
const bases = new Set();
|
|
@@ -5345,7 +5555,7 @@ async function discoverNativeProjectRoots(rootProjectDir) {
|
|
|
5345
5555
|
if (rootProjectDir && existsSync(nativeMarkerPath(rootProjectDir)) && existsSync(join(rootProjectDir, '.openclaw', 'openclaw.json'))) {
|
|
5346
5556
|
roots.add(resolve(rootProjectDir));
|
|
5347
5557
|
}
|
|
5348
|
-
return [...roots];
|
|
5558
|
+
return [...roots].filter((dir) => !isPhantomStateDirProject(dir));
|
|
5349
5559
|
}
|
|
5350
5560
|
|
|
5351
5561
|
async function findLatestProject(rootProjectDir) {
|
|
@@ -5383,7 +5593,7 @@ async function findLatestProject(rootProjectDir) {
|
|
|
5383
5593
|
|
|
5384
5594
|
if (existsSync(join(full, '.openclaw', 'openclaw.json'))) {
|
|
5385
5595
|
const st = await fsp.stat(join(full, '.openclaw', 'openclaw.json')).catch(() => null);
|
|
5386
|
-
if (st) candidates.push({ dir: full, mtimeMs: st.mtimeMs });
|
|
5596
|
+
if (st && !isPhantomStateDirProject(full)) candidates.push({ dir: full, mtimeMs: st.mtimeMs });
|
|
5387
5597
|
return;
|
|
5388
5598
|
}
|
|
5389
5599
|
const entries = await fsp.readdir(full, { withFileTypes: true }).catch(() => []);
|
|
@@ -5453,6 +5663,10 @@ async function computeDiscoverProjects(rootProjectDir) {
|
|
|
5453
5663
|
}
|
|
5454
5664
|
}
|
|
5455
5665
|
|
|
5666
|
+
// Drop phantom state-dir "projects" that slipped into the saved list (e.g. the account
|
|
5667
|
+
// home after a 2026.8.x native state move) — and keep them out of the persisted state.
|
|
5668
|
+
state.projects = state.projects.filter((p) => !isPhantomStateDirProject(p.projectDir));
|
|
5669
|
+
|
|
5456
5670
|
// In parallel: each buildProjectMeta runs runtime detection (docker calls, port probes), so a
|
|
5457
5671
|
// handful of projects turned into seconds of dashboard load when this was a sequential loop.
|
|
5458
5672
|
const metas = await Promise.all(
|
|
@@ -5475,16 +5689,22 @@ async function computeDiscoverProjects(rootProjectDir) {
|
|
|
5475
5689
|
}
|
|
5476
5690
|
|
|
5477
5691
|
async function resolveProjectDir(rootProjectDir, body = {}) {
|
|
5478
|
-
|
|
5692
|
+
// Every acceptance path below must refuse phantom state-dir "projects" — the browser can
|
|
5693
|
+
// keep sending a remembered projectDir (e.g. "/root") long after it stopped being one,
|
|
5694
|
+
// and accepting it re-saves the phantom into state on the next saveState().
|
|
5695
|
+
if (body.projectDir && existsSync(join(resolve(String(body.projectDir)), '.openclaw', 'openclaw.json'))
|
|
5696
|
+
&& !isPhantomStateDirProject(resolve(String(body.projectDir)))) {
|
|
5479
5697
|
state.projectDir = resolve(String(body.projectDir));
|
|
5480
5698
|
await syncRuntimeState(state.projectDir);
|
|
5481
5699
|
return state.projectDir;
|
|
5482
5700
|
}
|
|
5701
|
+
if (state.projectDir && isPhantomStateDirProject(state.projectDir)) state.projectDir = null;
|
|
5483
5702
|
if (state.projectDir && existsSync(join(state.projectDir, '.openclaw', 'openclaw.json'))) {
|
|
5484
5703
|
await syncRuntimeState(state.projectDir);
|
|
5485
5704
|
return state.projectDir;
|
|
5486
5705
|
}
|
|
5487
5706
|
await loadSavedState(rootProjectDir);
|
|
5707
|
+
if (state.projectDir && isPhantomStateDirProject(state.projectDir)) state.projectDir = null;
|
|
5488
5708
|
if (state.projectDir && existsSync(join(state.projectDir, '.openclaw', 'openclaw.json'))) {
|
|
5489
5709
|
await syncRuntimeState(state.projectDir);
|
|
5490
5710
|
return state.projectDir;
|
|
@@ -5527,6 +5747,7 @@ async function buildProjectMeta(projectDir) {
|
|
|
5527
5747
|
async function connectExistingProject(projectDir, rootProjectDir) {
|
|
5528
5748
|
const resolved = resolve(String(projectDir || ''));
|
|
5529
5749
|
if (!existsSync(join(resolved, '.openclaw', 'openclaw.json'))) throw httpError(404, 'openclaw.json not found in selected project');
|
|
5750
|
+
if (isPhantomStateDirProject(resolved)) throw httpError(400, `${resolved} chỉ là nơi chứa state của project khác (symlink 2026.8.x), không phải project riêng`);
|
|
5530
5751
|
// Switch the active project + return its bots FAST (a plain file read). The heavy runtime
|
|
5531
5752
|
// probing — detectRuntime runs `openclaw gateway status` + `config get` (slow CLI / docker
|
|
5532
5753
|
// exec) and used to run TWICE here (syncRuntimeState + buildProjectMeta), ~6s total — is
|
|
@@ -5888,7 +6109,7 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
5888
6109
|
// Native: no container — install on the host with the project env (ocCapture) so the
|
|
5889
6110
|
// skill lands in this project's workspace, then reload the managed gateway service.
|
|
5890
6111
|
sendLog(`[skill] Installing/updating clawhub:${slug} natively for agent ${agentId}...`);
|
|
5891
|
-
const out = await
|
|
6112
|
+
const out = await ocCaptureInstall(projectDir, ['skills', 'install', slug, '--agent', agentId, '--force', '--acknowledge-install-policy-warning']);
|
|
5892
6113
|
for (const line of `${out.stdout}\n${out.stderr}`.split(/\r?\n/).filter(Boolean)) sendLog(line);
|
|
5893
6114
|
if (out.code !== 0 && !isSkillFolderExists(projectDir, agentId, slug)) {
|
|
5894
6115
|
throw new Error(out.stderr || out.stdout || `Failed to install skill ${slug}.`);
|
|
@@ -5899,7 +6120,7 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
5899
6120
|
const botContainer = getBotContainerName(projectDir);
|
|
5900
6121
|
sendLog(`[skill] Installing/updating clawhub:${slug} inside container ${botContainer} for agent ${agentId}...`);
|
|
5901
6122
|
|
|
5902
|
-
const cmd = `cd /home/node/project && openclaw skills install ${slug} --agent ${agentId} --force --acknowledge-
|
|
6123
|
+
const cmd = `cd /home/node/project && (openclaw skills install ${slug} --agent ${agentId} --force --acknowledge-install-policy-warning || openclaw skills install ${slug} --agent ${agentId} --force ${LEGACY_CLAWHUB_FLAG})`;
|
|
5903
6124
|
const cmdOut = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', cmd], { cwd: projectDir, shell: false });
|
|
5904
6125
|
|
|
5905
6126
|
if (cmdOut) {
|
|
@@ -5921,9 +6142,13 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
5921
6142
|
await run('openclaw', ['doctor', '--fix'], { cwd: projectDir, env: openclawProjectEnv(projectDir) }).catch((err) => sendLog(`[skill] doctor --fix skipped: ${err.message}`));
|
|
5922
6143
|
sendLog(`[skill] Installing clawhub:${slug} for agent ${agentId}...`);
|
|
5923
6144
|
|
|
5924
|
-
|
|
6145
|
+
const skillArgs = ['skills', 'install', slug, '--agent', agentId, '--force', '--acknowledge-install-policy-warning'];
|
|
6146
|
+
await run('openclaw', skillArgs, {
|
|
5925
6147
|
cwd: projectDir,
|
|
5926
6148
|
env: openclawProjectEnv(projectDir)
|
|
6149
|
+
}).catch(async (err) => {
|
|
6150
|
+
if (!flagNotRecognized(err)) throw err;
|
|
6151
|
+
await run('openclaw', legacyConsentArgs(skillArgs), { cwd: projectDir, env: openclawProjectEnv(projectDir) });
|
|
5927
6152
|
});
|
|
5928
6153
|
}
|
|
5929
6154
|
|
|
@@ -5952,14 +6177,14 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
5952
6177
|
}
|
|
5953
6178
|
if (native) {
|
|
5954
6179
|
sendLog(`[zalo-connect] Installing/updating ${ZALO_CONNECT_PLUGIN_SPEC} natively...`);
|
|
5955
|
-
const out = await
|
|
6180
|
+
const out = await ocCaptureInstall(projectDir, ['plugins', 'install', ZALO_CONNECT_PLUGIN_SPEC, '--force', '--accept-capabilities']);
|
|
5956
6181
|
if (out) for (const line of `${out.stdout}\n${out.stderr}`.split(/\r?\n/).filter(Boolean)) sendLog(`[zalo-connect] ${line}`);
|
|
5957
6182
|
const okDir = existsSync(join(projectDir, '.openclaw', 'extensions', 'zalo-connect'));
|
|
5958
6183
|
if (out.code !== 0 && !okDir) throw new Error(out.stderr || out.stdout || 'Failed to install zalo-connect.');
|
|
5959
6184
|
} else if (composeDir) {
|
|
5960
6185
|
const botContainer = getBotContainerName(projectDir);
|
|
5961
6186
|
sendLog(`[zalo-connect] Installing/updating ${ZALO_CONNECT_PLUGIN_SPEC} inside ${botContainer}...`);
|
|
5962
|
-
const cmd = `cd /home/node/project && openclaw plugins install ${ZALO_CONNECT_PLUGIN_SPEC} --force --
|
|
6187
|
+
const cmd = `cd /home/node/project && (openclaw plugins install ${ZALO_CONNECT_PLUGIN_SPEC} --force --accept-capabilities || openclaw plugins install ${ZALO_CONNECT_PLUGIN_SPEC} --force ${LEGACY_CLAWHUB_FLAG}) 2>&1`;
|
|
5963
6188
|
const out = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', cmd], { cwd: projectDir, shell: false });
|
|
5964
6189
|
if (out) for (const line of `${out.stdout}\n${out.stderr}`.split(/\r?\n/).filter(Boolean)) sendLog(`[zalo-connect] ${line}`);
|
|
5965
6190
|
const okDir = existsSync(join(projectDir, '.openclaw', 'extensions', 'zalo-connect'));
|
|
@@ -5992,7 +6217,7 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
5992
6217
|
}
|
|
5993
6218
|
const installSpec = pluginInstallSpec(id);
|
|
5994
6219
|
const installArgs = ['plugins', 'install', installSpec, '--force'];
|
|
5995
|
-
if (installSpec.startsWith('clawhub:')) installArgs.push('--
|
|
6220
|
+
if (installSpec.startsWith('clawhub:')) installArgs.push('--accept-capabilities');
|
|
5996
6221
|
|
|
5997
6222
|
let composeDir = null;
|
|
5998
6223
|
if (isNativeProject(projectDir)) {
|
|
@@ -6009,11 +6234,11 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
6009
6234
|
let cmdOut;
|
|
6010
6235
|
if (isNativeProject(projectDir)) {
|
|
6011
6236
|
sendLog(`[plugin] Installing/updating ${installSpec} natively...`);
|
|
6012
|
-
cmdOut = await
|
|
6237
|
+
cmdOut = await ocCaptureInstall(projectDir, installArgs);
|
|
6013
6238
|
} else {
|
|
6014
6239
|
const botContainer = getBotContainerName(projectDir);
|
|
6015
6240
|
sendLog(`[plugin] Installing/updating ${installSpec} inside container ${botContainer}...`);
|
|
6016
|
-
const cmd = `cd /home/node/project && openclaw ${installArgs.join(' ')}`;
|
|
6241
|
+
const cmd = `cd /home/node/project && (openclaw ${installArgs.join(' ')} || openclaw ${legacyConsentArgs(installArgs).join(' ')})`;
|
|
6017
6242
|
cmdOut = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', cmd], { cwd: projectDir, shell: false });
|
|
6018
6243
|
}
|
|
6019
6244
|
|
|
@@ -6122,6 +6347,13 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
6122
6347
|
cwd: projectDir,
|
|
6123
6348
|
env: openclawProjectEnv(projectDir),
|
|
6124
6349
|
resolveOnPattern: /Installed plugin:/
|
|
6350
|
+
}).catch(async (err) => {
|
|
6351
|
+
if (!flagNotRecognized(err)) throw err;
|
|
6352
|
+
await run('openclaw', legacyConsentArgs(installArgs), {
|
|
6353
|
+
cwd: projectDir,
|
|
6354
|
+
env: openclawProjectEnv(projectDir),
|
|
6355
|
+
resolveOnPattern: /Installed plugin:/
|
|
6356
|
+
});
|
|
6125
6357
|
}).catch((err) => {
|
|
6126
6358
|
// Fallback verification: if the plugin's folder or mapped key is present, it succeeded despite integrity warnings
|
|
6127
6359
|
const aliases = ['openclaw-zalo-mod', 'zalo-mod', id, id.replace('openclaw-', '')];
|
|
@@ -204,7 +204,9 @@
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
// ── commands ──────────────────────────────────────────────────────────────
|
|
207
|
-
|
|
207
|
+
// No `ownerDisplay`: openclaw 2026.8.x strict schema rejects it as an unrecognized key
|
|
208
|
+
// and refuses to start (measured on vps_c-thu, 02/09/2026).
|
|
209
|
+
cfg.commands = { native: 'auto', nativeSkills: 'auto', restart: true };
|
|
208
210
|
if (selectedSkills.includes('scheduler')) {
|
|
209
211
|
cfg.commands.ownerAllowFrom = ['*'];
|
|
210
212
|
}
|
|
@@ -476,7 +478,8 @@
|
|
|
476
478
|
plugins.allow = allow;
|
|
477
479
|
// Select learning-memory as the active context engine (always-on memory).
|
|
478
480
|
plugins.slots = { contextEngine: 'learning-memory' };
|
|
479
|
-
|
|
481
|
+
// No `bundledDiscovery`: openclaw 2026.8.x moved it into shared SQLite state and the
|
|
482
|
+
// strict schema rejects the key in openclaw.json (measured on vps_c-thu, 02/09/2026).
|
|
480
483
|
|
|
481
484
|
return { plugins };
|
|
482
485
|
}
|
|
@@ -207,7 +207,7 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
207
207
|
// Gio gate theo version openclaw THAT trong container: <2026.8 thi backfill nhu cu, ≥2026.8 thi
|
|
208
208
|
// GO key neu con (tu chua cac project da nhiem). Khong doc duoc version thi KHONG dong nao chay
|
|
209
209
|
// — tha thieu mot default con hon gieo key lam gateway tu choi boot.
|
|
210
|
-
const contextDefaultsScript = `const fs=require('fs'),path=require('path');const p=path.join(process.cwd(),'.openclaw','openclaw.json');if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));let ch=false;c.skills=c.skills||{};c.skills.workshop=c.skills.workshop||{};if(!c.skills.workshop.approvalPolicy){c.skills.workshop.approvalPolicy='auto';ch=true;}if(c.browser&&c.browser.enabled!==false){c.tools=c.tools||{};const dn=Array.isArray(c.tools.deny)?c.tools.deny:[];if(!dn.includes('browser')){dn.push('browser');c.tools.deny=dn;ch=true;}}
|
|
210
|
+
const contextDefaultsScript = `const fs=require('fs'),path=require('path');const p=path.join(process.cwd(),'.openclaw','openclaw.json');if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));let ch=false;c.skills=c.skills||{};c.skills.workshop=c.skills.workshop||{};if(!c.skills.workshop.approvalPolicy){c.skills.workshop.approvalPolicy='auto';ch=true;}if(c.browser&&c.browser.enabled!==false){c.tools=c.tools||{};const dn=Array.isArray(c.tools.deny)?c.tools.deny:[];if(!dn.includes('browser')){dn.push('browser');c.tools.deny=dn;ch=true;}}let ocMajorMinor=0;try{const v=String(require('child_process').execSync('openclaw --version',{stdio:['ignore','pipe','ignore']})).match(/(\\d{4})\\.(\\d+)/);if(v)ocMajorMinor=Number(v[1])*100+Number(v[2]);}catch(e){}const d=(c.agents&&c.agents.defaults)?c.agents.defaults:null;if(d){if(d.imageMaxDimensionPx===undefined){d.imageMaxDimensionPx=1024;ch=true;}if(d.imageQuality===undefined){d.imageQuality='efficient';ch=true;}if(ocMajorMinor&&ocMajorMinor<202608){d.contextLimits=d.contextLimits||{};if(d.contextLimits.toolResultMaxChars===undefined){d.contextLimits.toolResultMaxChars=12000;ch=true;}}else if(ocMajorMinor>=202608&&d.contextLimits&&d.contextLimits.toolResultMaxChars!==undefined){delete d.contextLimits.toolResultMaxChars;ch=true;}}if(ocMajorMinor>=202608){const ag=c.agents||{};const nEntries=(Array.isArray(ag.list)?ag.list.length:0)+(ag.entries&&typeof ag.entries==='object'?Object.keys(ag.entries).length:0);if(nEntries>1&&ag.ownership!=='explicit'){ag.ownership='explicit';c.agents=ag;ch=true;}try{const agRoot=path.join(process.cwd(),'.openclaw','agents');for(const id of fs.readdirSync(agRoot)){const sf=path.join(agRoot,id,'sessions','sessions.json');if(fs.existsSync(sf)){fs.renameSync(sf,sf+'.bak-legacy-'+Date.now());console.log('[migrate] parked legacy session store '+sf);}}}catch(e){}if(c.commands&&c.commands.ownerDisplay!==undefined){delete c.commands.ownerDisplay;ch=true;}if(c.plugins&&c.plugins.bundledDiscovery!==undefined){delete c.plugins.bundledDiscovery;ch=true;}if(Array.isArray(ag.list)){ag.entries=(ag.entries&&typeof ag.entries==='object'&&!Array.isArray(ag.entries))?ag.entries:{};for(const a of ag.list){if(a&&a.id&&!ag.entries[a.id]){const{id:_aid,...rest}=a;ag.entries[_aid]=rest;}}delete ag.list;c.agents=ag;ch=true;console.log('[migrate] moved agents.list into agents.entries');}try{const ea=path.join(process.cwd(),'.openclaw','exec-approvals.json');if(fs.existsSync(ea)){fs.renameSync(ea,ea+'.bak-legacy-'+Date.now());console.log('[migrate] parked legacy exec approvals '+ea);}}catch(e){}}const pr=c.models&&c.models.providers&&c.models.providers['9router'];if(pr&&Array.isArray(pr.models)){for(const m of pr.models){if(m&&m.id==='smart-route'&&(m.contextWindow===200000||m.contextWindow===131072)){m.contextWindow=1048576;ch=true;}}}if(ch)fs.writeFileSync(p,JSON.stringify(c,null,2));}`;
|
|
211
211
|
|
|
212
212
|
function buildDockerArtifacts(options) {
|
|
213
213
|
const {
|
|
@@ -259,10 +259,10 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
259
259
|
const stateVolDecl = useStateVolume ? '\n openclaw-state:' : '';
|
|
260
260
|
const stateVolBlock = useStateVolume ? '\n\nvolumes:\n openclaw-state:' : '';
|
|
261
261
|
const skillLines = dockerfileSkillInstallMode === 'build' && allSkills.length > 0
|
|
262
|
-
? `\n# Install skills (ClawHub)\n${allSkills.map((skill) => `RUN openclaw skills install ${skill} --acknowledge-clawhub-risk || echo "Warning: Failed to install ${skill} due to rate limits."`).join('\n')}\n`
|
|
262
|
+
? `\n# Install skills (ClawHub)\n${allSkills.map((skill) => `RUN openclaw skills install ${skill} --acknowledge-install-policy-warning || openclaw skills install ${skill} --acknowledge-clawhub-risk || echo "Warning: Failed to install ${skill} due to rate limits."`).join('\n')}\n`
|
|
263
263
|
: '';
|
|
264
264
|
const pluginLines = dockerfilePlugins.length > 0
|
|
265
|
-
? `\n# Install plugins (ClawHub)\n${dockerfilePlugins.map((p) => `RUN openclaw plugins install ${p} --acknowledge-clawhub-risk || echo "Warning: Failed to install plugin ${p}"`).join('\n')}\n`
|
|
265
|
+
? `\n# Install plugins (ClawHub)\n${dockerfilePlugins.map((p) => `RUN openclaw plugins install ${p} --accept-capabilities || openclaw plugins install ${p} --acknowledge-clawhub-risk || echo "Warning: Failed to install plugin ${p}"`).join('\n')}\n`
|
|
266
266
|
: '';
|
|
267
267
|
const patchLine = `RUN node -e "const fs=require('fs');const path=require('path');const dir='/usr/local/lib/node_modules/openclaw/dist';const from='\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const to='\\t\\t\\t\\t\\ttimeoutOverrideSeconds: Math.max(1, Math.ceil(timeoutMs / 1e3)),\\n\\t\\t\\t\\t\\tonAgentRunStart: (runId) => {';const files=fs.readdirSync(dir).filter(n=>/\\.js$/.test(n));let patched=0;for(const file of files){const p=path.join(dir,file);let s='';try{s=fs.readFileSync(p,'utf8');}catch{continue;}if(s.includes(to)||!s.includes(from))continue;s=s.replace(from,to);fs.writeFileSync(p,s);patched++;}if(!patched){process.exit(0);}"`;
|
|
268
268
|
|
|
@@ -351,7 +351,7 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
351
351
|
' echo "[entrypoint] warning: failed to clone plugin $spec"',
|
|
352
352
|
' fi',
|
|
353
353
|
' ;;',
|
|
354
|
-
' *) openclaw plugins install "$spec" --acknowledge-clawhub-risk 2>/dev/null || echo "[entrypoint] warning: failed to install plugin $spec" ;;',
|
|
354
|
+
' *) openclaw plugins install "$spec" --accept-capabilities 2>/dev/null || openclaw plugins install "$spec" --acknowledge-clawhub-risk 2>/dev/null || echo "[entrypoint] warning: failed to install plugin $spec" ;;',
|
|
355
355
|
' esac',
|
|
356
356
|
'}',
|
|
357
357
|
'ensure_skill() {',
|
|
@@ -361,7 +361,7 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
361
361
|
' return 0',
|
|
362
362
|
' fi',
|
|
363
363
|
' echo "[entrypoint] skill $id missing; installing"',
|
|
364
|
-
' openclaw skills install "$id" --acknowledge-clawhub-risk 2>/dev/null || echo "[entrypoint] warning: failed to install skill $id"',
|
|
364
|
+
' openclaw skills install "$id" --acknowledge-install-policy-warning 2>/dev/null || openclaw skills install "$id" --acknowledge-clawhub-risk 2>/dev/null || echo "[entrypoint] warning: failed to install skill $id"',
|
|
365
365
|
'}',
|
|
366
366
|
'echo "[entrypoint] ensuring runtime assets, then starting gateway"',
|
|
367
367
|
];
|
|
@@ -467,6 +467,18 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
467
467
|
// `set -e` is on: a non-zero return here must never stop the gateway from starting.
|
|
468
468
|
'start_local_headless_chrome || true',
|
|
469
469
|
].join('\n'));
|
|
470
|
+
// openclaw ≥2026.8 TACH duckduckgo khoi bundle thanh plugin external co capability consent.
|
|
471
|
+
// Config do setup sinh (bot chon skill web-search) van khai `plugins.entries.duckduckgo`,
|
|
472
|
+
// va gateway 2026.8.x TU CHOI ready khi entry khai ma plugin chua cai ("Plugin verification
|
|
473
|
+
// failed … requires capability consent") — do that tren 103.98.149.154, 31/08/2026.
|
|
474
|
+
// Cai o day, truoc gateway run, dung lenh + co ma chinh gateway goi y; `yes |` cho cau hoi
|
|
475
|
+
// consent; loi khong chan boot (|| true) — thieu web search chi la mat mot tool phu.
|
|
476
|
+
runtimeParts.push([
|
|
477
|
+
'if grep -q \'"duckduckgo"\' "$OPENCLAW_HOME/openclaw.json" 2>/dev/null && [ ! -d "$OPENCLAW_HOME/extensions/duckduckgo" ]; then',
|
|
478
|
+
' echo "[entrypoint] installing external duckduckgo plugin (openclaw >=2026.8 unbundled it)"',
|
|
479
|
+
' yes | openclaw plugins install @openclaw/duckduckgo-plugin --accept-capabilities || true',
|
|
480
|
+
'fi',
|
|
481
|
+
].join('\n'));
|
|
470
482
|
// ── Doctor-on-upgrade ─────────────────────────────────────────────────────────────────
|
|
471
483
|
// Update openclaw khong bao gio chi la "doi so version": schema config doi (key cu bi TU CHOI
|
|
472
484
|
// chu khong bo qua) va state DB doi migration (audit-events-v2…), va doctor chi chay sach khi
|
|
@@ -855,6 +855,12 @@ Skills cung cấp công cụ cho bạn. Cần cái nào, đọc \`SKILL.md\` c
|
|
|
855
855
|
- **Discord links:** Bọc nhiều link trong \`<>\` để khỏi hiện embed
|
|
856
856
|
- **WhatsApp/Zalo:** Không dùng header — dùng **đậm** hoặc IN HOA để nhấn mạnh
|
|
857
857
|
|
|
858
|
+
**📎 File user gửi qua chat (zip/pdf/doc...):**
|
|
859
|
+
|
|
860
|
+
- File đã được runtime tải sẵn và nằm ở đường dẫn TUYỆT ĐỐI trong metadata của lượt đó (\`MediaPath\`, hoặc dòng ghi chú "[Attached file(s) already saved to disk...]") — thường là \`.openclaw/media/inbound/…\`, **BÊN NGOÀI workspace của bạn**.
|
|
861
|
+
- Dùng NGUYÊN đường dẫn đó với \`exec\`/\`read\`. ĐỪNG \`find\`/\`ls\` trong workspace rồi kết luận "file chưa được lưu" — file không nằm ở đó là điều bình thường.
|
|
862
|
+
- Không thấy đường dẫn trong lượt hiện tại → nói thật là không thấy file đính kèm, đừng đoán.
|
|
863
|
+
|
|
858
864
|
## 💓 Heartbeats — Chủ động!
|
|
859
865
|
|
|
860
866
|
Khi nhận heartbeat poll, đừng chỉ trả \`HEARTBEAT_OK\` mọi lần. Dùng heartbeat có ích!
|
|
@@ -1059,6 +1065,12 @@ Skills provide your tools. When you need one, check its \`SKILL.md\`. Keep local
|
|
|
1059
1065
|
- **Discord links:** Wrap multiple links in \`<>\` to suppress embeds
|
|
1060
1066
|
- **WhatsApp/Zalo:** No headers — use **bold** or CAPS for emphasis
|
|
1061
1067
|
|
|
1068
|
+
**📎 Files users send through chat (zip/pdf/doc...):**
|
|
1069
|
+
|
|
1070
|
+
- The runtime already downloaded the file to an ABSOLUTE path given in that turn's metadata (\`MediaPath\`, or a note line "[Attached file(s) already saved to disk...]") — usually under \`.openclaw/media/inbound/…\`, **OUTSIDE your workspace**.
|
|
1071
|
+
- Use that EXACT path with \`exec\`/\`read\`. Do NOT \`find\`/\`ls\` inside your workspace and then claim "the file was never saved" — it is normal for the file not to be there.
|
|
1072
|
+
- If no path is present in the current turn, say plainly that you see no attachment — don't guess.
|
|
1073
|
+
|
|
1062
1074
|
## 💓 Heartbeats — Be Proactive!
|
|
1063
1075
|
|
|
1064
1076
|
When you receive a heartbeat poll, don't just reply \`HEARTBEAT_OK\` every time. Use heartbeats productively!
|