create-openclaw-bot 5.7.0 → 5.7.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 +3 -3
- package/README.vi.md +3 -3
- package/dist/cli.js +56 -23
- package/dist/setup/shared/bot-config-gen.js +4 -5
- package/dist/setup/shared/docker-gen.js +66 -10
- package/dist/setup/shared/workspace-gen.js +35 -14
- package/dist/setup.js +206 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# 🦞 OpenClaw Setup
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.7.
|
|
6
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.7.2-0EA5E9?style=for-the-badge" alt="Version 5.7.2" /></a>
|
|
7
7
|
<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>
|
|
8
8
|
<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>
|
|
9
9
|
<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>
|
|
@@ -24,7 +24,7 @@ An interactive **CLI tool** and **Setup Wizard** to deploy your own free AI Bot
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
## 🆕 What's new in v5.7.
|
|
27
|
+
## 🆕 What's new in v5.7.2
|
|
28
28
|
|
|
29
29
|
- 🏗️ **Centralized config architecture** — All `openclaw.json`, `.env`, and `exec-approvals.json` generation now flows through a single `bot-config-gen.js` module. Both the Web Wizard and CLI share the same builder, eliminating config drift between surfaces.
|
|
30
30
|
- 🔄 **Rolling `@latest` versioning** — Installation scripts now use `openclaw@latest` instead of pinned versions, ensuring users always get the newest release without waiting for a setup update.
|
|
@@ -33,7 +33,7 @@ An interactive **CLI tool** and **Setup Wizard** to deploy your own free AI Bot
|
|
|
33
33
|
- 💬 **Standardized Zalo Personal config** — Zalo Personal (`zalouser`) channel now uses production-matching config with `groups`, `groupPolicy`, `historyLimit`, and proper `bindings`.
|
|
34
34
|
|
|
35
35
|
<details>
|
|
36
|
-
<summary><b>Previous: What's new in v5.7.
|
|
36
|
+
<summary><b>Previous: What's new in v5.7.2</b></summary>
|
|
37
37
|
|
|
38
38
|
- 🔧 **OpenAI Codex provider fix** — Updated Codex model registry to match OpenAI's current API. Removed 6 deprecated models, retained 4 active ones: `gpt-5.4`, `gpt-5.3-codex`, `gpt-5.2`, `gpt-5.4-mini`.
|
|
39
39
|
- 🔀 **9Router API mode switch** — Switched from `openai-completions` to `openai-responses` to align with OpenAI's Responses API.
|
package/README.vi.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# 🦞 OpenClaw Setup
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.7.
|
|
6
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.7.2-0EA5E9?style=for-the-badge" alt="Version 5.7.2" /></a>
|
|
7
7
|
<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>
|
|
8
8
|
<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>
|
|
9
9
|
<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>
|
|
@@ -24,7 +24,7 @@ Công cụ **CLI tương tác** và **Setup Wizard** để tự triển khai Bot
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
## 🆕 Có gì mới trong v5.7.
|
|
27
|
+
## 🆕 Có gì mới trong v5.7.2
|
|
28
28
|
|
|
29
29
|
- 🏗️ **Kiến trúc config tập trung** — Toàn bộ logic tạo `openclaw.json`, `.env`, và `exec-approvals.json` giờ chạy qua module duy nhất `bot-config-gen.js`. Cả Web Wizard và CLI dùng chung cùng một builder, loại bỏ sai lệch config giữa 2 bề mặt.
|
|
30
30
|
- 🔄 **Phiên bản rolling `@latest`** — Script cài đặt giờ dùng `openclaw@latest` thay vì version cố định, đảm bảo người dùng luôn nhận bản mới nhất.
|
|
@@ -33,7 +33,7 @@ Công cụ **CLI tương tác** và **Setup Wizard** để tự triển khai Bot
|
|
|
33
33
|
- 💬 **Chuẩn hóa config Zalo Personal** — Kênh Zalo Personal (`zalouser`) giờ dùng config khớp production với `groups`, `groupPolicy`, `historyLimit`, và `bindings` đúng.
|
|
34
34
|
|
|
35
35
|
<details>
|
|
36
|
-
<summary><b>Trước đó: Có gì mới ở v5.7.
|
|
36
|
+
<summary><b>Trước đó: Có gì mới ở v5.7.2</b></summary>
|
|
37
37
|
|
|
38
38
|
- 🔧 **Sửa provider OpenAI Codex** — Cập nhật registry model Codex phù hợp API hiện tại của OpenAI. Loại 6 model đã dừng, giữ 4 model đang hoạt động: `gpt-5.4`, `gpt-5.3-codex`, `gpt-5.2`, `gpt-5.4-mini`.
|
|
39
39
|
- 🔀 **Chuyển chế độ API 9Router** — Đổi từ `openai-completions` sang `openai-responses` cho khớp với Responses API mới của OpenAI.
|
package/dist/cli.js
CHANGED
|
@@ -613,26 +613,54 @@ function printNativeDashboardAccessInfo({ isVi, providerKey, projectDir, gateway
|
|
|
613
613
|
|
|
614
614
|
function printZaloPersonalLoginInfo({ isVi, deployMode, projectDir }) {
|
|
615
615
|
const nativeCmd = 'openclaw channels login --channel zalouser --verbose';
|
|
616
|
-
const dockerCmd = 'docker
|
|
616
|
+
const dockerCmd = 'docker exec -it openclaw-bot openclaw channels login --channel zalouser --verbose';
|
|
617
617
|
const cmd = deployMode === 'native' ? nativeCmd : dockerCmd;
|
|
618
618
|
const qrPath = deployMode === 'native'
|
|
619
619
|
? path.join(os.tmpdir(), 'openclaw', 'openclaw-zalouser-qr-default.png')
|
|
620
620
|
: '/tmp/openclaw/openclaw-zalouser-qr-default.png';
|
|
621
|
-
const projectQrPath = path.join(projectDir, 'zalo-login-qr.png');
|
|
622
621
|
const copyCmd = deployMode === 'native'
|
|
623
622
|
? (process.platform === 'win32'
|
|
624
|
-
? `Copy-Item "${qrPath}" "${
|
|
625
|
-
: `cp "${qrPath}" "${
|
|
626
|
-
: `docker
|
|
623
|
+
? `Copy-Item "${qrPath}" "${path.join(projectDir, 'zalo-login-qr.png')}"`
|
|
624
|
+
: `cp "${qrPath}" "${path.join(projectDir, 'zalo-login-qr.png')}"`)
|
|
625
|
+
: `docker cp openclaw-bot:${qrPath} ./zalo-qr.png`;
|
|
627
626
|
|
|
628
627
|
console.log(chalk.yellow(`\n📱 ${isVi ? 'Đăng nhập Zalo Personal (1 lần):' : 'Zalo Personal login (one time):'}`));
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
628
|
+
if (deployMode === 'docker') {
|
|
629
|
+
console.log(chalk.white(isVi
|
|
630
|
+
? ` 1. cd ${projectDir}/docker/openclaw`
|
|
631
|
+
: ` 1. cd ${projectDir}/docker/openclaw`));
|
|
632
|
+
console.log(chalk.white(isVi
|
|
633
|
+
? ` 2. ${cmd}`
|
|
634
|
+
: ` 2. ${cmd}`));
|
|
635
|
+
console.log(chalk.white(isVi
|
|
636
|
+
? ` 3. Tìm file QR trong container: ${qrPath}`
|
|
637
|
+
: ` 3. Find QR image in container: ${qrPath}`));
|
|
638
|
+
console.log(chalk.gray(isVi
|
|
639
|
+
? ` → Mở Docker Desktop > container openclaw-bot > tab Files > tìm file trên`
|
|
640
|
+
: ` → Open Docker Desktop > container openclaw-bot > Files tab > find file above`));
|
|
641
|
+
console.log(chalk.gray(isVi
|
|
642
|
+
? ` → Hoặc chạy: ${copyCmd}`
|
|
643
|
+
: ` → Or run: ${copyCmd}`));
|
|
644
|
+
console.log(chalk.white(isVi
|
|
645
|
+
? ' 4. Mở app Zalo > Quét QR > quét mã trong file QR'
|
|
646
|
+
: ' 4. Open Zalo app > Scan QR > scan the QR image'));
|
|
647
|
+
console.log(chalk.white(isVi
|
|
648
|
+
? ' 5. Đợi thấy "Login successful" trong terminal'
|
|
649
|
+
: ' 5. Wait for "Login successful" in terminal'));
|
|
650
|
+
console.log(chalk.white(isVi
|
|
651
|
+
? ' 6. docker compose restart'
|
|
652
|
+
: ' 6. docker compose restart'));
|
|
653
|
+
} else {
|
|
654
|
+
console.log(chalk.white(` cd ${projectDir} ${process.platform === 'win32' ? ';' : '&&'} ${cmd}`));
|
|
655
|
+
console.log(chalk.gray(isVi
|
|
656
|
+
? ` → File QR sẽ tạo tại: ${qrPath}`
|
|
657
|
+
: ` → QR file will be created at: ${qrPath}`));
|
|
658
|
+
if (process.platform === 'win32') {
|
|
659
|
+
console.log(chalk.gray(isVi
|
|
660
|
+
? ` → Copy QR ra project: ${copyCmd}`
|
|
661
|
+
: ` → Copy QR to project: ${copyCmd}`));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
636
664
|
}
|
|
637
665
|
|
|
638
666
|
async function waitForFile(filePath, timeoutMs = 15000, intervalMs = 500) {
|
|
@@ -1831,6 +1859,7 @@ async function writeWorkspaceFiles({
|
|
|
1831
1859
|
includeBrowserTool: isDesktop,
|
|
1832
1860
|
teamRosterFormatted,
|
|
1833
1861
|
hasScheduler: selectedSkills.includes('scheduler'),
|
|
1862
|
+
hasZaloMod: channelKey === 'zalo-personal',
|
|
1834
1863
|
});
|
|
1835
1864
|
|
|
1836
1865
|
await fs.ensureDir(workspaceDir);
|
|
@@ -2128,15 +2157,18 @@ async function main() {
|
|
|
2128
2157
|
|
|
2129
2158
|
|
|
2130
2159
|
// ── Docker artifacts: Dockerfile + docker-compose via shared buildDockerArtifacts() ──────
|
|
2131
|
-
const skillSlugs = SKILLS
|
|
2132
|
-
.filter(s => selectedSkills.includes(s.value) && s.slug)
|
|
2133
|
-
.map(s => s.slug);
|
|
2134
|
-
const skillInstallCmd = skillSlugs.length > 0
|
|
2135
|
-
? skillSlugs.map(s => `
|
|
2136
|
-
: '';
|
|
2160
|
+
const skillSlugs = SKILLS
|
|
2161
|
+
.filter(s => selectedSkills.includes(s.value) && s.slug)
|
|
2162
|
+
.map(s => s.slug);
|
|
2163
|
+
const skillInstallCmd = skillSlugs.length > 0
|
|
2164
|
+
? skillSlugs.map(s => `ensure_skill ${s}`).join('\n')
|
|
2165
|
+
: '';
|
|
2137
2166
|
const relayInstallCmd = (isMultiBot && channelKey === 'telegram')
|
|
2138
2167
|
? buildRelayPluginInstallCommand('openclaw')
|
|
2139
2168
|
: '';
|
|
2169
|
+
const zaloModInstallCmd = hasZaloPersonal(channelKey)
|
|
2170
|
+
? 'ensure_plugin zalo-mod openclaw-zalo-mod'
|
|
2171
|
+
: '';
|
|
2140
2172
|
const socatBridge = hasBrowserDesktop ? 'socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 &' : '';
|
|
2141
2173
|
const deviceApproveLoop = 'while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done >/dev/null 2>&1 &';
|
|
2142
2174
|
|
|
@@ -2151,11 +2183,12 @@ async function main() {
|
|
|
2151
2183
|
hasBrowser: hasBrowserDesktop || hasBrowserServer,
|
|
2152
2184
|
selectedModel: modelsPrimary,
|
|
2153
2185
|
agentId,
|
|
2154
|
-
runtimeCommandParts: [
|
|
2155
|
-
skillInstallCmd
|
|
2156
|
-
relayInstallCmd
|
|
2157
|
-
|
|
2158
|
-
|
|
2186
|
+
runtimeCommandParts: [
|
|
2187
|
+
skillInstallCmd,
|
|
2188
|
+
relayInstallCmd,
|
|
2189
|
+
zaloModInstallCmd,
|
|
2190
|
+
socatBridge,
|
|
2191
|
+
deviceApproveLoop,
|
|
2159
2192
|
].filter(Boolean),
|
|
2160
2193
|
volumeMount: '../../.openclaw:/root/project/.openclaw',
|
|
2161
2194
|
singleComposeName: `oc-${agentId}`,
|
|
@@ -295,11 +295,11 @@
|
|
|
295
295
|
},
|
|
296
296
|
};
|
|
297
297
|
|
|
298
|
-
const allow = [];
|
|
298
|
+
const allow = ['memory-core'];
|
|
299
299
|
|
|
300
300
|
// zalo-mod plugin for Zalo Personal
|
|
301
301
|
if (isZaloPersonal(channelKey)) {
|
|
302
|
-
allow.push('zalo-mod');
|
|
302
|
+
allow.push('zalo-mod', 'zalouser');
|
|
303
303
|
entries['zalo-mod'] = {
|
|
304
304
|
enabled: true,
|
|
305
305
|
config: {
|
|
@@ -311,12 +311,11 @@
|
|
|
311
311
|
spamWindowSeconds: 300,
|
|
312
312
|
},
|
|
313
313
|
};
|
|
314
|
+
entries.zalouser = { enabled: true };
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
const plugins = { entries };
|
|
317
|
-
|
|
318
|
-
plugins.allow = allow;
|
|
319
|
-
}
|
|
318
|
+
plugins.allow = allow;
|
|
320
319
|
|
|
321
320
|
return { plugins };
|
|
322
321
|
}
|
|
@@ -152,6 +152,7 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
152
152
|
selectedModel,
|
|
153
153
|
agentId,
|
|
154
154
|
allSkills = [],
|
|
155
|
+
dockerfilePlugins = [],
|
|
155
156
|
dockerfileSkillInstallMode = 'none',
|
|
156
157
|
runtimeCommandParts = [],
|
|
157
158
|
volumeMount = '../..:/root/project',
|
|
@@ -185,16 +186,59 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
185
186
|
const skillLines = dockerfileSkillInstallMode === 'build' && allSkills.length > 0
|
|
186
187
|
? `\n# Install skills (ClawHub)\n${allSkills.map((skill) => `RUN openclaw skills install ${skill} || echo "Warning: Failed to install ${skill} due to rate limits."`).join('\n')}\n`
|
|
187
188
|
: '';
|
|
189
|
+
const pluginLines = dockerfilePlugins.length > 0
|
|
190
|
+
? `\n# Install plugins (ClawHub)\n${dockerfilePlugins.map((p) => `RUN openclaw plugins install ${p} || echo "Warning: Failed to install plugin ${p}"`).join('\n')}\n`
|
|
191
|
+
: '';
|
|
188
192
|
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);}"`;
|
|
189
193
|
|
|
190
|
-
// Dynamic runtime configuration
|
|
191
|
-
|
|
192
|
-
const
|
|
194
|
+
// Dynamic runtime configuration: backup config before any first-run install, restore after.
|
|
195
|
+
// Missing plugin install may touch openclaw.json, so preserve critical fields.
|
|
196
|
+
const backupConfigScript = `const fs=require('fs'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json'),b=p.replace('openclaw.json','.openclaw-config-backup.json');if(fs.existsSync(p)){fs.copyFileSync(p,b);}`;
|
|
197
|
+
const backupConfigB64 = encodeBase64Utf8(backupConfigScript);
|
|
198
|
+
|
|
199
|
+
const restoreConfigScript = `const fs=require('fs'),os=require('os'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json'),b=p.replace('openclaw.json','.openclaw-config-backup.json');if(fs.existsSync(p)&&fs.existsSync(b)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const bk=JSON.parse(fs.readFileSync(b,'utf8'));const keep=['agents','channels','bindings','commands','models','browser','skills'];for(const k of keep){if(bk[k]&&!c[k])c[k]=bk[k];}const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add('http://'+entry.address+':18791');}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',mode:c.gateway?.mode||bk.gateway?.mode||'local',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a).filter(Boolean)})});fs.writeFileSync(p,JSON.stringify(c,null,2));fs.unlinkSync(b);}`;
|
|
200
|
+
const restoreConfigB64 = encodeBase64Utf8(restoreConfigScript);
|
|
193
201
|
|
|
194
|
-
const runtimeParts = runtimeCommandParts.filter(Boolean);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
202
|
+
const runtimeParts = runtimeCommandParts.filter(Boolean);
|
|
203
|
+
const runtimePrelude = [
|
|
204
|
+
'export OPENCLAW_HOME="${OPENCLAW_HOME:-$PWD/.openclaw}"',
|
|
205
|
+
'export OPENCLAW_STATE_DIR="${OPENCLAW_STATE_DIR:-$OPENCLAW_HOME}"',
|
|
206
|
+
'mkdir -p "$OPENCLAW_HOME" "$OPENCLAW_STATE_DIR"',
|
|
207
|
+
'if [ "$OPENCLAW_STATE_DIR" != "$OPENCLAW_HOME" ]; then',
|
|
208
|
+
' for path in "$OPENCLAW_HOME"/*; do',
|
|
209
|
+
' [ -e "$path" ] || continue',
|
|
210
|
+
' name="$(basename "$path")"',
|
|
211
|
+
' [ "$name" = "plugin-runtime-deps" ] && continue',
|
|
212
|
+
' [ "$name" = "logs" ] && continue',
|
|
213
|
+
' [ -e "$OPENCLAW_STATE_DIR/$name" ] || ln -s "$path" "$OPENCLAW_STATE_DIR/$name"',
|
|
214
|
+
' done',
|
|
215
|
+
'fi',
|
|
216
|
+
'ensure_plugin() {',
|
|
217
|
+
' id="$1"',
|
|
218
|
+
' spec="$2"',
|
|
219
|
+
' if [ -d "$OPENCLAW_HOME/extensions/$id" ]; then',
|
|
220
|
+
' echo "[entrypoint] plugin $id already installed"',
|
|
221
|
+
' return 0',
|
|
222
|
+
' fi',
|
|
223
|
+
' echo "[entrypoint] plugin $id missing; installing $spec"',
|
|
224
|
+
' openclaw plugins install "$spec" 2>/dev/null || echo "[entrypoint] warning: failed to install plugin $spec"',
|
|
225
|
+
'}',
|
|
226
|
+
'ensure_skill() {',
|
|
227
|
+
' id="$1"',
|
|
228
|
+
' if find "$OPENCLAW_HOME" -maxdepth 4 -type d -path "*/skills/$id" -print -quit 2>/dev/null | grep -q .; then',
|
|
229
|
+
' echo "[entrypoint] skill $id already installed"',
|
|
230
|
+
' return 0',
|
|
231
|
+
' fi',
|
|
232
|
+
' echo "[entrypoint] skill $id missing; installing"',
|
|
233
|
+
' openclaw skills install "$id" 2>/dev/null || echo "[entrypoint] warning: failed to install skill $id"',
|
|
234
|
+
'}',
|
|
235
|
+
'echo "[entrypoint] ensuring runtime assets, then starting gateway"',
|
|
236
|
+
];
|
|
237
|
+
runtimeParts.unshift(...runtimePrelude);
|
|
238
|
+
// Backup config BEFORE plugin installs (runtimeCommandParts may contain plugin install commands)
|
|
239
|
+
runtimeParts.unshift(`node -e 'eval(Buffer.from("${backupConfigB64}","base64").toString())'`);
|
|
240
|
+
// Restore config AFTER plugin installs (which may clobber openclaw.json)
|
|
241
|
+
runtimeParts.push(`node -e 'eval(Buffer.from("${restoreConfigB64}","base64").toString())'`);
|
|
198
242
|
if (hasBrowser) {
|
|
199
243
|
runtimeParts.push('socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 &');
|
|
200
244
|
runtimeParts.push('Xvfb :99 -screen 0 1280x720x24 > /dev/null 2>&1 & DISPLAY=:99 openclaw gateway run');
|
|
@@ -209,7 +253,7 @@ RUN apt-get update && apt-get install -y git curl${browserAptExtra} && rm -rf /v
|
|
|
209
253
|
${browserInstallLines}
|
|
210
254
|
ARG OPENCLAW_VER="${openClawNpmSpec}"
|
|
211
255
|
ARG CACHE_BUST=""
|
|
212
|
-
RUN npm install -g ${openClawNpmSpec} ${openClawRuntimePackages}${skillLines}
|
|
256
|
+
RUN npm install -g ${openClawNpmSpec} ${openClawRuntimePackages}${skillLines}${pluginLines}
|
|
213
257
|
${patchLine}
|
|
214
258
|
RUN node -e "require('fs').writeFileSync('/usr/local/bin/openclaw-entrypoint.sh', Buffer.from('${runtimeScriptB64}','base64').toString())" && chmod +x /usr/local/bin/openclaw-entrypoint.sh
|
|
215
259
|
WORKDIR /root/project
|
|
@@ -225,7 +269,7 @@ CMD ["/bin/sh", "/usr/local/bin/openclaw-entrypoint.sh"]`;
|
|
|
225
269
|
const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64, patchScriptBase64);
|
|
226
270
|
const extraHostsBlock = ` extra_hosts:\n - "host.docker.internal:host-gateway"`;
|
|
227
271
|
|
|
228
|
-
const appEnvironmentBlock = ' environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/
|
|
272
|
+
const appEnvironmentBlock = ' environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/var/lib/openclaw-state\n';
|
|
229
273
|
|
|
230
274
|
let compose;
|
|
231
275
|
if (isMultiBot) {
|
|
@@ -418,7 +462,19 @@ ${appEnvironmentBlock}${plainSingleExtraHosts ? `${extraHostsBlock}\n` : ''}
|
|
|
418
462
|
- "18791:18791"`;
|
|
419
463
|
}
|
|
420
464
|
|
|
421
|
-
|
|
465
|
+
compose = compose.replaceAll(
|
|
466
|
+
` - ${volumeMount}`,
|
|
467
|
+
` - ${volumeMount}\n - openclaw-state:/var/lib/openclaw-state`
|
|
468
|
+
);
|
|
469
|
+
if (compose.includes('\nvolumes:\n')) {
|
|
470
|
+
if (!compose.includes(' openclaw-state:')) {
|
|
471
|
+
compose = compose.replace('\nvolumes:\n', '\nvolumes:\n openclaw-state:\n');
|
|
472
|
+
}
|
|
473
|
+
} else {
|
|
474
|
+
compose += '\n\nvolumes:\n openclaw-state:';
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return {
|
|
422
478
|
dockerfile,
|
|
423
479
|
compose,
|
|
424
480
|
syncScript,
|
|
@@ -16,21 +16,34 @@ const workspaceRoot = /** @type {OpenClawWorkspaceRoot} */ (
|
|
|
16
16
|
return `# Identity\n\n- **Name:** ${name}\n- **Role:** ${desc}${emoji ? `\n- **Emoji:** ${emoji}` : ''}\n\n---\n\nI am **${name}**. When asked my name, I answer: _\"I'm ${name}\"_.${richAiNote ? "\nI don't pretend to be human — I'm an AI, and I'm proud of it." : ''}`;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function buildZaloSoulSection(isVi, botName) {
|
|
20
|
+
const name = botName || 'Bot';
|
|
21
|
+
if (isVi) {
|
|
22
|
+
return `\n\n**RULE — Zalo Group: Phản hồi theo chế độ Silent Mode:**\nKhi nhận tin từ \`channel: zalouser\` và \`group_id\` có giá trị:\n\n- Nếu tin nhắn chứa \`@${name}\` → **LUÔN reply** (bất kể silent mode).\n- Nếu tin nhắn bắt đầu bằng \`/\` (slash command) → KHÔNG reply, plugin đã xử lý rồi.\n- Tin thường trong group (không mention, không slash):\n - Nếu **Silent Mode BẬT** → tin này KHÔNG đến được bot (plugin đã chặn).\n - Nếu **Silent Mode TẮT** → tin này ĐẾN ĐƯỢC bot → **reply bình thường** như DM.\n- DM (không có group_id) → reply bình thường.`;
|
|
23
|
+
}
|
|
24
|
+
return `\n\n**RULE — Zalo Group: Reply based on Silent Mode:**\nWhen receiving messages from \`channel: zalouser\` with a \`group_id\`:\n\n- If the message contains \`@${name}\` → **ALWAYS reply** (regardless of silent mode).\n- If the message starts with \`/\` (slash command) → DO NOT reply, the plugin already handled it.\n- Regular group messages (no mention, no slash):\n - If **Silent Mode is ON** → this message does NOT reach the bot (plugin blocks it).\n - If **Silent Mode is OFF** → this message DOES reach the bot → **reply normally** like DM.\n- DM (no group_id) → reply normally.`;
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
function buildSoulDoc(options = {}) {
|
|
20
|
-
const { isVi = true, persona = '', variant = 'wizard' } = options;
|
|
28
|
+
const { isVi = true, persona = '', variant = 'wizard', hasZaloMod = false, botName = 'Bot' } = options;
|
|
29
|
+
let doc;
|
|
21
30
|
if (variant === 'cli-simple') {
|
|
22
|
-
|
|
31
|
+
doc = isVi
|
|
23
32
|
? `# Tính cách\n\n${persona || 'Thân thiện, rõ ràng, giải quyết việc thẳng vào mục tiêu.'}\n`
|
|
24
33
|
: `# Soul\n\n${persona || 'Friendly, clear, and outcome-focused.'}\n`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return isVi
|
|
34
|
+
} else if (variant === 'cli-rich') {
|
|
35
|
+
doc = isVi
|
|
28
36
|
? `# Tính cách\n\n**Hữu ích thật sự.** Bỏ qua câu nệ — cứ giúp thẳng.\n**Có cá tính.** Trợ lý không có cá tính thì chỉ là công cụ.\n\n## Phong cách\n- Tự nhiên, gần gũi như bạn bè\n- Trực tiếp, không parrot câu hỏi.${persona ? `\n\n## Custom Rules\n${persona}` : ''}`
|
|
29
37
|
: `# Soul\n\n**Be genuinely helpful.** Skip filler and help directly.\n**Have personality.** An assistant without personality is just a tool.\n\n## Style\n- Natural and approachable\n- Direct, do not parrot the prompt.${persona ? `\n\n## Custom Rules\n${persona}` : ''}`;
|
|
38
|
+
} else {
|
|
39
|
+
doc = isVi
|
|
40
|
+
? `# Tính cách\n\n**Hữu ích thật sự.** Bỏ qua câu nệ, cứ giúp thẳng.\n**Có cá tính.** Trợ lý không có cá tính thì chỉ là công cụ.\n\n## Phong cách\n- Tự nhiên, gần gũi\n- Trực tiếp, ngắn gọn${persona ? `\n\n## Custom Rules\n${persona}` : ''}`
|
|
41
|
+
: `# Soul\n\n**Be genuinely helpful.** Skip filler and just help.\n**Have personality.** An assistant with no personality is just a tool.\n\n## Style\n- Natural and concise\n- Direct and practical${persona ? `\n\n## Custom Rules\n${persona}` : ''}`;
|
|
30
42
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
if (hasZaloMod) {
|
|
44
|
+
doc += buildZaloSoulSection(isVi, botName);
|
|
45
|
+
}
|
|
46
|
+
return doc;
|
|
34
47
|
}
|
|
35
48
|
|
|
36
49
|
function buildTeamDoc(options = {}) {
|
|
@@ -305,6 +318,7 @@ const CDP_URL = 'http://127.0.0.1:9222';
|
|
|
305
318
|
agentWorkspaceDir = 'workspace',
|
|
306
319
|
hasBrowser = false,
|
|
307
320
|
hasScheduler = false,
|
|
321
|
+
hasZaloMod = false,
|
|
308
322
|
browserDocVariant = '',
|
|
309
323
|
} = options;
|
|
310
324
|
|
|
@@ -332,19 +346,25 @@ const CDP_URL = 'http://127.0.0.1:9222';
|
|
|
332
346
|
: `\n\n## \u23F0 Cron / Scheduled Tasks\n- OpenClaw natively supports system tools for Cron Jobs.\n- When the user asks to schedule tasks or reminders, use the built-in tools automatically. Do NOT ask users to run crontab or Task Scheduler manually on the host.\n- When operating cron/scheduler tools, do **not** put \`current\` into the Session directory.\n- Skip internal doc lookups such as \`cron-jobs.mdx\`; rely on the available tools and complete the scheduling task directly.`)
|
|
333
347
|
: '';
|
|
334
348
|
|
|
349
|
+
const zaloModSection = hasZaloMod
|
|
350
|
+
? (isVi
|
|
351
|
+
? `\n\n## 💬 Zalo Group — Slash Commands (xử lý bởi plugin)\n\nPlugin \`zalo-mod\` tự động xử lý các slash command sau trong group. Bot KHÔNG cần reply cho chúng:\n\n| Command | Mô tả |\n|---------|-------|\n| \`/rules status\` | Xem cấu hình bot |\n| \`/rules silent-on/off\` | Bật/tắt silent mode |\n| \`/rules welcome-on/off\` | Bật/tắt welcome message |\n| \`/rules tracking-on/off\` | Bật/tắt ghi log chat |\n| \`/noi-quy\` | Hiện nội quy group |\n| \`/menu\` | Danh sách lệnh |\n| \`/groupid\` | Scan và cập nhật config |\n| \`/report\` | Báo cáo hoạt động group |\n\n### Zalo Sticker & Media\n- Sticker Zalo gửi dạng JSON → plugin tự convert thành \`[Sticker]\`\n- Ảnh/video/file trong group: zalouser channel chỉ forward text, media bị drop`
|
|
352
|
+
: `\n\n## 💬 Zalo Group — Slash Commands (handled by plugin)\n\nThe \`zalo-mod\` plugin automatically handles these slash commands in group. Bot does NOT need to reply:\n\n| Command | Description |\n|---------|-------------|\n| \`/rules status\` | View bot config |\n| \`/rules silent-on/off\` | Toggle silent mode |\n| \`/rules welcome-on/off\` | Toggle welcome message |\n| \`/rules tracking-on/off\` | Toggle chat logging |\n| \`/noi-quy\` | Show group rules |\n| \`/menu\` | List commands |\n| \`/groupid\` | Scan and update config |\n| \`/report\` | Group activity report |\n\n### Zalo Sticker & Media\n- Zalo stickers arrive as JSON → plugin auto-converts to \`[Sticker]\`\n- Images/videos/files in groups: zalouser channel only forwards text, media is dropped`)
|
|
353
|
+
: '';
|
|
354
|
+
|
|
335
355
|
const dmOverride = isVi
|
|
336
356
|
? `\n\n## Override: DM và Telegram reaction\n- Nếu metadata không nói rõ đây là group/supergroup, mặc định xem là chat riêng/DM và trả lời bình thường.\n- Quy tắc im lặng khi không ai được gọi chỉ áp dụng cho group chat, không áp dụng cho DM/chat riêng.\n- Trước MỖI phản hồi Telegram nhìn thấy bởi user, nếu turn hiện tại có tool/action reaction native thì ưu tiên dùng nó để thả \`👍\` trước, trừ khi \`👍\` đã có sẵn.\n- Nếu turn hiện tại không expose tool reaction hoặc chat đích không hỗ trợ reaction thật, cứ trả lời đúng vai trò và không được nói là mình đã thả reaction.`
|
|
337
357
|
: `\n\n## Override: DM and Telegram reaction\n- If metadata does not clearly say this is a group/supergroup, treat it as a private DM and reply normally.\n- The stay-silent rule for unaddressed messages applies only to group chats, never to DMs.\n- Before EVERY user-visible Telegram reply, if the current turn exposes a native reaction tool/action, use it to add \`👍\` first unless \`👍\` is already present.\n- If the reaction tool is unavailable or the target chat does not support real reactions, just reply in-character and do not claim that you reacted.`;
|
|
338
358
|
|
|
339
359
|
if (variant === 'relay') {
|
|
340
360
|
return isVi
|
|
341
|
-
? `# Hướng dẫn dùng tool\n\n## Tools có sẵn\n${skillsSection}\n\n## Quy tắc chung\n- Tóm tắt kết quả tool thay vì dump raw output.\n- Mọi bot đều có quyền sử dụng tất cả tool (scheduler, browser, exec). Vai trò (dev/marketing/...) chỉ là persona, KHÔNG giới hạn quyền dùng tool.\n- Workspace của bạn là \`.openclaw/${agentWorkspaceDir}/\`.${browserRef}${telegramSection}${cronSection}${dmOverride}\n`
|
|
342
|
-
: `# Tool Usage Guide\n\n## Available Tools\n${skillsSection}\n\n## General Rules\n- Summarize tool output instead of dumping raw output.\n- All bots have equal access to all tools (scheduler, browser, exec). Roles (dev/marketing/...) are persona only, NOT tool permissions.\n- Your workspace is \`.openclaw/${agentWorkspaceDir}/\`.${browserRef}${telegramSection}${cronSection}${dmOverride}\n`;
|
|
361
|
+
? `# Hướng dẫn dùng tool\n\n## Tools có sẵn\n${skillsSection}\n\n## Quy tắc chung\n- Tóm tắt kết quả tool thay vì dump raw output.\n- Mọi bot đều có quyền sử dụng tất cả tool (scheduler, browser, exec). Vai trò (dev/marketing/...) chỉ là persona, KHÔNG giới hạn quyền dùng tool.\n- Workspace của bạn là \`.openclaw/${agentWorkspaceDir}/\`.${browserRef}${telegramSection}${cronSection}${zaloModSection}${dmOverride}\n`
|
|
362
|
+
: `# Tool Usage Guide\n\n## Available Tools\n${skillsSection}\n\n## General Rules\n- Summarize tool output instead of dumping raw output.\n- All bots have equal access to all tools (scheduler, browser, exec). Roles (dev/marketing/...) are persona only, NOT tool permissions.\n- Your workspace is \`.openclaw/${agentWorkspaceDir}/\`.${browserRef}${telegramSection}${cronSection}${zaloModSection}${dmOverride}\n`;
|
|
343
363
|
}
|
|
344
364
|
|
|
345
365
|
return isVi
|
|
346
|
-
? `# Hướng dẫn sử dụng Tools\n\n## Danh sách skills đã cài\n${skillsSection}\n\n## Nguyên tắc chung\n- Ưu tiên dùng tool/skill phù hợp thay vì tự suy đoán\n- Nếu tool trả về lỗi — thử lại 1 lần, sau đó báo user\n- Không chạy tool liên tục mà không có mục đích rõ ràng\n- Luôn tóm tắt kết quả tool cho user thay vì dump raw output${browserRef}\n\n## Quy ước\n- Web Search: chỉ dùng khi cần thông tin realtime hoặc user yêu cầu\n- Browser: chỉ mở trang khi user yêu cầu cụ thể\n- Memory: tự ghi nhớ thông tin tự nhiên, không cần user nhắc${cronSection}\n\n## \uD83D\uDCC1 File & Workspace\n- Bot có thể đọc/ghi file trong thư mục workspace: \`${workspacePath}\`\n- Dùng để lưu notes, scripts, cấu hình tạm\n\n## \u26A0\uFE0F Tool Error Handling\n- Retry tối đa 2 lần nếu tool lỗi network\n- Nếu vẫn lỗi: báo user kèm mô tả lỗi cụ thể và gợi ý workaround${dmOverride}\n`
|
|
347
|
-
: `# Tool Usage Guide\n\n## Installed Skills\n${skillsSection}\n\n## General Principles\n- Prefer using the right tool/skill over guessing\n- If a tool returns an error — retry once, then report to user\n- Don't run tools repeatedly without a clear purpose\n- Always summarize tool output for user instead of dumping raw data${browserRef}\n\n## Conventions\n- Web Search: only use when needing real-time info or user explicitly asks\n- Browser: only open pages when user specifically requests\n- Memory: proactively remember important info without user prompting${cronSection}\n\n## \uD83D\uDCC1 File & Workspace\n- Bot can read/write files in workspace: \`${workspacePath}\`\n\n## \u26A0\uFE0F Tool Error Handling\n- Retry up to 2 times on network errors\n- If still failing: report to user with specific error description and workaround${dmOverride}\n`;
|
|
366
|
+
? `# Hướng dẫn sử dụng Tools\n\n## Danh sách skills đã cài\n${skillsSection}\n\n## Nguyên tắc chung\n- Ưu tiên dùng tool/skill phù hợp thay vì tự suy đoán\n- Nếu tool trả về lỗi — thử lại 1 lần, sau đó báo user\n- Không chạy tool liên tục mà không có mục đích rõ ràng\n- Luôn tóm tắt kết quả tool cho user thay vì dump raw output${browserRef}\n\n## Quy ước\n- Web Search: chỉ dùng khi cần thông tin realtime hoặc user yêu cầu\n- Browser: chỉ mở trang khi user yêu cầu cụ thể\n- Memory: tự ghi nhớ thông tin tự nhiên, không cần user nhắc${cronSection}${zaloModSection}\n\n## \uD83D\uDCC1 File & Workspace\n- Bot có thể đọc/ghi file trong thư mục workspace: \`${workspacePath}\`\n- Dùng để lưu notes, scripts, cấu hình tạm\n\n## \u26A0\uFE0F Tool Error Handling\n- Retry tối đa 2 lần nếu tool lỗi network\n- Nếu vẫn lỗi: báo user kèm mô tả lỗi cụ thể và gợi ý workaround${dmOverride}\n`
|
|
367
|
+
: `# Tool Usage Guide\n\n## Installed Skills\n${skillsSection}\n\n## General Principles\n- Prefer using the right tool/skill over guessing\n- If a tool returns an error — retry once, then report to user\n- Don't run tools repeatedly without a clear purpose\n- Always summarize tool output for user instead of dumping raw data${browserRef}\n\n## Conventions\n- Web Search: only use when needing real-time info or user explicitly asks\n- Browser: only open pages when user specifically requests\n- Memory: proactively remember important info without user prompting${cronSection}${zaloModSection}\n\n## \uD83D\uDCC1 File & Workspace\n- Bot can read/write files in workspace: \`${workspacePath}\`\n\n## \u26A0\uFE0F Tool Error Handling\n- Retry up to 2 times on network errors\n- If still failing: report to user with specific error description and workaround${dmOverride}\n`;
|
|
348
368
|
}
|
|
349
369
|
function buildTeamsDoc(options = {}) {
|
|
350
370
|
const {
|
|
@@ -418,20 +438,21 @@ const CDP_URL = 'http://127.0.0.1:9222';
|
|
|
418
438
|
teamRosterFormatted = '',
|
|
419
439
|
emoji = '',
|
|
420
440
|
hasScheduler = false,
|
|
441
|
+
hasZaloMod = false,
|
|
421
442
|
} = opts;
|
|
422
443
|
|
|
423
444
|
const isMultiBot = variant === 'relay';
|
|
424
445
|
|
|
425
446
|
const files = {
|
|
426
447
|
'IDENTITY.md': buildIdentityDoc({ isVi, name: botName, desc: botDesc, emoji }),
|
|
427
|
-
'SOUL.md': buildSoulDoc({ isVi, persona, variant: soulVariant }),
|
|
448
|
+
'SOUL.md': buildSoulDoc({ isVi, persona, variant: soulVariant, hasZaloMod, botName }),
|
|
428
449
|
'AGENTS.md': buildAgentsDoc({
|
|
429
450
|
isVi, botName, botDesc, ownAliases, otherAgents, workspacePath,
|
|
430
451
|
variant, includeSecurity: true, replyToDirectMessages: true,
|
|
431
452
|
}),
|
|
432
453
|
'USER.md': buildUserDoc({ isVi, userInfo, variant: userVariant || (isMultiBot ? 'cli-multi' : 'wizard') }),
|
|
433
454
|
'TOOLS.md': buildToolsDoc({
|
|
434
|
-
isVi, skillListStr, workspacePath, variant, agentWorkspaceDir, hasBrowser, hasScheduler, browserDocVariant,
|
|
455
|
+
isVi, skillListStr, workspacePath, variant, agentWorkspaceDir, hasBrowser, hasScheduler, hasZaloMod, browserDocVariant,
|
|
435
456
|
}),
|
|
436
457
|
'MEMORY.md': buildMemoryDoc({ isVi, variant: memoryVariant }),
|
|
437
458
|
'HEARTBEAT.md': buildHeartbeatDoc({ isVi }),
|
package/dist/setup.js
CHANGED
|
@@ -952,21 +952,34 @@ const workspaceRoot = /** @type {OpenClawWorkspaceRoot} */ (
|
|
|
952
952
|
return `# Identity\n\n- **Name:** ${name}\n- **Role:** ${desc}${emoji ? `\n- **Emoji:** ${emoji}` : ''}\n\n---\n\nI am **${name}**. When asked my name, I answer: _\"I'm ${name}\"_.${richAiNote ? "\nI don't pretend to be human — I'm an AI, and I'm proud of it." : ''}`;
|
|
953
953
|
}
|
|
954
954
|
|
|
955
|
+
function buildZaloSoulSection(isVi, botName) {
|
|
956
|
+
const name = botName || 'Bot';
|
|
957
|
+
if (isVi) {
|
|
958
|
+
return `\n\n**RULE — Zalo Group: Phản hồi theo chế độ Silent Mode:**\nKhi nhận tin từ \`channel: zalouser\` và \`group_id\` có giá trị:\n\n- Nếu tin nhắn chứa \`@${name}\` → **LUÔN reply** (bất kể silent mode).\n- Nếu tin nhắn bắt đầu bằng \`/\` (slash command) → KHÔNG reply, plugin đã xử lý rồi.\n- Tin thường trong group (không mention, không slash):\n - Nếu **Silent Mode BẬT** → tin này KHÔNG đến được bot (plugin đã chặn).\n - Nếu **Silent Mode TẮT** → tin này ĐẾN ĐƯỢC bot → **reply bình thường** như DM.\n- DM (không có group_id) → reply bình thường.`;
|
|
959
|
+
}
|
|
960
|
+
return `\n\n**RULE — Zalo Group: Reply based on Silent Mode:**\nWhen receiving messages from \`channel: zalouser\` with a \`group_id\`:\n\n- If the message contains \`@${name}\` → **ALWAYS reply** (regardless of silent mode).\n- If the message starts with \`/\` (slash command) → DO NOT reply, the plugin already handled it.\n- Regular group messages (no mention, no slash):\n - If **Silent Mode is ON** → this message does NOT reach the bot (plugin blocks it).\n - If **Silent Mode is OFF** → this message DOES reach the bot → **reply normally** like DM.\n- DM (no group_id) → reply normally.`;
|
|
961
|
+
}
|
|
962
|
+
|
|
955
963
|
function buildSoulDoc(options = {}) {
|
|
956
|
-
const { isVi = true, persona = '', variant = 'wizard' } = options;
|
|
964
|
+
const { isVi = true, persona = '', variant = 'wizard', hasZaloMod = false, botName = 'Bot' } = options;
|
|
965
|
+
let doc;
|
|
957
966
|
if (variant === 'cli-simple') {
|
|
958
|
-
|
|
967
|
+
doc = isVi
|
|
959
968
|
? `# Tính cách\n\n${persona || 'Thân thiện, rõ ràng, giải quyết việc thẳng vào mục tiêu.'}\n`
|
|
960
969
|
: `# Soul\n\n${persona || 'Friendly, clear, and outcome-focused.'}\n`;
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
return isVi
|
|
970
|
+
} else if (variant === 'cli-rich') {
|
|
971
|
+
doc = isVi
|
|
964
972
|
? `# Tính cách\n\n**Hữu ích thật sự.** Bỏ qua câu nệ — cứ giúp thẳng.\n**Có cá tính.** Trợ lý không có cá tính thì chỉ là công cụ.\n\n## Phong cách\n- Tự nhiên, gần gũi như bạn bè\n- Trực tiếp, không parrot câu hỏi.${persona ? `\n\n## Custom Rules\n${persona}` : ''}`
|
|
965
973
|
: `# Soul\n\n**Be genuinely helpful.** Skip filler and help directly.\n**Have personality.** An assistant without personality is just a tool.\n\n## Style\n- Natural and approachable\n- Direct, do not parrot the prompt.${persona ? `\n\n## Custom Rules\n${persona}` : ''}`;
|
|
974
|
+
} else {
|
|
975
|
+
doc = isVi
|
|
976
|
+
? `# Tính cách\n\n**Hữu ích thật sự.** Bỏ qua câu nệ, cứ giúp thẳng.\n**Có cá tính.** Trợ lý không có cá tính thì chỉ là công cụ.\n\n## Phong cách\n- Tự nhiên, gần gũi\n- Trực tiếp, ngắn gọn${persona ? `\n\n## Custom Rules\n${persona}` : ''}`
|
|
977
|
+
: `# Soul\n\n**Be genuinely helpful.** Skip filler and just help.\n**Have personality.** An assistant with no personality is just a tool.\n\n## Style\n- Natural and concise\n- Direct and practical${persona ? `\n\n## Custom Rules\n${persona}` : ''}`;
|
|
966
978
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
979
|
+
if (hasZaloMod) {
|
|
980
|
+
doc += buildZaloSoulSection(isVi, botName);
|
|
981
|
+
}
|
|
982
|
+
return doc;
|
|
970
983
|
}
|
|
971
984
|
|
|
972
985
|
function buildTeamDoc(options = {}) {
|
|
@@ -1241,6 +1254,7 @@ const CDP_URL = 'http://127.0.0.1:9222';
|
|
|
1241
1254
|
agentWorkspaceDir = 'workspace',
|
|
1242
1255
|
hasBrowser = false,
|
|
1243
1256
|
hasScheduler = false,
|
|
1257
|
+
hasZaloMod = false,
|
|
1244
1258
|
browserDocVariant = '',
|
|
1245
1259
|
} = options;
|
|
1246
1260
|
|
|
@@ -1268,19 +1282,25 @@ const CDP_URL = 'http://127.0.0.1:9222';
|
|
|
1268
1282
|
: `\n\n## \u23F0 Cron / Scheduled Tasks\n- OpenClaw natively supports system tools for Cron Jobs.\n- When the user asks to schedule tasks or reminders, use the built-in tools automatically. Do NOT ask users to run crontab or Task Scheduler manually on the host.\n- When operating cron/scheduler tools, do **not** put \`current\` into the Session directory.\n- Skip internal doc lookups such as \`cron-jobs.mdx\`; rely on the available tools and complete the scheduling task directly.`)
|
|
1269
1283
|
: '';
|
|
1270
1284
|
|
|
1285
|
+
const zaloModSection = hasZaloMod
|
|
1286
|
+
? (isVi
|
|
1287
|
+
? `\n\n## 💬 Zalo Group — Slash Commands (xử lý bởi plugin)\n\nPlugin \`zalo-mod\` tự động xử lý các slash command sau trong group. Bot KHÔNG cần reply cho chúng:\n\n| Command | Mô tả |\n|---------|-------|\n| \`/rules status\` | Xem cấu hình bot |\n| \`/rules silent-on/off\` | Bật/tắt silent mode |\n| \`/rules welcome-on/off\` | Bật/tắt welcome message |\n| \`/rules tracking-on/off\` | Bật/tắt ghi log chat |\n| \`/noi-quy\` | Hiện nội quy group |\n| \`/menu\` | Danh sách lệnh |\n| \`/groupid\` | Scan và cập nhật config |\n| \`/report\` | Báo cáo hoạt động group |\n\n### Zalo Sticker & Media\n- Sticker Zalo gửi dạng JSON → plugin tự convert thành \`[Sticker]\`\n- Ảnh/video/file trong group: zalouser channel chỉ forward text, media bị drop`
|
|
1288
|
+
: `\n\n## 💬 Zalo Group — Slash Commands (handled by plugin)\n\nThe \`zalo-mod\` plugin automatically handles these slash commands in group. Bot does NOT need to reply:\n\n| Command | Description |\n|---------|-------------|\n| \`/rules status\` | View bot config |\n| \`/rules silent-on/off\` | Toggle silent mode |\n| \`/rules welcome-on/off\` | Toggle welcome message |\n| \`/rules tracking-on/off\` | Toggle chat logging |\n| \`/noi-quy\` | Show group rules |\n| \`/menu\` | List commands |\n| \`/groupid\` | Scan and update config |\n| \`/report\` | Group activity report |\n\n### Zalo Sticker & Media\n- Zalo stickers arrive as JSON → plugin auto-converts to \`[Sticker]\`\n- Images/videos/files in groups: zalouser channel only forwards text, media is dropped`)
|
|
1289
|
+
: '';
|
|
1290
|
+
|
|
1271
1291
|
const dmOverride = isVi
|
|
1272
1292
|
? `\n\n## Override: DM và Telegram reaction\n- Nếu metadata không nói rõ đây là group/supergroup, mặc định xem là chat riêng/DM và trả lời bình thường.\n- Quy tắc im lặng khi không ai được gọi chỉ áp dụng cho group chat, không áp dụng cho DM/chat riêng.\n- Trước MỖI phản hồi Telegram nhìn thấy bởi user, nếu turn hiện tại có tool/action reaction native thì ưu tiên dùng nó để thả \`👍\` trước, trừ khi \`👍\` đã có sẵn.\n- Nếu turn hiện tại không expose tool reaction hoặc chat đích không hỗ trợ reaction thật, cứ trả lời đúng vai trò và không được nói là mình đã thả reaction.`
|
|
1273
1293
|
: `\n\n## Override: DM and Telegram reaction\n- If metadata does not clearly say this is a group/supergroup, treat it as a private DM and reply normally.\n- The stay-silent rule for unaddressed messages applies only to group chats, never to DMs.\n- Before EVERY user-visible Telegram reply, if the current turn exposes a native reaction tool/action, use it to add \`👍\` first unless \`👍\` is already present.\n- If the reaction tool is unavailable or the target chat does not support real reactions, just reply in-character and do not claim that you reacted.`;
|
|
1274
1294
|
|
|
1275
1295
|
if (variant === 'relay') {
|
|
1276
1296
|
return isVi
|
|
1277
|
-
? `# Hướng dẫn dùng tool\n\n## Tools có sẵn\n${skillsSection}\n\n## Quy tắc chung\n- Tóm tắt kết quả tool thay vì dump raw output.\n- Mọi bot đều có quyền sử dụng tất cả tool (scheduler, browser, exec). Vai trò (dev/marketing/...) chỉ là persona, KHÔNG giới hạn quyền dùng tool.\n- Workspace của bạn là \`.openclaw/${agentWorkspaceDir}/\`.${browserRef}${telegramSection}${cronSection}${dmOverride}\n`
|
|
1278
|
-
: `# Tool Usage Guide\n\n## Available Tools\n${skillsSection}\n\n## General Rules\n- Summarize tool output instead of dumping raw output.\n- All bots have equal access to all tools (scheduler, browser, exec). Roles (dev/marketing/...) are persona only, NOT tool permissions.\n- Your workspace is \`.openclaw/${agentWorkspaceDir}/\`.${browserRef}${telegramSection}${cronSection}${dmOverride}\n`;
|
|
1297
|
+
? `# Hướng dẫn dùng tool\n\n## Tools có sẵn\n${skillsSection}\n\n## Quy tắc chung\n- Tóm tắt kết quả tool thay vì dump raw output.\n- Mọi bot đều có quyền sử dụng tất cả tool (scheduler, browser, exec). Vai trò (dev/marketing/...) chỉ là persona, KHÔNG giới hạn quyền dùng tool.\n- Workspace của bạn là \`.openclaw/${agentWorkspaceDir}/\`.${browserRef}${telegramSection}${cronSection}${zaloModSection}${dmOverride}\n`
|
|
1298
|
+
: `# Tool Usage Guide\n\n## Available Tools\n${skillsSection}\n\n## General Rules\n- Summarize tool output instead of dumping raw output.\n- All bots have equal access to all tools (scheduler, browser, exec). Roles (dev/marketing/...) are persona only, NOT tool permissions.\n- Your workspace is \`.openclaw/${agentWorkspaceDir}/\`.${browserRef}${telegramSection}${cronSection}${zaloModSection}${dmOverride}\n`;
|
|
1279
1299
|
}
|
|
1280
1300
|
|
|
1281
1301
|
return isVi
|
|
1282
|
-
? `# Hướng dẫn sử dụng Tools\n\n## Danh sách skills đã cài\n${skillsSection}\n\n## Nguyên tắc chung\n- Ưu tiên dùng tool/skill phù hợp thay vì tự suy đoán\n- Nếu tool trả về lỗi — thử lại 1 lần, sau đó báo user\n- Không chạy tool liên tục mà không có mục đích rõ ràng\n- Luôn tóm tắt kết quả tool cho user thay vì dump raw output${browserRef}\n\n## Quy ước\n- Web Search: chỉ dùng khi cần thông tin realtime hoặc user yêu cầu\n- Browser: chỉ mở trang khi user yêu cầu cụ thể\n- Memory: tự ghi nhớ thông tin tự nhiên, không cần user nhắc${cronSection}\n\n## \uD83D\uDCC1 File & Workspace\n- Bot có thể đọc/ghi file trong thư mục workspace: \`${workspacePath}\`\n- Dùng để lưu notes, scripts, cấu hình tạm\n\n## \u26A0\uFE0F Tool Error Handling\n- Retry tối đa 2 lần nếu tool lỗi network\n- Nếu vẫn lỗi: báo user kèm mô tả lỗi cụ thể và gợi ý workaround${dmOverride}\n`
|
|
1283
|
-
: `# Tool Usage Guide\n\n## Installed Skills\n${skillsSection}\n\n## General Principles\n- Prefer using the right tool/skill over guessing\n- If a tool returns an error — retry once, then report to user\n- Don't run tools repeatedly without a clear purpose\n- Always summarize tool output for user instead of dumping raw data${browserRef}\n\n## Conventions\n- Web Search: only use when needing real-time info or user explicitly asks\n- Browser: only open pages when user specifically requests\n- Memory: proactively remember important info without user prompting${cronSection}\n\n## \uD83D\uDCC1 File & Workspace\n- Bot can read/write files in workspace: \`${workspacePath}\`\n\n## \u26A0\uFE0F Tool Error Handling\n- Retry up to 2 times on network errors\n- If still failing: report to user with specific error description and workaround${dmOverride}\n`;
|
|
1302
|
+
? `# Hướng dẫn sử dụng Tools\n\n## Danh sách skills đã cài\n${skillsSection}\n\n## Nguyên tắc chung\n- Ưu tiên dùng tool/skill phù hợp thay vì tự suy đoán\n- Nếu tool trả về lỗi — thử lại 1 lần, sau đó báo user\n- Không chạy tool liên tục mà không có mục đích rõ ràng\n- Luôn tóm tắt kết quả tool cho user thay vì dump raw output${browserRef}\n\n## Quy ước\n- Web Search: chỉ dùng khi cần thông tin realtime hoặc user yêu cầu\n- Browser: chỉ mở trang khi user yêu cầu cụ thể\n- Memory: tự ghi nhớ thông tin tự nhiên, không cần user nhắc${cronSection}${zaloModSection}\n\n## \uD83D\uDCC1 File & Workspace\n- Bot có thể đọc/ghi file trong thư mục workspace: \`${workspacePath}\`\n- Dùng để lưu notes, scripts, cấu hình tạm\n\n## \u26A0\uFE0F Tool Error Handling\n- Retry tối đa 2 lần nếu tool lỗi network\n- Nếu vẫn lỗi: báo user kèm mô tả lỗi cụ thể và gợi ý workaround${dmOverride}\n`
|
|
1303
|
+
: `# Tool Usage Guide\n\n## Installed Skills\n${skillsSection}\n\n## General Principles\n- Prefer using the right tool/skill over guessing\n- If a tool returns an error — retry once, then report to user\n- Don't run tools repeatedly without a clear purpose\n- Always summarize tool output for user instead of dumping raw data${browserRef}\n\n## Conventions\n- Web Search: only use when needing real-time info or user explicitly asks\n- Browser: only open pages when user specifically requests\n- Memory: proactively remember important info without user prompting${cronSection}${zaloModSection}\n\n## \uD83D\uDCC1 File & Workspace\n- Bot can read/write files in workspace: \`${workspacePath}\`\n\n## \u26A0\uFE0F Tool Error Handling\n- Retry up to 2 times on network errors\n- If still failing: report to user with specific error description and workaround${dmOverride}\n`;
|
|
1284
1304
|
}
|
|
1285
1305
|
function buildTeamsDoc(options = {}) {
|
|
1286
1306
|
const {
|
|
@@ -1354,20 +1374,21 @@ const CDP_URL = 'http://127.0.0.1:9222';
|
|
|
1354
1374
|
teamRosterFormatted = '',
|
|
1355
1375
|
emoji = '',
|
|
1356
1376
|
hasScheduler = false,
|
|
1377
|
+
hasZaloMod = false,
|
|
1357
1378
|
} = opts;
|
|
1358
1379
|
|
|
1359
1380
|
const isMultiBot = variant === 'relay';
|
|
1360
1381
|
|
|
1361
1382
|
const files = {
|
|
1362
1383
|
'IDENTITY.md': buildIdentityDoc({ isVi, name: botName, desc: botDesc, emoji }),
|
|
1363
|
-
'SOUL.md': buildSoulDoc({ isVi, persona, variant: soulVariant }),
|
|
1384
|
+
'SOUL.md': buildSoulDoc({ isVi, persona, variant: soulVariant, hasZaloMod, botName }),
|
|
1364
1385
|
'AGENTS.md': buildAgentsDoc({
|
|
1365
1386
|
isVi, botName, botDesc, ownAliases, otherAgents, workspacePath,
|
|
1366
1387
|
variant, includeSecurity: true, replyToDirectMessages: true,
|
|
1367
1388
|
}),
|
|
1368
1389
|
'USER.md': buildUserDoc({ isVi, userInfo, variant: userVariant || (isMultiBot ? 'cli-multi' : 'wizard') }),
|
|
1369
1390
|
'TOOLS.md': buildToolsDoc({
|
|
1370
|
-
isVi, skillListStr, workspacePath, variant, agentWorkspaceDir, hasBrowser, hasScheduler, browserDocVariant,
|
|
1391
|
+
isVi, skillListStr, workspacePath, variant, agentWorkspaceDir, hasBrowser, hasScheduler, hasZaloMod, browserDocVariant,
|
|
1371
1392
|
}),
|
|
1372
1393
|
'MEMORY.md': buildMemoryDoc({ isVi, variant: memoryVariant }),
|
|
1373
1394
|
'HEARTBEAT.md': buildHeartbeatDoc({ isVi }),
|
|
@@ -1712,11 +1733,11 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
|
|
|
1712
1733
|
},
|
|
1713
1734
|
};
|
|
1714
1735
|
|
|
1715
|
-
const allow = [];
|
|
1736
|
+
const allow = ['memory-core'];
|
|
1716
1737
|
|
|
1717
1738
|
// zalo-mod plugin for Zalo Personal
|
|
1718
1739
|
if (isZaloPersonal(channelKey)) {
|
|
1719
|
-
allow.push('zalo-mod');
|
|
1740
|
+
allow.push('zalo-mod', 'zalouser');
|
|
1720
1741
|
entries['zalo-mod'] = {
|
|
1721
1742
|
enabled: true,
|
|
1722
1743
|
config: {
|
|
@@ -1728,12 +1749,11 @@ if (typeof exports !== 'undefined' && workspaceRoot.__openclawWorkspace) {
|
|
|
1728
1749
|
spamWindowSeconds: 300,
|
|
1729
1750
|
},
|
|
1730
1751
|
};
|
|
1752
|
+
entries.zalouser = { enabled: true };
|
|
1731
1753
|
}
|
|
1732
1754
|
|
|
1733
1755
|
const plugins = { entries };
|
|
1734
|
-
|
|
1735
|
-
plugins.allow = allow;
|
|
1736
|
-
}
|
|
1756
|
+
plugins.allow = allow;
|
|
1737
1757
|
|
|
1738
1758
|
return { plugins };
|
|
1739
1759
|
}
|
|
@@ -2606,6 +2626,7 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
2606
2626
|
selectedModel,
|
|
2607
2627
|
agentId,
|
|
2608
2628
|
allSkills = [],
|
|
2629
|
+
dockerfilePlugins = [],
|
|
2609
2630
|
dockerfileSkillInstallMode = 'none',
|
|
2610
2631
|
runtimeCommandParts = [],
|
|
2611
2632
|
volumeMount = '../..:/root/project',
|
|
@@ -2639,16 +2660,59 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
2639
2660
|
const skillLines = dockerfileSkillInstallMode === 'build' && allSkills.length > 0
|
|
2640
2661
|
? `\n# Install skills (ClawHub)\n${allSkills.map((skill) => `RUN openclaw skills install ${skill} || echo "Warning: Failed to install ${skill} due to rate limits."`).join('\n')}\n`
|
|
2641
2662
|
: '';
|
|
2663
|
+
const pluginLines = dockerfilePlugins.length > 0
|
|
2664
|
+
? `\n# Install plugins (ClawHub)\n${dockerfilePlugins.map((p) => `RUN openclaw plugins install ${p} || echo "Warning: Failed to install plugin ${p}"`).join('\n')}\n`
|
|
2665
|
+
: '';
|
|
2642
2666
|
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);}"`;
|
|
2643
2667
|
|
|
2644
|
-
// Dynamic runtime configuration
|
|
2645
|
-
|
|
2646
|
-
const
|
|
2668
|
+
// Dynamic runtime configuration: backup config before any first-run install, restore after.
|
|
2669
|
+
// Missing plugin install may touch openclaw.json, so preserve critical fields.
|
|
2670
|
+
const backupConfigScript = `const fs=require('fs'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json'),b=p.replace('openclaw.json','.openclaw-config-backup.json');if(fs.existsSync(p)){fs.copyFileSync(p,b);}`;
|
|
2671
|
+
const backupConfigB64 = encodeBase64Utf8(backupConfigScript);
|
|
2672
|
+
|
|
2673
|
+
const restoreConfigScript = `const fs=require('fs'),os=require('os'),path=require('path'),p=path.join(process.cwd(),'.openclaw','openclaw.json'),b=p.replace('openclaw.json','.openclaw-config-backup.json');if(fs.existsSync(p)&&fs.existsSync(b)){const c=JSON.parse(fs.readFileSync(p,'utf8'));const bk=JSON.parse(fs.readFileSync(b,'utf8'));const keep=['agents','channels','bindings','commands','models','browser','skills'];for(const k of keep){if(bk[k]&&!c[k])c[k]=bk[k];}const a=new Set(['http://localhost:18791','http://127.0.0.1:18791','http://0.0.0.0:18791']);for(const entries of Object.values(os.networkInterfaces()||{})){for(const entry of entries||[]){if(!entry||entry.internal||entry.family!=='IPv4'||!entry.address)continue;a.add('http://'+entry.address+':18791');}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:18791,bind:'custom',customBindHost:'0.0.0.0',mode:c.gateway?.mode||bk.gateway?.mode||'local',controlUi:Object.assign({},c.gateway?.controlUi,{allowedOrigins:Array.from(a).filter(Boolean)})});fs.writeFileSync(p,JSON.stringify(c,null,2));fs.unlinkSync(b);}`;
|
|
2674
|
+
const restoreConfigB64 = encodeBase64Utf8(restoreConfigScript);
|
|
2647
2675
|
|
|
2648
2676
|
const runtimeParts = runtimeCommandParts.filter(Boolean);
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2677
|
+
const runtimePrelude = [
|
|
2678
|
+
'export OPENCLAW_HOME="${OPENCLAW_HOME:-$PWD/.openclaw}"',
|
|
2679
|
+
'export OPENCLAW_STATE_DIR="${OPENCLAW_STATE_DIR:-$OPENCLAW_HOME}"',
|
|
2680
|
+
'mkdir -p "$OPENCLAW_HOME" "$OPENCLAW_STATE_DIR"',
|
|
2681
|
+
'if [ "$OPENCLAW_STATE_DIR" != "$OPENCLAW_HOME" ]; then',
|
|
2682
|
+
' for path in "$OPENCLAW_HOME"/*; do',
|
|
2683
|
+
' [ -e "$path" ] || continue',
|
|
2684
|
+
' name="$(basename "$path")"',
|
|
2685
|
+
' [ "$name" = "plugin-runtime-deps" ] && continue',
|
|
2686
|
+
' [ "$name" = "logs" ] && continue',
|
|
2687
|
+
' [ -e "$OPENCLAW_STATE_DIR/$name" ] || ln -s "$path" "$OPENCLAW_STATE_DIR/$name"',
|
|
2688
|
+
' done',
|
|
2689
|
+
'fi',
|
|
2690
|
+
'ensure_plugin() {',
|
|
2691
|
+
' id="$1"',
|
|
2692
|
+
' spec="$2"',
|
|
2693
|
+
' if [ -d "$OPENCLAW_HOME/extensions/$id" ]; then',
|
|
2694
|
+
' echo "[entrypoint] plugin $id already installed"',
|
|
2695
|
+
' return 0',
|
|
2696
|
+
' fi',
|
|
2697
|
+
' echo "[entrypoint] plugin $id missing; installing $spec"',
|
|
2698
|
+
' openclaw plugins install "$spec" 2>/dev/null || echo "[entrypoint] warning: failed to install plugin $spec"',
|
|
2699
|
+
'}',
|
|
2700
|
+
'ensure_skill() {',
|
|
2701
|
+
' id="$1"',
|
|
2702
|
+
' if find "$OPENCLAW_HOME" -maxdepth 4 -type d -path "*/skills/$id" -print -quit 2>/dev/null | grep -q .; then',
|
|
2703
|
+
' echo "[entrypoint] skill $id already installed"',
|
|
2704
|
+
' return 0',
|
|
2705
|
+
' fi',
|
|
2706
|
+
' echo "[entrypoint] skill $id missing; installing"',
|
|
2707
|
+
' openclaw skills install "$id" 2>/dev/null || echo "[entrypoint] warning: failed to install skill $id"',
|
|
2708
|
+
'}',
|
|
2709
|
+
'echo "[entrypoint] ensuring runtime assets, then starting gateway"',
|
|
2710
|
+
];
|
|
2711
|
+
runtimeParts.unshift(...runtimePrelude);
|
|
2712
|
+
// Backup config BEFORE plugin installs (runtimeCommandParts may contain plugin install commands)
|
|
2713
|
+
runtimeParts.unshift(`node -e 'eval(Buffer.from("${backupConfigB64}","base64").toString())'`);
|
|
2714
|
+
// Restore config AFTER plugin installs (which may clobber openclaw.json)
|
|
2715
|
+
runtimeParts.push(`node -e 'eval(Buffer.from("${restoreConfigB64}","base64").toString())'`);
|
|
2652
2716
|
if (hasBrowser) {
|
|
2653
2717
|
runtimeParts.push('socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 &');
|
|
2654
2718
|
runtimeParts.push('Xvfb :99 -screen 0 1280x720x24 > /dev/null 2>&1 & DISPLAY=:99 openclaw gateway run');
|
|
@@ -2663,7 +2727,7 @@ RUN apt-get update && apt-get install -y git curl${browserAptExtra} && rm -rf /v
|
|
|
2663
2727
|
${browserInstallLines}
|
|
2664
2728
|
ARG OPENCLAW_VER="${openClawNpmSpec}"
|
|
2665
2729
|
ARG CACHE_BUST=""
|
|
2666
|
-
RUN npm install -g ${openClawNpmSpec} ${openClawRuntimePackages}${skillLines}
|
|
2730
|
+
RUN npm install -g ${openClawNpmSpec} ${openClawRuntimePackages}${skillLines}${pluginLines}
|
|
2667
2731
|
${patchLine}
|
|
2668
2732
|
RUN node -e "require('fs').writeFileSync('/usr/local/bin/openclaw-entrypoint.sh', Buffer.from('${runtimeScriptB64}','base64').toString())" && chmod +x /usr/local/bin/openclaw-entrypoint.sh
|
|
2669
2733
|
WORKDIR /root/project
|
|
@@ -2679,7 +2743,7 @@ CMD ["/bin/sh", "/usr/local/bin/openclaw-entrypoint.sh"]`;
|
|
|
2679
2743
|
const docker9RouterEntrypointScript = build9RouterComposeEntrypointScript(syncScriptBase64, patchScriptBase64);
|
|
2680
2744
|
const extraHostsBlock = ` extra_hosts:\n - "host.docker.internal:host-gateway"`;
|
|
2681
2745
|
|
|
2682
|
-
const appEnvironmentBlock = ' environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/
|
|
2746
|
+
const appEnvironmentBlock = ' environment:\n - OPENCLAW_HOME=/root/project/.openclaw\n - OPENCLAW_STATE_DIR=/var/lib/openclaw-state\n';
|
|
2683
2747
|
|
|
2684
2748
|
let compose;
|
|
2685
2749
|
if (isMultiBot) {
|
|
@@ -2872,6 +2936,18 @@ ${appEnvironmentBlock}${plainSingleExtraHosts ? `${extraHostsBlock}\n` : ''}
|
|
|
2872
2936
|
- "18791:18791"`;
|
|
2873
2937
|
}
|
|
2874
2938
|
|
|
2939
|
+
compose = compose.replaceAll(
|
|
2940
|
+
` - ${volumeMount}`,
|
|
2941
|
+
` - ${volumeMount}\n - openclaw-state:/var/lib/openclaw-state`
|
|
2942
|
+
);
|
|
2943
|
+
if (compose.includes('\nvolumes:\n')) {
|
|
2944
|
+
if (!compose.includes(' openclaw-state:')) {
|
|
2945
|
+
compose = compose.replace('\nvolumes:\n', '\nvolumes:\n openclaw-state:\n');
|
|
2946
|
+
}
|
|
2947
|
+
} else {
|
|
2948
|
+
compose += '\n\nvolumes:\n openclaw-state:';
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2875
2951
|
return {
|
|
2876
2952
|
dockerfile,
|
|
2877
2953
|
compose,
|
|
@@ -2932,6 +3008,7 @@ function buildNativeScriptCtx(options) {
|
|
|
2932
3008
|
const p = PLUGINS.find((x) => x.id === pid);
|
|
2933
3009
|
if (p) allPlugins.push(p.package);
|
|
2934
3010
|
});
|
|
3011
|
+
if (ch && ch.hasZaloPersonal) allPlugins.push('openclaw-zalo-mod');
|
|
2935
3012
|
if (isMultiBot && state.channel === 'telegram') allPlugins.push(relayPluginSpec);
|
|
2936
3013
|
const uniquePlugins = [...new Set(allPlugins)];
|
|
2937
3014
|
const pluginCmd = uniquePlugins.length > 0 ? uniquePlugins.map(function(pkg) { return 'call npm exec -- openclaw plugins install ' + pkg + ' || echo [WARN] Plugin ' + pkg + ' cai dat that bai (co the do rate limit). Ban co the cai thu cong sau.'; }).join('\r\n') : '';
|
|
@@ -3379,6 +3456,7 @@ const sync=async()=>{try{const res=await fetch(ROUTER+'/api/providers');if(!res.
|
|
|
3379
3456
|
hasBrowser,
|
|
3380
3457
|
soulVariant: 'wizard',
|
|
3381
3458
|
memoryVariant: 'wizard',
|
|
3459
|
+
hasZaloMod: state.channel === 'zalo-personal',
|
|
3382
3460
|
});
|
|
3383
3461
|
}
|
|
3384
3462
|
|
|
@@ -3553,11 +3631,18 @@ function generateZaloLoginSh(opts) {
|
|
|
3553
3631
|
`if [ -f "${credPath}" ]; then`,
|
|
3554
3632
|
` echo "[OK] Session Zalo da ton tai. Bo qua buoc dang nhap."`,
|
|
3555
3633
|
`else`,
|
|
3556
|
-
` echo "[Zalo] Chua co session.
|
|
3557
|
-
`
|
|
3634
|
+
` echo "[Zalo] Chua co session. Bat dau dang nhap..."`,
|
|
3635
|
+
` ${loginCmd}`,
|
|
3636
|
+
` ZALO_QR="/tmp/openclaw/openclaw-zalouser-qr${useInstance ? '-default' : ''}.png"`,
|
|
3637
|
+
` echo ""`,
|
|
3638
|
+
` echo "=== HUONG DAN DANG NHAP ZALO ==="`,
|
|
3639
|
+
` echo " 1. Tim file QR tai: $ZALO_QR"`,
|
|
3640
|
+
` echo " 2. Copy file QR ra may: scp user@host:$ZALO_QR ./zalo-qr.png"`,
|
|
3641
|
+
` echo " Hoac mo truc tiep: xdg-open $ZALO_QR 2>/dev/null || open $ZALO_QR 2>/dev/null || echo 'Mo file QR thu cong'"`,
|
|
3642
|
+
` echo " 3. Mo app Zalo > Quet QR > quet ma trong file QR"`,
|
|
3643
|
+
` echo " 4. Doi thay Login successful trong terminal"`,
|
|
3644
|
+
` echo "================================"`,
|
|
3558
3645
|
` echo ""`,
|
|
3559
|
-
` echo " QR code se xuat hien trong terminal do."`,
|
|
3560
|
-
` echo " Sau khi quet QR thanh cong, nhay Enter de tiep tuc."`,
|
|
3561
3646
|
` read -p "Nhan Enter sau khi dang nhap Zalo thanh cong... "`,
|
|
3562
3647
|
`fi`,
|
|
3563
3648
|
];
|
|
@@ -3710,6 +3795,34 @@ New-Item -ItemType Directory -Force -Path "$projectDir" | Out-Null
|
|
|
3710
3795
|
ps += `Write-Host "[3/4] ${isVi ? 'Build Docker image...' : 'Building Docker image...'}" -ForegroundColor Yellow\n`;
|
|
3711
3796
|
ps += `Set-Location "$projectDir\\docker\\openclaw"\n$cacheBust = (Get-Date -Format 'yyyyMMddHHmmss')\n& docker compose build --build-arg CACHE_BUST=$cacheBust\n`;
|
|
3712
3797
|
ps += `Write-Host "[4/4] ${isVi ? 'Khoi dong bot...' : 'Starting bot...'}" -ForegroundColor Yellow\n& docker compose up -d\n`;
|
|
3798
|
+
|
|
3799
|
+
if (state.channel === 'zalo-personal') {
|
|
3800
|
+
const containerName = 'openclaw-bot';
|
|
3801
|
+
const qrPath = '/tmp/openclaw/openclaw-zalouser-qr-default.png';
|
|
3802
|
+
ps += `\nWrite-Host "" -ForegroundColor White\n`;
|
|
3803
|
+
ps += `Write-Host "${isVi ? '=== DANG NHAP ZALO ===' : '=== ZALO LOGIN ==='}" -ForegroundColor Cyan\n`;
|
|
3804
|
+
ps += `Write-Host "${isVi ? 'Doi gateway khoi dong xong (30 giay)...' : 'Waiting for gateway to start (30s)...'}" -ForegroundColor Yellow\n`;
|
|
3805
|
+
ps += `Start-Sleep -Seconds 30\n`;
|
|
3806
|
+
ps += `Write-Host "" -ForegroundColor White\n`;
|
|
3807
|
+
ps += `Write-Host "${isVi ? 'Huong dan dang nhap Zalo:' : 'Zalo login instructions:'}" -ForegroundColor White\n`;
|
|
3808
|
+
ps += `Write-Host " ${isVi ? '1. cd docker\\\\openclaw' : '1. cd docker\\\\openclaw'}" -ForegroundColor White\n`;
|
|
3809
|
+
ps += `Write-Host " ${isVi ? `2. docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose` : `2. docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose`}" -ForegroundColor White\n`;
|
|
3810
|
+
ps += `Write-Host " ${isVi ? `3. Mo Docker Desktop > container ${containerName} > tab Files > tim file: ${qrPath}` : `3. Open Docker Desktop > container ${containerName} > Files tab > find: ${qrPath}`}" -ForegroundColor White\n`;
|
|
3811
|
+
ps += `Write-Host " ${isVi ? ` Hoac chay: docker cp ${containerName}:${qrPath} ./zalo-qr.png` : ` Or run: docker cp ${containerName}:${qrPath} ./zalo-qr.png`}" -ForegroundColor White\n`;
|
|
3812
|
+
ps += `Write-Host " ${isVi ? '4. Mo app Zalo > Quet QR > quet ma trong file QR' : '4. Open Zalo app > Scan QR > scan the QR image'}" -ForegroundColor White\n`;
|
|
3813
|
+
ps += `Write-Host " ${isVi ? '5. Doi thay chu Login successful trong terminal' : '5. Wait for Login successful in terminal'}" -ForegroundColor White\n`;
|
|
3814
|
+
ps += `Write-Host " ${isVi ? '6. Restart container: docker compose restart' : '6. Restart container: docker compose restart'}" -ForegroundColor White\n`;
|
|
3815
|
+
ps += `Write-Host "" -ForegroundColor White\n`;
|
|
3816
|
+
ps += `Write-Host "${isVi ? 'Dung gateway de login...' : 'Stopping gateway for login...'}" -ForegroundColor Yellow\\n`;
|
|
3817
|
+
ps += `& docker exec ${containerName} openclaw gateway stop 2>$null\\n`;
|
|
3818
|
+
ps += `Start-Sleep -Seconds 3\\n`;
|
|
3819
|
+
ps += `Write-Host "${isVi ? 'Dang chay lenh login...' : 'Running login command...'}" -ForegroundColor Yellow\n`;
|
|
3820
|
+
ps += `& docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose\n`;
|
|
3821
|
+
ps += `Write-Host "" -ForegroundColor White\n`;
|
|
3822
|
+
ps += `Write-Host "${isVi ? 'Restart container de ap dung...' : 'Restarting container to apply...'}" -ForegroundColor Green\n`;
|
|
3823
|
+
ps += `& docker compose restart\n`;
|
|
3824
|
+
}
|
|
3825
|
+
|
|
3713
3826
|
ps += `} catch { Write-Host $_.Exception.Message -ForegroundColor Red }\nRead-Host "${isVi ? 'Nhan Enter de thoat' : 'Press Enter to exit'}"\n`;
|
|
3714
3827
|
return `@echo off
|
|
3715
3828
|
chcp 65001>nul
|
|
@@ -3747,6 +3860,16 @@ echo ""
|
|
|
3747
3860
|
script += `cat > "${path}" << 'CLAWEOF'\n${String(content)}${String(content).endsWith('\n') ? '' : '\n'}CLAWEOF\n\n`;
|
|
3748
3861
|
});
|
|
3749
3862
|
script += `echo "${isVi ? 'Files created' : 'Files created'}"\ncd "docker/openclaw"\ndocker compose build --build-arg CACHE_BUST=$(date +%s)\ndocker compose up --detach\n`;
|
|
3863
|
+
|
|
3864
|
+
if (state.channel === 'zalo-personal') {
|
|
3865
|
+
const containerName = 'openclaw-bot';
|
|
3866
|
+
const qrPath = '/tmp/openclaw/openclaw-zalouser-qr-default.png';
|
|
3867
|
+
script += `\necho ""\necho "${isVi ? '=== DANG NHAP ZALO ===' : '=== ZALO LOGIN ==='}"\necho "${isVi ? 'Doi container khoi dong 10 giay...' : 'Waiting 10s for container to start...'}"\nsleep 10\n`;
|
|
3868
|
+
script += `echo "${isVi ? 'Huong dan dang nhap Zalo:' : 'Zalo login instructions:'}"\necho " ${isVi ? '1. cd docker/openclaw' : '1. cd docker/openclaw'}"\necho " 2. docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose"\necho " ${isVi ? `3. Tim file QR trong container: ${qrPath}` : `3. Find QR image in container: ${qrPath}`}"\necho " ${isVi ? ` Hoac chay: docker cp ${containerName}:${qrPath} ./zalo-qr.png` : ` Or run: docker cp ${containerName}:${qrPath} ./zalo-qr.png`}"\necho " ${isVi ? '4. Mo app Zalo > Quet QR > quet ma' : '4. Open Zalo app > Scan QR > scan'}"\necho " ${isVi ? '5. Doi thay Login successful' : '5. Wait for Login successful'}"\necho " ${isVi ? '6. Restart: docker compose restart' : '6. Restart: docker compose restart'}"\necho ""\n`;
|
|
3869
|
+
script += `docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose\n`;
|
|
3870
|
+
script += `echo "${isVi ? 'Restart container...' : 'Restarting container...'}"\ndocker compose restart\n`;
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3750
3873
|
return script;
|
|
3751
3874
|
}
|
|
3752
3875
|
|
|
@@ -3935,18 +4058,7 @@ function generateWinBat(ctx) {
|
|
|
3935
4058
|
|
|
3936
4059
|
if (state.channel === 'zalo-personal') {
|
|
3937
4060
|
lines.push('echo [5/6] Dang nhap Zalo Personal...');
|
|
3938
|
-
lines.push('
|
|
3939
|
-
lines.push('echo === HUONG DAN DANG NHAP ZALO ===');
|
|
3940
|
-
lines.push('echo Cua so Zalo Login se mo. Hay:');
|
|
3941
|
-
lines.push('echo 1. Doi QR hien ra trong cua so Zalo Login');
|
|
3942
|
-
lines.push('echo 2. Mo app Zalo, chon Quet QR va quet ma');
|
|
3943
|
-
lines.push('echo 3. Doi thay chu "Login successful" trong cua so do');
|
|
3944
|
-
lines.push('echo 4. Dong cua so Zalo Login');
|
|
3945
|
-
lines.push('echo ================================');
|
|
3946
|
-
lines.push('echo.');
|
|
3947
|
-
lines.push('start "Zalo Login" cmd /k "cd /d \"%PROJECT_DIR%\" && set OPENCLAW_HOME=%OPENCLAW_HOME% && set OPENCLAW_STATE_DIR=%OPENCLAW_HOME% && openclaw channels login --channel zalouser --verbose"');
|
|
3948
|
-
lines.push('echo Nhan phim bat ky sau khi dong cua so Zalo Login...');
|
|
3949
|
-
lines.push('pause >nul');
|
|
4061
|
+
lines.push(...generateZaloLoginBat({ homeVar: '%OPENCLAW_HOME%', projectDirVar: '%PROJECT_DIR%', label: 'win' }));
|
|
3950
4062
|
lines.push('call openclaw gateway stop 2>nul');
|
|
3951
4063
|
lines.push('timeout /t 2 /nobreak >nul');
|
|
3952
4064
|
lines.push('echo [6/6] Khoi dong bot...');
|
|
@@ -4008,6 +4120,28 @@ function generateMacOsSh(ctx) {
|
|
|
4008
4120
|
sh.push('if docker compose version > /dev/null 2>&1; then COMPOSE="docker compose"; else COMPOSE="docker-compose"; fi');
|
|
4009
4121
|
sh.push('cd docker/openclaw');
|
|
4010
4122
|
sh.push('$COMPOSE up --detach --build');
|
|
4123
|
+
if (state.channel === 'zalo-personal') {
|
|
4124
|
+
const containerName = 'openclaw-bot';
|
|
4125
|
+
const qrPath = '/tmp/openclaw/openclaw-zalouser-qr-default.png';
|
|
4126
|
+
sh.push('echo ""');
|
|
4127
|
+
sh.push('echo "=== DANG NHAP ZALO ==="');
|
|
4128
|
+
sh.push('echo "Doi container khoi dong 10 giay..."');
|
|
4129
|
+
sh.push('sleep 10');
|
|
4130
|
+
sh.push('echo "Dung gateway de login..."');
|
|
4131
|
+
sh.push(`docker exec ${containerName} openclaw gateway stop 2>/dev/null || true`);
|
|
4132
|
+
sh.push('sleep 3');
|
|
4133
|
+
sh.push('echo "Huong dan dang nhap Zalo:"');
|
|
4134
|
+
sh.push(`echo " 1. cd docker/openclaw"`);
|
|
4135
|
+
sh.push(`echo " 2. docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose"`);
|
|
4136
|
+
sh.push(`echo " 3. Tim file QR trong container: ${qrPath}"`);
|
|
4137
|
+
sh.push(`echo " Hoac chay: docker cp ${containerName}:${qrPath} ./zalo-qr.png"`);
|
|
4138
|
+
sh.push('echo " 4. Mo app Zalo > Quet QR > quet ma"');
|
|
4139
|
+
sh.push('echo " 5. Doi thay Login successful"');
|
|
4140
|
+
sh.push('echo " 6. Restart: $COMPOSE restart"');
|
|
4141
|
+
sh.push('echo ""');
|
|
4142
|
+
sh.push(`docker exec -it ${containerName} openclaw channels login --channel zalouser --verbose || true`);
|
|
4143
|
+
sh.push('$COMPOSE restart');
|
|
4144
|
+
}
|
|
4011
4145
|
sh.push('echo "\u2705 Bot dang chay via Docker. Xem log: docker logs -f openclaw-bot"');
|
|
4012
4146
|
scriptContent = sh.filter(Boolean).join('\n');
|
|
4013
4147
|
} else {
|
|
@@ -4041,13 +4175,19 @@ function generateMacOsSh(ctx) {
|
|
|
4041
4175
|
appendShWriteCommands(sh, sharedNativeFileMap());
|
|
4042
4176
|
const _uninstallMacMulti = generateUninstallScript();
|
|
4043
4177
|
if (_uninstallMacMulti) appendShWriteCommands(sh, { [_uninstallMacMulti.name]: _uninstallMacMulti.content });
|
|
4044
|
-
|
|
4045
|
-
|
|
4178
|
+
sh.push('echo "Starting shared multi-bot gateway..."');
|
|
4179
|
+
if (state.channel === 'zalo-personal') {
|
|
4180
|
+
sh.push(...generateZaloLoginSh({ homeVar: '$OPENCLAW_HOME', projectDirVar: '$PROJECT_DIR' }));
|
|
4181
|
+
}
|
|
4182
|
+
sh.push('openclaw gateway run');
|
|
4046
4183
|
} else {
|
|
4047
4184
|
appendShWriteCommands(sh, botFiles(0));
|
|
4048
4185
|
const _uninstallMac = generateUninstallScript();
|
|
4049
4186
|
if (_uninstallMac) appendShWriteCommands(sh, { [_uninstallMac.name]: _uninstallMac.content });
|
|
4050
|
-
|
|
4187
|
+
if (state.channel === 'zalo-personal') {
|
|
4188
|
+
sh.push(...generateZaloLoginSh({ homeVar: '$OPENCLAW_HOME', projectDirVar: '$PROJECT_DIR' }));
|
|
4189
|
+
}
|
|
4190
|
+
sh.push('openclaw gateway run');
|
|
4051
4191
|
}
|
|
4052
4192
|
scriptContent = sh.filter(Boolean).join('\n');
|
|
4053
4193
|
}
|
|
@@ -4149,6 +4289,11 @@ GWEOF`);
|
|
|
4149
4289
|
vps.push(`pm2 start "$PROJECT_DIR/.9router/9router-smart-route-sync.js" --name ${appName}-9router-sync --interpreter "$NODE_BIN" --env DATA_DIR="$DATA_DIR"`);
|
|
4150
4290
|
}
|
|
4151
4291
|
|
|
4292
|
+
// ── Zalo Personal Login (before gateway) ──────────────────────────────
|
|
4293
|
+
if (state.channel === 'zalo-personal') {
|
|
4294
|
+
vps.push(...generateZaloLoginSh({ homeVar: '$OPENCLAW_HOME', projectDirVar: '$PROJECT_DIR' }));
|
|
4295
|
+
}
|
|
4296
|
+
|
|
4152
4297
|
// Gateway: start the bash wrapper script (has all env setup baked in)
|
|
4153
4298
|
vps.push(`pm2 start "$PROJECT_DIR/.openclaw/start-gateway.sh" --name ${appName} --interpreter bash`);
|
|
4154
4299
|
vps.push('pm2 save && pm2 startup');
|
|
@@ -4220,11 +4365,17 @@ function generateLinuxSh(ctx) {
|
|
|
4220
4365
|
const _uninstallLnxMulti = generateUninstallScript();
|
|
4221
4366
|
if (_uninstallLnxMulti) appendShWriteCommands(lnx, { [_uninstallLnxMulti.name]: _uninstallLnxMulti.content });
|
|
4222
4367
|
lnx.push('echo "Starting shared multi-bot gateway..."');
|
|
4368
|
+
if (state.channel === 'zalo-personal') {
|
|
4369
|
+
lnx.push(...generateZaloLoginSh({ homeVar: '$OPENCLAW_HOME', projectDirVar: '$PROJECT_DIR' }));
|
|
4370
|
+
}
|
|
4223
4371
|
lnx.push('openclaw gateway run');
|
|
4224
4372
|
} else {
|
|
4225
4373
|
appendShWriteCommands(lnx, botFiles(0));
|
|
4226
4374
|
const _uninstallLnx = generateUninstallScript();
|
|
4227
4375
|
if (_uninstallLnx) appendShWriteCommands(lnx, { [_uninstallLnx.name]: _uninstallLnx.content });
|
|
4376
|
+
if (state.channel === 'zalo-personal') {
|
|
4377
|
+
lnx.push(...generateZaloLoginSh({ homeVar: '$OPENCLAW_HOME', projectDirVar: '$PROJECT_DIR' }));
|
|
4378
|
+
}
|
|
4228
4379
|
lnx.push('openclaw gateway run');
|
|
4229
4380
|
}
|
|
4230
4381
|
scriptContent = lnx.filter(Boolean).join('\n');
|
|
@@ -5749,6 +5900,7 @@ model:
|
|
|
5749
5900
|
// 3. Dockerfile + docker-compose.yml
|
|
5750
5901
|
const allPlugins = [];
|
|
5751
5902
|
if (ch.pluginInstall) allPlugins.push(ch.pluginInstall);
|
|
5903
|
+
if (ch.hasZaloPersonal) allPlugins.push('openclaw-zalo-mod');
|
|
5752
5904
|
state.config.plugins.forEach((pid) => {
|
|
5753
5905
|
const plug = PLUGINS.find((p) => p.id === pid);
|
|
5754
5906
|
if (plug) allPlugins.push(plug.package);
|
|
@@ -5762,10 +5914,15 @@ model:
|
|
|
5762
5914
|
});
|
|
5763
5915
|
const dockerGen = globalThis.__openclawDockerGen;
|
|
5764
5916
|
const relayPluginInstallCmd = isMultiBot ? buildRelayPluginInstallCommand('openclaw') : '';
|
|
5917
|
+
const pluginRuntimeSpecs = allPlugins.filter((p) => p && p !== '@openclaw/zalouser');
|
|
5918
|
+
const pluginIdForSpec = (spec) => {
|
|
5919
|
+
if (String(spec).includes('zalo-mod')) return 'zalo-mod';
|
|
5920
|
+
return String(spec).replace(/^@openclaw\//, '').replace(/^openclaw-/, '');
|
|
5921
|
+
};
|
|
5765
5922
|
const pluginInstallCmd = [
|
|
5766
|
-
...
|
|
5923
|
+
...pluginRuntimeSpecs.map((p) => `ensure_plugin ${pluginIdForSpec(p)} ${p}`),
|
|
5767
5924
|
relayPluginInstallCmd,
|
|
5768
|
-
].filter(Boolean).join('
|
|
5925
|
+
].filter(Boolean).join('\n') || '';
|
|
5769
5926
|
const dockerArtifacts = dockerGen.buildDockerArtifacts({
|
|
5770
5927
|
openClawNpmSpec: common.OPENCLAW_NPM_SPEC,
|
|
5771
5928
|
openClawRuntimePackages,
|
|
@@ -5776,6 +5933,7 @@ model:
|
|
|
5776
5933
|
selectedModel: state.config.model || 'ollama/gemma4:e2b',
|
|
5777
5934
|
agentId: 'bot',
|
|
5778
5935
|
allSkills,
|
|
5936
|
+
dockerfilePlugins: [],
|
|
5779
5937
|
dockerfileSkillInstallMode: 'build',
|
|
5780
5938
|
runtimeCommandParts: [
|
|
5781
5939
|
pluginInstallCmd,
|