create-openclaw-bot 5.16.2 → 5.16.4

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.2-0EA5E9?style=for-the-badge" alt="Version 5.16.2" /></a>
10
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.16.4-0EA5E9?style=for-the-badge" alt="Version 5.16.4" /></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,15 +37,26 @@
37
37
 
38
38
  ---
39
39
 
40
- ## 🆕 What's New in v5.16.2
40
+ ## 🆕 What's New in v5.16.4
41
+
42
+ - 🚑 **The big OpenClaw 2026.8.1 catch-up — the old setup had gone stale; this wave heals it all in one pass.** OpenClaw (the platform the bots run on) shipped a major upgrade and changed the rules: configs are strictly checked, data moved to a new home, several install commands were renamed. With the old setup that meant fresh installs could stall, running bots suddenly went **silent**, and the admin screen showed **0 bots** while the bot was alive. As of 5.16.3–5.16.4: fresh installs run smoothly on 2026.8.1 (Docker and native, Windows/macOS/Linux), and **existing machines heal themselves on upgrade** — stale config cleaned, data relocated, and the old file that could mute the bot retired automatically.
43
+ - 🤖 **The bot stops claiming "I can't see the file"**: files sent through chat now come with clear directions for the AI — even weaker models find them.
44
+ - 🔁 **9Router (the AI model router) comes back on its own** after a server reboot or UI maintenance — previously it could die silently and the bot lost its brain.
45
+ - 🔄 **The in-app "Update" button really updates now**: on standard server installs it used to just restart the old version while reporting success — now it pulls the latest and relaunches itself.
46
+ - 🧭 **One setup build manages both generations** (2026.7 and 2026.8) — renamed commands are tried the new way first with an automatic fallback, so nobody is forced to upgrade overnight.
47
+
48
+ <details>
49
+ <summary><b>Previous: What's new in v5.15.3</b></summary>
41
50
 
42
51
  - 🖥️ **"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
52
  - 🌐 **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.
44
53
 
54
+ </details>
55
+
45
56
  <details>
46
57
  <summary><b>Previous: What's new in v5.14.1</b></summary>
47
58
 
48
- - 🦞 **Zalo bots acknowledge your message with a reaction**: an icon lands on your message right away, so you can see the bot picked it up while the answer is still being written. It runs in the channel layer, so it **costs no tokens**. On by default for new Zalo bots; change or disable it via `messages.ackReaction` in `openclaw.json`. Needs Zalo Connect 3.0.15+.
59
+ - 🦞 **Zalo bots react to acknowledge your message**: an icon appears on the message you just sent, so you know the bot received it while it is still writing the reply. Runs at the chat-channel layer, so it **costs no tokens**. On by default for new Zalo bots; change the icon or turn it off via `messages.ackReaction` in `openclaw.json`. Needs Zalo Connect 3.0.15+.
49
60
 
50
61
  </details>
51
62
 
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.2-0EA5E9?style=for-the-badge" alt="Version 5.16.2" /></a>
10
+ <a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.16.4-0EA5E9?style=for-the-badge" alt="Version 5.16.4" /></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,11 +37,22 @@
37
37
 
38
38
  ---
39
39
 
40
- ## 🆕 Có gì mới trong v5.16.2
40
+ ## 🆕 Có gì mới trong v5.16.4
41
+
42
+ - 🚑 **Theo kịp OpenClaw 2026.8.1 — bản setup cũ đã lỗi thời, đợt này chữa hết một lượt.** OpenClaw (nền tảng chạy bot) nâng cấp lớn và đổi nhiều "luật chơi": file cấu hình bị soát khắt khe hơn, chỗ lưu dữ liệu dời đi, vài lệnh cài đặt đổi tên. Với bản setup cũ, hệ quả là cài bot mới có thể đứng giữa chừng, bot đang chạy bỗng **im lặng không trả lời**, màn hình quản trị hiện **0 bot** dù bot vẫn sống. Từ 5.16.3–5.16.4: cài mới chạy mượt trên 2026.8.1 (đủ Docker lẫn native, Windows/macOS/Linux), **máy cũ tự được chữa khi nâng cấp** — tự dọn cấu hình lỗi thời, tự dời dữ liệu, tự gỡ file cũ từng khiến bot "câm".
43
+ - 🤖 **Bot hết chối "không thấy file"**: file khách gửi qua chat giờ được chỉ đường tận nơi cho AI — kể cả model yếu cũng tìm được.
44
+ - 🔁 **9Router (bộ định tuyến model AI) tự sống lại** sau khi máy chủ khởi động lại hay giao diện bảo trì — trước đây nó có thể chết im mà không ai hay, bot mất não.
45
+ - 🔄 **Nút "Cập nhật" trong giao diện giờ cập nhật thật**: trên máy chủ cài kiểu chuẩn, trước đây nút chỉ khởi động lại bản cũ mà vẫn báo thành công — giờ bấm là setup tự kéo bản mới về rồi tự khởi động lại.
46
+ - 🧭 **Một bản setup quản được cả máy đời cũ (2026.7) lẫn đời mới (2026.8)** — lệnh nào openclaw đổi tên thì setup tự thử tên mới rồi lùi về tên cũ, không bắt khách nâng cấp gấp.
47
+
48
+ <details>
49
+ <summary><b>Trước đó: Có gì mới trong v5.15.3</b></summary>
41
50
 
42
51
  - 🖥️ **"Đ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
52
  - 🌐 **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.
44
53
 
54
+ </details>
55
+
45
56
  <details>
46
57
  <summary><b>Trước đó: Có gì mới trong v5.14.1</b></summary>
47
58
 
@@ -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
- cfg.agents.list = Array.isArray(cfg.agents.list) ? cfg.agents.list : [];
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 --acknowledge-clawhub-risk 2>&1`;
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
- const home = join(dir, '.openclaw');
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 ocCapture(projectDir, ['plugins', 'install', spec, '--force', '--acknowledge-clawhub-risk'], { timeout: 300000 });
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
- await run('openclaw', ['daemon', 'install', '--force', '--port', String(gwPort)], { cwd: projectDir, env });
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
- if (body.projectDir && existsSync(join(resolve(String(body.projectDir)), '.openclaw', 'openclaw.json'))) {
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 ocCapture(projectDir, ['skills', 'install', slug, '--agent', agentId, '--force', '--acknowledge-clawhub-risk']);
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-clawhub-risk`;
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
- await run('openclaw', ['skills', 'install', slug, '--agent', agentId, '--force', '--acknowledge-clawhub-risk'], {
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 ocCapture(projectDir, ['plugins', 'install', ZALO_CONNECT_PLUGIN_SPEC, '--force', '--acknowledge-clawhub-risk']);
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 --acknowledge-clawhub-risk 2>&1`;
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('--acknowledge-clawhub-risk');
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 ocCapture(projectDir, installArgs);
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-', '')];
@@ -6637,7 +6869,13 @@ async function handler(req, res, rootProjectDir) {
6637
6869
  if (url.pathname === '/api/setup/update' && req.method === 'POST') {
6638
6870
  const installerDir = resolve(__dirname, '../..');
6639
6871
  const isGit = existsSync(resolve(installerDir, '.git'));
6640
- const mode = isGit ? 'git' : 'github';
6872
+ // npm -g install: the new-host template installs create-openclaw-bot globally and runs
6873
+ // the UI as a systemd user service. Without its own branch this fell into the "github"
6874
+ // relaunch path, which only restarted the OLD version (measured on vps_c-thu, 02/09/2026).
6875
+ // npx installs also live under node_modules, so exclude the _npx cache explicitly.
6876
+ const isNpx = /[\\/]_npx[\\/]/.test(installerDir);
6877
+ const isGlobalNpm = !isGit && !isNpx && /[\\/]node_modules[\\/]create-openclaw-bot[\\/]?$/.test(installerDir);
6878
+ const mode = isGit ? 'git' : (isGlobalNpm ? 'npm-global' : 'github');
6641
6879
  setImmediate(async () => {
6642
6880
  try {
6643
6881
  if (isGit) {
@@ -6651,6 +6889,11 @@ async function handler(req, res, rootProjectDir) {
6651
6889
  await run('npm', ['run', 'build'], { cwd: installerDir }).catch((e) =>
6652
6890
  sendLog(`[update-setup] build skipped: ${e.message}`));
6653
6891
  }
6892
+ } else if (isGlobalNpm) {
6893
+ // Pull the latest from npm in place, then exit — the service manager (or the
6894
+ // respawn path for a hand-run UI) relaunches onto the freshly installed dist.
6895
+ sendLog('[update-setup] Global npm install detected — npm i -g create-openclaw-bot@latest…');
6896
+ await run('npm', ['i', '-g', 'create-openclaw-bot@latest', '--no-audit', '--no-fund'], { cwd: installerDir });
6654
6897
  } else {
6655
6898
  // Ephemeral `npx github:…` install: nothing to pull in place — the relaunch
6656
6899
  // re-runs `npx github:…`, which fetches the latest from GitHub.
@@ -204,7 +204,9 @@
204
204
  }
205
205
 
206
206
  // ── commands ──────────────────────────────────────────────────────────────
207
- cfg.commands = { native: 'auto', nativeSkills: 'auto', restart: true, ownerDisplay: 'raw' };
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
- if (allow.length) plugins.bundledDiscovery = 'compat';
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;}}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){}}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));}`;
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
  ];
@@ -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!
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-openclaw-bot",
3
- "version": "5.16.2",
3
+ "version": "5.16.4",
4
4
  "description": "Interactive CLI installer for OpenClaw Bot",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {