create-openclaw-bot 5.8.2 → 5.8.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 +2 -2
- package/README.vi.md +2 -2
- package/dist/server/local-server.js +122 -80
- package/dist/setup/shared/workspace-gen.js +12 -2
- package/dist/setup.js +12 -2
- package/dist/web/app.js +5 -2
- 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.8.
|
|
6
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.8.3-0EA5E9?style=for-the-badge" alt="Version 5.8.3" /></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>
|
|
@@ -23,7 +23,7 @@ A next-generation **Web UI Setup** and management dashboard that automates 100%
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
-
## 🆕 What's New in v5.8.
|
|
26
|
+
## 🆕 What's New in v5.8.3
|
|
27
27
|
|
|
28
28
|
- 🔄 **Smart Header Update Button**: Instantly upgrades the setup wizard from the UI! The button queries the npm registry dynamically and only reveals itself when a newer release is published.
|
|
29
29
|
- 📡 **Live Log-Streaming Upgrade**: Kicking off the update automatically executes the migration (running `git pull && npm install && npm run build` for Git clones, or `npm install -g create-openclaw-bot` for NPM installs) while streaming standard outputs in real-time straight to the dashboard's Logs terminal.
|
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.8.
|
|
6
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.8.3-0EA5E9?style=for-the-badge" alt="Version 5.8.3" /></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>
|
|
@@ -23,7 +23,7 @@ Trình cài đặt và quản trị **Web UI Setup** thế hệ mới giúp tự
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
-
## 🆕 Có gì mới trong v5.8.
|
|
26
|
+
## 🆕 Có gì mới trong v5.8.3
|
|
27
27
|
|
|
28
28
|
- 🔄 **Nút cập nhật Header thông minh**: Nâng cấp trực tiếp setup wizard từ giao diện! Nút cập nhật tự truy vấn npm registry và chỉ hiển thị khi có phiên bản mới hơn.
|
|
29
29
|
- 📡 **Nâng cấp Stream Log trực tiếp**: Khởi chạy cập nhật sẽ tự động nâng cấp (chạy `git pull && npm install && npm run build` cho bản Git, hoặc `npm install -g create-openclaw-bot` cho bản NPM) và đẩy dòng log theo thời gian thực về tab Logs.
|
|
@@ -74,6 +74,30 @@ function detectOs() {
|
|
|
74
74
|
return 'linux-desktop';
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// Blacklist of Windows system/large directories that should never be walked
|
|
78
|
+
const SYSTEM_DIR_BLACKLIST = new Set([
|
|
79
|
+
'windows', 'program files', 'program files (x86)', 'programdata',
|
|
80
|
+
'$recycle.bin', 'system volume information', 'recovery', 'boot',
|
|
81
|
+
'perflogs', 'msocache', 'intel', 'amd', 'nvidia',
|
|
82
|
+
'$windows.~bt', '$windows.~ws', 'config.msi', 'documents and settings',
|
|
83
|
+
'swapfile.sys', 'pagefile.sys', 'hiberfil.sys',
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
/** Discover all available drive letters on Windows (A-Z). Returns ['C:\\', 'D:\\', ...] */
|
|
87
|
+
async function getAvailableDrives() {
|
|
88
|
+
if (process.platform !== 'win32') return ['/'];
|
|
89
|
+
const drives = [];
|
|
90
|
+
for (let code = 65; code <= 90; code++) { // A-Z
|
|
91
|
+
const letter = String.fromCharCode(code);
|
|
92
|
+
const drive = `${letter}:\\`;
|
|
93
|
+
try {
|
|
94
|
+
await fsp.access(drive);
|
|
95
|
+
drives.push(drive);
|
|
96
|
+
} catch {}
|
|
97
|
+
}
|
|
98
|
+
return drives.length ? drives : ['C:\\', 'D:\\'];
|
|
99
|
+
}
|
|
100
|
+
|
|
77
101
|
function recommendedMode(osChoice) {
|
|
78
102
|
if (osChoice === 'win' || osChoice === 'macos') return 'docker';
|
|
79
103
|
return 'native';
|
|
@@ -566,6 +590,26 @@ function ensureZaloApiChannel(cfg, token) {
|
|
|
566
590
|
});
|
|
567
591
|
}
|
|
568
592
|
|
|
593
|
+
function ensureZaloModPluginConfig(entry, cfg) {
|
|
594
|
+
entry.hooks = entry.hooks || {};
|
|
595
|
+
entry.hooks.allowConversationAccess = true;
|
|
596
|
+
entry.config = entry.config || {};
|
|
597
|
+
// Auto-assign dashboardPort = gateway port + 1
|
|
598
|
+
if (!entry.config.dashboardPort) {
|
|
599
|
+
const gwPort = Number(cfg.gateway?.port) || state.gatewayPort || 18789;
|
|
600
|
+
entry.config.dashboardPort = gwPort + 1;
|
|
601
|
+
}
|
|
602
|
+
// Auto-assign botName from first agent name
|
|
603
|
+
if (!entry.config.botName) {
|
|
604
|
+
const agentName = cfg.agents?.list?.[0]?.name;
|
|
605
|
+
if (agentName) entry.config.botName = agentName;
|
|
606
|
+
}
|
|
607
|
+
// Auto-assign zaloDisplayNames from botName
|
|
608
|
+
if ((!entry.config.zaloDisplayNames || entry.config.zaloDisplayNames.length === 0) && entry.config.botName) {
|
|
609
|
+
entry.config.zaloDisplayNames = [entry.config.botName];
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
569
613
|
function readProjectConfig(projectDir) {
|
|
570
614
|
const cfgPath = join(projectDir || '', '.openclaw', 'openclaw.json');
|
|
571
615
|
if (!projectDir || !existsSync(cfgPath)) return null;
|
|
@@ -935,6 +979,7 @@ async function createBotInProject(projectDir, body = {}, runtime = {}) {
|
|
|
935
979
|
if (existsSync(cfgPath)) await fsp.copyFile(cfgPath, `${cfgPath}.bak`);
|
|
936
980
|
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
937
981
|
|
|
982
|
+
const hasScheduler = !!(cfg.tools?.alsoAllow || []).includes('group:automation');
|
|
938
983
|
const files = buildWorkspaceFileMap({
|
|
939
984
|
isVi: true,
|
|
940
985
|
botName,
|
|
@@ -945,6 +990,7 @@ async function createBotInProject(projectDir, body = {}, runtime = {}) {
|
|
|
945
990
|
agentWorkspaceDir: workspaceDir,
|
|
946
991
|
workspacePath: `.openclaw/${workspaceDir}`,
|
|
947
992
|
hasZaloMod: channel === 'zalo-personal',
|
|
993
|
+
hasScheduler,
|
|
948
994
|
});
|
|
949
995
|
const wsRoot = join(openclawHome, workspaceDir);
|
|
950
996
|
for (const [name, content] of Object.entries(files)) {
|
|
@@ -1033,6 +1079,7 @@ async function updateBotInProject(projectDir, agentId, body = {}, runtime = {})
|
|
|
1033
1079
|
}
|
|
1034
1080
|
}
|
|
1035
1081
|
|
|
1082
|
+
const hasScheduler = !!(cfg.tools?.alsoAllow || []).includes('group:automation');
|
|
1036
1083
|
const files = buildWorkspaceFileMap({
|
|
1037
1084
|
isVi: true,
|
|
1038
1085
|
botName,
|
|
@@ -1043,6 +1090,7 @@ async function updateBotInProject(projectDir, agentId, body = {}, runtime = {})
|
|
|
1043
1090
|
agentWorkspaceDir: workspaceDir,
|
|
1044
1091
|
workspacePath: `.openclaw/${workspaceDir}`,
|
|
1045
1092
|
hasZaloMod: channel === 'zalo-personal',
|
|
1093
|
+
hasScheduler,
|
|
1046
1094
|
});
|
|
1047
1095
|
const wsRoot = join(projectDir, '.openclaw', workspaceDir);
|
|
1048
1096
|
for (const [name, content] of Object.entries(files)) {
|
|
@@ -1388,6 +1436,23 @@ async function syncDockerInfra(projectDir, force = false) {
|
|
|
1388
1436
|
sendLog(`[sync] Updating Docker infrastructure files (v${existingVersion} \u2192 v${SETUP_VERSION})`);
|
|
1389
1437
|
await fsp.writeFile(join(dockerDir, 'Dockerfile'), docker.dockerfile, 'utf8');
|
|
1390
1438
|
await fsp.writeFile(join(dockerDir, 'docker-compose.yml'), newCompose, 'utf8');
|
|
1439
|
+
// Preserve zalo-mod dashboard port if plugin is active
|
|
1440
|
+
try {
|
|
1441
|
+
const syncCfg = JSON.parse(await fsp.readFile(cfgPath, 'utf8'));
|
|
1442
|
+
const zmEntry = syncCfg.plugins?.entries?.['zalo-mod'] || syncCfg.plugins?.entries?.['openclaw-zalo-mod'];
|
|
1443
|
+
if (zmEntry?.enabled !== false && zmEntry?.config?.dashboardPort) {
|
|
1444
|
+
const dp = zmEntry.config.dashboardPort;
|
|
1445
|
+
let cc = await fsp.readFile(join(dockerDir, 'docker-compose.yml'), 'utf8');
|
|
1446
|
+
if (!cc.includes(`:${dp}`)) {
|
|
1447
|
+
const gpStr = String(gatewayPort);
|
|
1448
|
+
cc = cc.replace(
|
|
1449
|
+
new RegExp(`^(\\s*-\\s*"(?:\\d+:)?${gpStr}(?::${gpStr})?"\\s*)$`, 'm'),
|
|
1450
|
+
`$1\n - "127.0.0.1:${dp}:${dp}" # zalo-mod dashboard`
|
|
1451
|
+
);
|
|
1452
|
+
await fsp.writeFile(join(dockerDir, 'docker-compose.yml'), cc, 'utf8');
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
} catch {}
|
|
1391
1456
|
await fsp.writeFile(entrypointPath, entryScript, 'utf8');
|
|
1392
1457
|
if (docker.syncScript) await fsp.writeFile(join(dockerDir, 'sync.js'), docker.syncScript, 'utf8');
|
|
1393
1458
|
if (docker.patchScript) await fsp.writeFile(join(dockerDir, 'patch-9router.js'), docker.patchScript, 'utf8');
|
|
@@ -1678,11 +1743,16 @@ async function findLatestProject(rootProjectDir) {
|
|
|
1678
1743
|
join(rootProjectDir, DEFAULT_PROJECT_NAME),
|
|
1679
1744
|
dirname(rootProjectDir),
|
|
1680
1745
|
os.homedir(),
|
|
1681
|
-
'D:\\tmp',
|
|
1682
1746
|
];
|
|
1683
|
-
|
|
1747
|
+
// Scan all available drives, walking top-level dirs but skipping system folders
|
|
1748
|
+
const drives = await getAvailableDrives();
|
|
1749
|
+
for (const drive of drives) {
|
|
1684
1750
|
const entries = await fsp.readdir(drive, { withFileTypes: true }).catch(() => []);
|
|
1685
|
-
for (const e of entries)
|
|
1751
|
+
for (const e of entries) {
|
|
1752
|
+
if (e.isDirectory() && !e.name.startsWith('$') && !SYSTEM_DIR_BLACKLIST.has(e.name.toLowerCase())) {
|
|
1753
|
+
roots.push(join(drive, e.name));
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1686
1756
|
}
|
|
1687
1757
|
const candidates = [];
|
|
1688
1758
|
async function walk(dir, depth = 0) {
|
|
@@ -1693,7 +1763,11 @@ async function findLatestProject(rootProjectDir) {
|
|
|
1693
1763
|
return;
|
|
1694
1764
|
}
|
|
1695
1765
|
const entries = await fsp.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
1696
|
-
for (const e of entries)
|
|
1766
|
+
for (const e of entries) {
|
|
1767
|
+
if (e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules' && !SYSTEM_DIR_BLACKLIST.has(e.name.toLowerCase())) {
|
|
1768
|
+
await walk(join(dir, e.name), depth + 1);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1697
1771
|
}
|
|
1698
1772
|
for (const r of roots) await walk(r);
|
|
1699
1773
|
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
@@ -1706,10 +1780,10 @@ async function discoverProjects(rootProjectDir) {
|
|
|
1706
1780
|
rootProjectDir,
|
|
1707
1781
|
dirname(rootProjectDir),
|
|
1708
1782
|
process.env.OPENCLAW_HOME ? dirname(process.env.OPENCLAW_HOME) : '',
|
|
1709
|
-
'D:\\tmp',
|
|
1710
|
-
'D:\\',
|
|
1711
|
-
'E:\\',
|
|
1712
1783
|
];
|
|
1784
|
+
// Add all available drives for scanning
|
|
1785
|
+
const drives = await getAvailableDrives();
|
|
1786
|
+
for (const drive of drives) roots.push(drive);
|
|
1713
1787
|
const seen = new Set();
|
|
1714
1788
|
const hits = [];
|
|
1715
1789
|
async function walk(dir, depth = 0) {
|
|
@@ -1743,7 +1817,7 @@ async function discoverProjects(rootProjectDir) {
|
|
|
1743
1817
|
const entries = await fsp.readdir(full, { withFileTypes: true }).catch(() => []);
|
|
1744
1818
|
for (const e of entries) {
|
|
1745
1819
|
if (!e.isDirectory()) continue;
|
|
1746
|
-
if (e.name === 'node_modules' || e.name.startsWith('.git')) continue;
|
|
1820
|
+
if (e.name === 'node_modules' || e.name.startsWith('.git') || SYSTEM_DIR_BLACKLIST.has(e.name.toLowerCase())) continue;
|
|
1747
1821
|
await walk(join(full, e.name), depth + 1);
|
|
1748
1822
|
}
|
|
1749
1823
|
}
|
|
@@ -1987,9 +2061,13 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
|
|
|
1987
2061
|
cfg.commands.ownerAllowFrom = Array.from(new Set([...(cfg.commands.ownerAllowFrom || []), '*']));
|
|
1988
2062
|
const cronGuide = `## ⏰ Cron / Lên lịch nhắc nhở (tool: \`cron\`)
|
|
1989
2063
|
- **Tên tool chính xác:** Tên công cụ là \`cron\` (tuyệt đối không nhầm là \`native\` hay command line bên ngoài).
|
|
2064
|
+
- **⛔ TUYỆT ĐỐI KHÔNG sửa trực tiếp file JSON** như \`jobs.json\`, \`jobs-state.json\` trong thư mục \`.openclaw/cron/\`. Dữ liệu cron được lưu trong SQLite database, file JSON chỉ là legacy format đã ngưng hỗ trợ. Mọi thao tác PHẢI thông qua tool \`cron\`.
|
|
1990
2065
|
- **Khi tạo cronjob mới (action \`add\`):**
|
|
1991
2066
|
- **TUYỆT ĐỐI KHÔNG điền trường \`agentId\`** trong object \`job\` (hãy bỏ qua/omitted trường này). Hệ thống OpenClaw sẽ tự động gán chính xác ID của bạn vào job đó.
|
|
1992
2067
|
- Tuyệt đối **không tự điền** \`agentId\` là \`"bot"\` hay \`"main"\`, vì làm vậy sẽ khiến cronjob thuộc về agent khác và bạn sẽ mất quyền kiểm soát/xóa nó sau này.
|
|
2068
|
+
- **Session:** Luôn dùng \`sessionTarget: "isolated"\` cho các job chạy nền (báo cáo, nhắc nhở, gửi tin nhắn tự động). Chỉ dùng \`"main"\` cho system event/reminder ngắn.
|
|
2069
|
+
- **Timezone:** Luôn chỉ định timezone rõ ràng bằng trường \`tz\` (ví dụ: \`"Asia/Ho_Chi_Minh"\`). Nếu không chỉ định, hệ thống sẽ dùng timezone của Gateway host (thường là UTC) và job sẽ chạy sai giờ.
|
|
2070
|
+
- **Delivery:** Đối với job cần gửi kết quả ra chat, set \`delivery.mode: "announce"\` kèm \`delivery.channel\` và \`delivery.to\`.
|
|
1993
2071
|
- **Khi user yêu cầu tắt/bật/xóa cronjob:**
|
|
1994
2072
|
1. **Bước 1 (Tìm kiếm):** Gọi tool \`cron\` với action \`list\` (và \`includeDisabled: true\`) để xem danh sách tất cả cronjob đang chạy trên hệ thống và tìm đúng \`jobId\` phù hợp với yêu cầu.
|
|
1995
2073
|
2. **Bước 2 (Xử lý):**
|
|
@@ -1999,7 +2077,8 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
|
|
|
1999
2077
|
3. **Tuyên bố trung thực:** Tuyệt đối không bao giờ trả lời "đã xóa" hay "không có" dựa trên suy đoán của bản thân mà chưa gọi tool \`cron\` để kiểm tra thực tế.
|
|
2000
2078
|
- Khi user yêu cầu tạo nhắc nhở / lệnh tự động định kỳ, bạn hãy TỰ ĐỘNG dùng tool \`cron\` (action \`add\`) để tạo. **Tuyệt đối không** bắt user dùng crontab hay Task Scheduler chạy tay trên host.
|
|
2001
2079
|
- Khi thao tác tool cho cron/scheduler, **không điền \`current\` vào thư mục Session**.
|
|
2002
|
-
- **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố
|
|
2080
|
+
- **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố thích hợp trước ID của group. Với kênh Telegram/Matrix/Discord/Slack, dùng tiền tố \`group:\` (ví dụ: \`group:123456\`). RIÊNG với kênh Zalo (\`zalouser\`), **bắt buộc** phải sử dụng tiền tố \`g:\` (ví dụ: \`g:3815464776067464419\`) để tránh bị OpenClaw core lược bỏ tiền tố và gửi nhầm vào DM chat cá nhân.
|
|
2081
|
+
- **One-shot job:** Dùng schedule kind \`"at"\` với ISO 8601 timestamp. Job sẽ tự xóa sau khi chạy thành công trừ khi set \`deleteAfterRun: false\`.
|
|
2003
2082
|
- Bỏ qua việc tra cứu docs nội bộ như \`cron-jobs.mdx\`; tin tưởng khả năng dùng tool hiện có để hoàn thành yêu cầu.`;
|
|
2004
2083
|
for (const a of cfg.agents.list) {
|
|
2005
2084
|
const tf = await readWorkspaceText(projectDir, a, 'TOOLS.md');
|
|
@@ -2038,13 +2117,31 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
|
|
|
2038
2117
|
const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
|
|
2039
2118
|
cfg.plugins.entries[existingKey] = cfg.plugins.entries[existingKey] || {};
|
|
2040
2119
|
cfg.plugins.entries[existingKey].enabled = !!enabled;
|
|
2041
|
-
if (existingKey === 'zalo-mod') {
|
|
2042
|
-
cfg.plugins.entries[existingKey]
|
|
2043
|
-
cfg.plugins.entries[existingKey].hooks.allowConversationAccess = true;
|
|
2120
|
+
if (existingKey === 'zalo-mod' || existingKey === 'openclaw-zalo-mod') {
|
|
2121
|
+
ensureZaloModPluginConfig(cfg.plugins.entries[existingKey], cfg);
|
|
2044
2122
|
}
|
|
2045
2123
|
// Only add the canonical config key to allow list (not all aliases)
|
|
2046
2124
|
cfg.plugins.allow = cfg.plugins.allow || [];
|
|
2047
2125
|
if (!cfg.plugins.allow.includes(existingKey)) cfg.plugins.allow.push(existingKey);
|
|
2126
|
+
// Auto-expose zalo-mod dashboard port in docker-compose.yml when enabling
|
|
2127
|
+
if (enabled && (existingKey === 'zalo-mod' || existingKey === 'openclaw-zalo-mod')) {
|
|
2128
|
+
const composeFile = join(projectDir, 'docker', 'openclaw', 'docker-compose.yml');
|
|
2129
|
+
if (existsSync(composeFile)) {
|
|
2130
|
+
try {
|
|
2131
|
+
let composeContent = await fsp.readFile(composeFile, 'utf8');
|
|
2132
|
+
const dashPort = cfg.plugins.entries[existingKey].config?.dashboardPort;
|
|
2133
|
+
if (dashPort && !composeContent.includes(`:${dashPort}`)) {
|
|
2134
|
+
const gwPortStr = String(Number(cfg.gateway?.port) || state.gatewayPort || 18789);
|
|
2135
|
+
composeContent = composeContent.replace(
|
|
2136
|
+
new RegExp(`^(\\s*-\\s*"(?:\\d+:)?${gwPortStr}(?::${gwPortStr})?"\\s*)$`, 'm'),
|
|
2137
|
+
`$1\n - "127.0.0.1:${dashPort}:${dashPort}" # zalo-mod dashboard`
|
|
2138
|
+
);
|
|
2139
|
+
await fsp.writeFile(composeFile, composeContent, 'utf8');
|
|
2140
|
+
sendLog(`[plugin] Added dashboard port ${dashPort} to docker-compose.yml`);
|
|
2141
|
+
}
|
|
2142
|
+
} catch (e) { sendLog(`[plugin] Warning: could not add dashboard port: ${e.message}`); }
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2048
2145
|
}
|
|
2049
2146
|
|
|
2050
2147
|
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
@@ -2062,8 +2159,15 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
2062
2159
|
|
|
2063
2160
|
if (composeDir) {
|
|
2064
2161
|
const botContainer = getBotContainerName(projectDir);
|
|
2162
|
+
sendLog(`[plugin] Installing/updating clawhub:${id} inside container ${botContainer}...`);
|
|
2065
2163
|
|
|
2066
|
-
|
|
2164
|
+
const cmd = `cd /root/project && openclaw plugins install clawhub:${id} --force`;
|
|
2165
|
+
const cmdOut = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', cmd], { cwd: projectDir, shell: false });
|
|
2166
|
+
|
|
2167
|
+
if (cmdOut) {
|
|
2168
|
+
for (const line of `${cmdOut.stdout}\n${cmdOut.stderr}`.split(/\r?\n/).filter(Boolean)) sendLog(line);
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2067
2171
|
const cfgPath = join(projectDir, '.openclaw', 'openclaw.json');
|
|
2068
2172
|
const pluginAliasMap = {
|
|
2069
2173
|
'openclaw-browser-automation': ['browser-automation', 'openclaw-browser-automation'],
|
|
@@ -2072,53 +2176,12 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
2072
2176
|
'openclaw-n8n-facebook-poster': ['openclaw-n8n-facebook-poster', 'openclaw-facebook-poster', 'facebook-poster'],
|
|
2073
2177
|
};
|
|
2074
2178
|
const aliases = pluginAliasMap[id] || [id];
|
|
2075
|
-
|
|
2076
|
-
if (existsSync(cfgPath)) {
|
|
2077
|
-
try {
|
|
2078
|
-
const cfg = ensureConfigShape(JSON.parse(await fsp.readFile(cfgPath, 'utf8')));
|
|
2079
|
-
cfg.plugins = cfg.plugins || { entries: {} };
|
|
2080
|
-
cfg.plugins.entries = cfg.plugins.entries || {};
|
|
2081
|
-
const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
|
|
2082
|
-
|
|
2083
|
-
if (cfg.plugins.entries[existingKey]?.enabled) {
|
|
2084
|
-
sendLog(`[plugin] Temporarily disabling ${existingKey} and restarting bot to release file locks...`);
|
|
2085
|
-
cfg.plugins.entries[existingKey].enabled = false;
|
|
2086
|
-
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
2087
|
-
await run('docker', ['restart', botContainer], { shell: false }).catch(() => {});
|
|
2088
|
-
// Sleep 2 seconds to let container fully boot and release locks
|
|
2089
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2090
|
-
}
|
|
2091
|
-
} catch (_) {}
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
sendLog(`[plugin] Installing clawhub:${id} inside container ${botContainer}...`);
|
|
2095
|
-
|
|
2096
|
-
let installSuccess = true;
|
|
2097
|
-
const cleanCmd = `cd /root/project && (openclaw plugins uninstall ${id} --force 2>/dev/null || true) && (openclaw plugins uninstall ${id.replace('openclaw-', '')} --force 2>/dev/null || true) && rm -rf .openclaw/extensions/${id} .openclaw/extensions/${id.replace('openclaw-', '')} && openclaw plugins install clawhub:${id}`;
|
|
2098
|
-
const cmdOut = await runCapture('docker', ['exec', botContainer, 'sh', '-lc', cleanCmd], { cwd: projectDir, shell: false });
|
|
2099
|
-
|
|
2100
|
-
if (cmdOut) {
|
|
2101
|
-
for (const line of `${cmdOut.stdout}\n${cmdOut.stderr}`.split(/\r?\n/).filter(Boolean)) sendLog(line);
|
|
2102
|
-
}
|
|
2103
2179
|
|
|
2104
2180
|
if (cmdOut.code !== 0) {
|
|
2105
2181
|
const folderExists = aliases.some((a) => existsSync(join(projectDir, '.openclaw', 'extensions', a)));
|
|
2106
2182
|
if (folderExists) {
|
|
2107
2183
|
sendLog(`[plugin] Warning: installation reported errors, but plugin folder successfully written. Proceeding.`);
|
|
2108
2184
|
} else {
|
|
2109
|
-
installSuccess = false;
|
|
2110
|
-
// Re-enable in config on failure to restore state
|
|
2111
|
-
if (existsSync(cfgPath)) {
|
|
2112
|
-
try {
|
|
2113
|
-
const cfg = ensureConfigShape(JSON.parse(await fsp.readFile(cfgPath, 'utf8')));
|
|
2114
|
-
cfg.plugins = cfg.plugins || { entries: {} };
|
|
2115
|
-
cfg.plugins.entries = cfg.plugins.entries || {};
|
|
2116
|
-
const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
|
|
2117
|
-
cfg.plugins.entries[existingKey] = cfg.plugins.entries[existingKey] || {};
|
|
2118
|
-
cfg.plugins.entries[existingKey].enabled = true;
|
|
2119
|
-
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
2120
|
-
} catch (_) {}
|
|
2121
|
-
}
|
|
2122
2185
|
throw new Error(cmdOut.stderr || cmdOut.stdout || `Failed to install plugin ${id} inside container.`);
|
|
2123
2186
|
}
|
|
2124
2187
|
}
|
|
@@ -2127,25 +2190,11 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
2127
2190
|
const cfg = ensureConfigShape(JSON.parse(await fsp.readFile(cfgPath, 'utf8')));
|
|
2128
2191
|
cfg.plugins = cfg.plugins || { entries: {} };
|
|
2129
2192
|
cfg.plugins.entries = cfg.plugins.entries || {};
|
|
2130
|
-
const pluginAliasMap = {
|
|
2131
|
-
'openclaw-browser-automation': ['browser-automation', 'openclaw-browser-automation'],
|
|
2132
|
-
'openclaw-zalo-mod': ['zalo-mod', 'openclaw-zalo-mod'],
|
|
2133
|
-
'openclaw-facebook-crawler': ['openclaw-facebook-crawler', 'openclaw-n8n-facebook-crawler', 'n8n-facebook-crawler'],
|
|
2134
|
-
'openclaw-n8n-facebook-poster': ['openclaw-n8n-facebook-poster', 'openclaw-facebook-poster', 'facebook-poster'],
|
|
2135
|
-
};
|
|
2136
|
-
const aliases = pluginAliasMap[id] || [id];
|
|
2137
2193
|
const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
|
|
2138
2194
|
cfg.plugins.entries[existingKey] = cfg.plugins.entries[existingKey] || {};
|
|
2139
2195
|
cfg.plugins.entries[existingKey].enabled = true;
|
|
2140
|
-
if (existingKey === 'zalo-mod') {
|
|
2141
|
-
cfg.plugins.entries[existingKey]
|
|
2142
|
-
cfg.plugins.entries[existingKey].hooks.allowConversationAccess = true;
|
|
2143
|
-
// Auto-assign dashboard port = gateway port + 1 to avoid conflicts between bots
|
|
2144
|
-
const gwPort = Number(cfg.gateway?.port) || state.gatewayPort || 18789;
|
|
2145
|
-
cfg.plugins.entries[existingKey].config = cfg.plugins.entries[existingKey].config || {};
|
|
2146
|
-
if (!cfg.plugins.entries[existingKey].config.dashboardPort) {
|
|
2147
|
-
cfg.plugins.entries[existingKey].config.dashboardPort = gwPort + 1;
|
|
2148
|
-
}
|
|
2196
|
+
if (existingKey === 'zalo-mod' || existingKey === 'openclaw-zalo-mod') {
|
|
2197
|
+
ensureZaloModPluginConfig(cfg.plugins.entries[existingKey], cfg);
|
|
2149
2198
|
}
|
|
2150
2199
|
// Only add the canonical config key to allow list (not all aliases)
|
|
2151
2200
|
if (!cfg.plugins.allow.includes(existingKey)) cfg.plugins.allow.push(existingKey);
|
|
@@ -2199,7 +2248,7 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
2199
2248
|
sendLog(`[plugin] Installing clawhub:${id}...`);
|
|
2200
2249
|
|
|
2201
2250
|
let installSuccess = true;
|
|
2202
|
-
await run('openclaw', ['plugins', 'install', `clawhub:${id}
|
|
2251
|
+
await run('openclaw', ['plugins', 'install', `clawhub:${id}`, '--force'], {
|
|
2203
2252
|
cwd: projectDir,
|
|
2204
2253
|
env: openclawProjectEnv(projectDir),
|
|
2205
2254
|
resolveOnPattern: /Installed plugin:/
|
|
@@ -2230,15 +2279,8 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
2230
2279
|
const existingKey = aliases.find((a) => cfg.plugins.entries[a]) || aliases[0];
|
|
2231
2280
|
cfg.plugins.entries[existingKey] = cfg.plugins.entries[existingKey] || {};
|
|
2232
2281
|
cfg.plugins.entries[existingKey].enabled = true;
|
|
2233
|
-
if (existingKey === 'zalo-mod') {
|
|
2234
|
-
cfg.plugins.entries[existingKey]
|
|
2235
|
-
cfg.plugins.entries[existingKey].hooks.allowConversationAccess = true;
|
|
2236
|
-
// Auto-assign dashboard port = gateway port + 1 to avoid conflicts between bots
|
|
2237
|
-
const gwPort = Number(cfg.gateway?.port) || state.gatewayPort || 18789;
|
|
2238
|
-
cfg.plugins.entries[existingKey].config = cfg.plugins.entries[existingKey].config || {};
|
|
2239
|
-
if (!cfg.plugins.entries[existingKey].config.dashboardPort) {
|
|
2240
|
-
cfg.plugins.entries[existingKey].config.dashboardPort = gwPort + 1;
|
|
2241
|
-
}
|
|
2282
|
+
if (existingKey === 'zalo-mod' || existingKey === 'openclaw-zalo-mod') {
|
|
2283
|
+
ensureZaloModPluginConfig(cfg.plugins.entries[existingKey], cfg);
|
|
2242
2284
|
}
|
|
2243
2285
|
await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2), 'utf8');
|
|
2244
2286
|
}
|
|
@@ -628,9 +628,13 @@ node ${btPath} close_tab 2`;
|
|
|
628
628
|
? (isVi
|
|
629
629
|
? `\n\n## \u23F0 Cron / Lên lịch nhắc nhở (tool: \`cron\`)
|
|
630
630
|
- **Tên tool chính xác:** Tên công cụ là \`cron\` (tuyệt đối không nhầm là \`native\` hay command line bên ngoài).
|
|
631
|
+
- **⛔ TUYỆT ĐỐI KHÔNG sửa trực tiếp file JSON** như \`jobs.json\`, \`jobs-state.json\` trong thư mục \`.openclaw/cron/\`. Dữ liệu cron được lưu trong SQLite database, file JSON chỉ là legacy format đã ngưng hỗ trợ. Mọi thao tác PHẢI thông qua tool \`cron\`.
|
|
631
632
|
- **Khi tạo cronjob mới (action \`add\`):**
|
|
632
633
|
- **TUYỆT ĐỐI KHÔNG điền trường \`agentId\`** trong object \`job\` (hãy bỏ qua/omitted trường này). Hệ thống OpenClaw sẽ tự động gán chính xác ID của bạn vào job đó.
|
|
633
634
|
- Tuyệt đối **không tự điền** \`agentId\` là \`"bot"\` hay \`"main"\`, vì làm vậy sẽ khiến cronjob thuộc về agent khác và bạn sẽ mất quyền kiểm soát/xóa nó sau này.
|
|
635
|
+
- **Session:** Luôn dùng \`sessionTarget: "isolated"\` cho các job chạy nền (báo cáo, nhắc nhở, gửi tin nhắn tự động). Chỉ dùng \`"main"\` cho system event/reminder ngắn.
|
|
636
|
+
- **Timezone:** Luôn chỉ định timezone rõ ràng bằng trường \`tz\` (ví dụ: \`"Asia/Ho_Chi_Minh"\`). Nếu không chỉ định, hệ thống sẽ dùng timezone của Gateway host (thường là UTC) và job sẽ chạy sai giờ.
|
|
637
|
+
- **Delivery:** Đối với job cần gửi kết quả ra chat, set \`delivery.mode: "announce"\` kèm \`delivery.channel\` và \`delivery.to\`.
|
|
634
638
|
- **Khi user yêu cầu tắt/bật/xóa cronjob:**
|
|
635
639
|
1. **Bước 1 (Tìm kiếm):** Gọi tool \`cron\` với action \`list\` (và \`includeDisabled: true\`) để xem danh sách tất cả cronjob đang chạy trên hệ thống và tìm đúng \`jobId\` phù hợp với yêu cầu.
|
|
636
640
|
2. **Bước 2 (Xử lý):**
|
|
@@ -640,13 +644,18 @@ node ${btPath} close_tab 2`;
|
|
|
640
644
|
3. **Tuyên bố trung thực:** Tuyệt đối không bao giờ trả lời "đã xóa" hay "không có" dựa trên suy đoán của bản thân mà chưa gọi tool \`cron\` để kiểm tra thực tế.
|
|
641
645
|
- Khi user yêu cầu tạo nhắc nhở / lệnh tự động định kỳ, bạn hãy TỰ ĐỘNG dùng tool \`cron\` (action \`add\`) để tạo. **Tuyệt đối không** bắt user dùng crontab hay Task Scheduler chạy tay trên host.
|
|
642
646
|
- Khi thao tác tool cho cron/scheduler, **không điền \`current\` vào thư mục Session**.
|
|
643
|
-
- **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố
|
|
647
|
+
- **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố thích hợp trước ID của group. Với kênh Telegram/Matrix/Discord/Slack, dùng tiền tố \`group:\` (ví dụ: \`group:123456\`). RIÊNG với kênh Zalo (\`zalouser\`), **bắt buộc** phải sử dụng tiền tố \`g:\` (ví dụ: \`g:3815464776067464419\`) để tránh bị OpenClaw core lược bỏ tiền tố và gửi nhầm vào DM chat cá nhân.
|
|
648
|
+
- **One-shot job:** Dùng schedule kind \`"at"\` với ISO 8601 timestamp. Job sẽ tự xóa sau khi chạy thành công trừ khi set \`deleteAfterRun: false\`.
|
|
644
649
|
- Bỏ qua việc tra cứu docs nội bộ như \`cron-jobs.mdx\`; tin tưởng khả năng dùng tool hiện có để hoàn thành yêu cầu.`
|
|
645
650
|
: `\n\n## \u23F0 Cron / Scheduled Tasks (tool: \`cron\`)
|
|
646
651
|
- **Exact tool name:** The tool name is \`cron\` (never mistake it for \`native\` or external command lines).
|
|
652
|
+
- **⛔ NEVER edit JSON files directly** such as \`jobs.json\` or \`jobs-state.json\` in \`.openclaw/cron/\`. Cron data is stored in SQLite database; JSON files are legacy format no longer supported. All operations MUST go through the \`cron\` tool.
|
|
647
653
|
- **When creating a new cronjob (action \`add\`):**
|
|
648
654
|
- **ABSOLUTELY DO NOT specify the \`agentId\` field** in the \`job\` object (leave this field omitted). The OpenClaw system will automatically assign your correct agent ID to that job.
|
|
649
655
|
- Never manually specify \`agentId\` as \`"bot"\` or \`"main"\`, as this will cause the cronjob to belong to another agent and you will lose control to manage/delete it later.
|
|
656
|
+
- **Session:** Always use \`sessionTarget: "isolated"\` for background jobs (reports, reminders, automated messages). Only use \`"main"\` for short system events/reminders.
|
|
657
|
+
- **Timezone:** Always specify timezone explicitly via the \`tz\` field (e.g., \`"Asia/Ho_Chi_Minh"\`). If omitted, the system uses the Gateway host timezone (often UTC) and the job will run at the wrong time.
|
|
658
|
+
- **Delivery:** For jobs that should send results to chat, set \`delivery.mode: "announce"\` with \`delivery.channel\` and \`delivery.to\`.
|
|
650
659
|
- **When the user requests to disable/enable/delete a cronjob:**
|
|
651
660
|
1. **Step 1 (Search):** Call the \`cron\` tool with action \`list\` (and \`includeDisabled: true\`) to view all cron jobs on the system and find the matching \`jobId\`.
|
|
652
661
|
2. **Step 2 (Processing):**
|
|
@@ -656,7 +665,8 @@ node ${btPath} close_tab 2`;
|
|
|
656
665
|
3. **Honest statement:** Never claim a job is "deleted" or "not found" based on guessing without calling the \`cron\` tool to verify the actual state.
|
|
657
666
|
- When the user asks to schedule tasks or reminders, use the built-in \`cron\` tool (action \`add\`) automatically. Do NOT ask users to run crontab or Task Scheduler manually on the host.
|
|
658
667
|
- When operating cron/scheduler tools, do **not** put \`current\` into the Session directory.
|
|
659
|
-
- **IMPORTANT ABOUT GROUP CHAT TARGETING**: When creating or configuring a cron job to send messages (announce mode) to a Group Chat, the value of the \`delivery.to\` field **must** use the
|
|
668
|
+
- **IMPORTANT ABOUT GROUP CHAT TARGETING**: When creating or configuring a cron job to send messages (announce mode) to a Group Chat, the value of the \`delivery.to\` field **must** use the appropriate prefix before the group ID. For Telegram/Matrix/Discord/Slack, use the \`group:\` prefix (e.g., \`group:123456\`). ESPECIALLY for Zalo (\`zalouser\`), you **must** use the \`g:\` prefix (e.g., \`g:3815464776067464419\`) to prevent the OpenClaw core from stripping the prefix and misrouting the message to a private DM.
|
|
669
|
+
- **One-shot jobs:** Use schedule kind \`"at"\` with an ISO 8601 timestamp. The job auto-deletes after successful run unless \`deleteAfterRun: false\` is set.
|
|
660
670
|
- Skip internal doc lookups such as \`cron-jobs.mdx\`; rely on the available tools and complete the scheduling task directly.`)
|
|
661
671
|
: '';
|
|
662
672
|
|
package/dist/setup.js
CHANGED
|
@@ -1561,9 +1561,13 @@ node ${btPath} close_tab 2`;
|
|
|
1561
1561
|
? (isVi
|
|
1562
1562
|
? `\n\n## \u23F0 Cron / Lên lịch nhắc nhở (tool: \`cron\`)
|
|
1563
1563
|
- **Tên tool chính xác:** Tên công cụ là \`cron\` (tuyệt đối không nhầm là \`native\` hay command line bên ngoài).
|
|
1564
|
+
- **⛔ TUYỆT ĐỐI KHÔNG sửa trực tiếp file JSON** như \`jobs.json\`, \`jobs-state.json\` trong thư mục \`.openclaw/cron/\`. Dữ liệu cron được lưu trong SQLite database, file JSON chỉ là legacy format đã ngưng hỗ trợ. Mọi thao tác PHẢI thông qua tool \`cron\`.
|
|
1564
1565
|
- **Khi tạo cronjob mới (action \`add\`):**
|
|
1565
1566
|
- **TUYỆT ĐỐI KHÔNG điền trường \`agentId\`** trong object \`job\` (hãy bỏ qua/omitted trường này). Hệ thống OpenClaw sẽ tự động gán chính xác ID của bạn vào job đó.
|
|
1566
1567
|
- Tuyệt đối **không tự điền** \`agentId\` là \`"bot"\` hay \`"main"\`, vì làm vậy sẽ khiến cronjob thuộc về agent khác và bạn sẽ mất quyền kiểm soát/xóa nó sau này.
|
|
1568
|
+
- **Session:** Luôn dùng \`sessionTarget: "isolated"\` cho các job chạy nền (báo cáo, nhắc nhở, gửi tin nhắn tự động). Chỉ dùng \`"main"\` cho system event/reminder ngắn.
|
|
1569
|
+
- **Timezone:** Luôn chỉ định timezone rõ ràng bằng trường \`tz\` (ví dụ: \`"Asia/Ho_Chi_Minh"\`). Nếu không chỉ định, hệ thống sẽ dùng timezone của Gateway host (thường là UTC) và job sẽ chạy sai giờ.
|
|
1570
|
+
- **Delivery:** Đối với job cần gửi kết quả ra chat, set \`delivery.mode: "announce"\` kèm \`delivery.channel\` và \`delivery.to\`.
|
|
1567
1571
|
- **Khi user yêu cầu tắt/bật/xóa cronjob:**
|
|
1568
1572
|
1. **Bước 1 (Tìm kiếm):** Gọi tool \`cron\` với action \`list\` (và \`includeDisabled: true\`) để xem danh sách tất cả cronjob đang chạy trên hệ thống và tìm đúng \`jobId\` phù hợp với yêu cầu.
|
|
1569
1573
|
2. **Bước 2 (Xử lý):**
|
|
@@ -1573,13 +1577,18 @@ node ${btPath} close_tab 2`;
|
|
|
1573
1577
|
3. **Tuyên bố trung thực:** Tuyệt đối không bao giờ trả lời "đã xóa" hay "không có" dựa trên suy đoán của bản thân mà chưa gọi tool \`cron\` để kiểm tra thực tế.
|
|
1574
1578
|
- Khi user yêu cầu tạo nhắc nhở / lệnh tự động định kỳ, bạn hãy TỰ ĐỘNG dùng tool \`cron\` (action \`add\`) để tạo. **Tuyệt đối không** bắt user dùng crontab hay Task Scheduler chạy tay trên host.
|
|
1575
1579
|
- Khi thao tác tool cho cron/scheduler, **không điền \`current\` vào thư mục Session**.
|
|
1576
|
-
- **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố
|
|
1580
|
+
- **QUAN TRỌNG VỀ TARGETING GROUP CHAT**: Khi tạo hoặc cấu hình cron job gửi tin nhắn thông báo (announce mode) đến một Group Chat, giá trị của trường \`delivery.to\` **bắt buộc** phải sử dụng tiền tố thích hợp trước ID của group. Với kênh Telegram/Matrix/Discord/Slack, dùng tiền tố \`group:\` (ví dụ: \`group:123456\`). RIÊNG với kênh Zalo (\`zalouser\`), **bắt buộc** phải sử dụng tiền tố \`g:\` (ví dụ: \`g:3815464776067464419\`) để tránh bị OpenClaw core lược bỏ tiền tố và gửi nhầm vào DM chat cá nhân.
|
|
1581
|
+
- **One-shot job:** Dùng schedule kind \`"at"\` với ISO 8601 timestamp. Job sẽ tự xóa sau khi chạy thành công trừ khi set \`deleteAfterRun: false\`.
|
|
1577
1582
|
- Bỏ qua việc tra cứu docs nội bộ như \`cron-jobs.mdx\`; tin tưởng khả năng dùng tool hiện có để hoàn thành yêu cầu.`
|
|
1578
1583
|
: `\n\n## \u23F0 Cron / Scheduled Tasks (tool: \`cron\`)
|
|
1579
1584
|
- **Exact tool name:** The tool name is \`cron\` (never mistake it for \`native\` or external command lines).
|
|
1585
|
+
- **⛔ NEVER edit JSON files directly** such as \`jobs.json\` or \`jobs-state.json\` in \`.openclaw/cron/\`. Cron data is stored in SQLite database; JSON files are legacy format no longer supported. All operations MUST go through the \`cron\` tool.
|
|
1580
1586
|
- **When creating a new cronjob (action \`add\`):**
|
|
1581
1587
|
- **ABSOLUTELY DO NOT specify the \`agentId\` field** in the \`job\` object (leave this field omitted). The OpenClaw system will automatically assign your correct agent ID to that job.
|
|
1582
1588
|
- Never manually specify \`agentId\` as \`"bot"\` or \`"main"\`, as this will cause the cronjob to belong to another agent and you will lose control to manage/delete it later.
|
|
1589
|
+
- **Session:** Always use \`sessionTarget: "isolated"\` for background jobs (reports, reminders, automated messages). Only use \`"main"\` for short system events/reminders.
|
|
1590
|
+
- **Timezone:** Always specify timezone explicitly via the \`tz\` field (e.g., \`"Asia/Ho_Chi_Minh"\`). If omitted, the system uses the Gateway host timezone (often UTC) and the job will run at the wrong time.
|
|
1591
|
+
- **Delivery:** For jobs that should send results to chat, set \`delivery.mode: "announce"\` with \`delivery.channel\` and \`delivery.to\`.
|
|
1583
1592
|
- **When the user requests to disable/enable/delete a cronjob:**
|
|
1584
1593
|
1. **Step 1 (Search):** Call the \`cron\` tool with action \`list\` (and \`includeDisabled: true\`) to view all cron jobs on the system and find the matching \`jobId\`.
|
|
1585
1594
|
2. **Step 2 (Processing):**
|
|
@@ -1589,7 +1598,8 @@ node ${btPath} close_tab 2`;
|
|
|
1589
1598
|
3. **Honest statement:** Never claim a job is "deleted" or "not found" based on guessing without calling the \`cron\` tool to verify the actual state.
|
|
1590
1599
|
- When the user asks to schedule tasks or reminders, use the built-in \`cron\` tool (action \`add\`) automatically. Do NOT ask users to run crontab or Task Scheduler manually on the host.
|
|
1591
1600
|
- When operating cron/scheduler tools, do **not** put \`current\` into the Session directory.
|
|
1592
|
-
- **IMPORTANT ABOUT GROUP CHAT TARGETING**: When creating or configuring a cron job to send messages (announce mode) to a Group Chat, the value of the \`delivery.to\` field **must** use the
|
|
1601
|
+
- **IMPORTANT ABOUT GROUP CHAT TARGETING**: When creating or configuring a cron job to send messages (announce mode) to a Group Chat, the value of the \`delivery.to\` field **must** use the appropriate prefix before the group ID. For Telegram/Matrix/Discord/Slack, use the \`group:\` prefix (e.g., \`group:123456\`). ESPECIALLY for Zalo (\`zalouser\`), you **must** use the \`g:\` prefix (e.g., \`g:3815464776067464419\`) to prevent the OpenClaw core from stripping the prefix and misrouting the message to a private DM.
|
|
1602
|
+
- **One-shot jobs:** Use schedule kind \`"at"\` with an ISO 8601 timestamp. The job auto-deletes after successful run unless \`deleteAfterRun: false\` is set.
|
|
1593
1603
|
- Skip internal doc lookups such as \`cron-jobs.mdx\`; rely on the available tools and complete the scheduling task directly.`)
|
|
1594
1604
|
: '';
|
|
1595
1605
|
|
package/dist/web/app.js
CHANGED
|
@@ -311,7 +311,7 @@ function render() {
|
|
|
311
311
|
if (!mainContainer) {
|
|
312
312
|
$('#app').innerHTML = `
|
|
313
313
|
<aside class="sidebar">
|
|
314
|
-
<div class="brand"><img src="/openclaw-logo.svg" onerror="this.src='/openclaw-logo.png'" alt="OpenClaw"/><div style="display: flex; flex-direction: column; align-items: center; text-align: center;"><b>OpenClaw Setup</b><span style="display: block; width: 100%; text-align: center; font-size: 13.5px; font-weight: 600; margin-top: 6px; color: var(--muted);">v${state.system?.versions?.setup || '
|
|
314
|
+
<div class="brand"><img src="/openclaw-logo.svg" onerror="this.src='/openclaw-logo.png'" alt="OpenClaw"/><div style="display: flex; flex-direction: column; align-items: center; text-align: center;"><b>OpenClaw Setup</b><span id="sidebar-version" style="display: block; width: 100%; text-align: center; font-size: 13.5px; font-weight: 600; margin-top: 6px; color: var(--muted);">v${state.system?.versions?.setup || '...'}</span></div></div>
|
|
315
315
|
<nav class="sidebar-nav">${tabs.map(([id,label]) => `<button class="nav ${state.tab===id?'active':''}" data-tab="${id}">${icon(id)}<span>${label}</span></button>`).join('')}</nav>
|
|
316
316
|
${sidebarExtras()}
|
|
317
317
|
</aside>
|
|
@@ -346,6 +346,9 @@ function render() {
|
|
|
346
346
|
const titleEl = $('#app-page-title');
|
|
347
347
|
if (titleEl) titleEl.innerHTML = title();
|
|
348
348
|
|
|
349
|
+
const sidebarVerEl = $('#sidebar-version');
|
|
350
|
+
if (sidebarVerEl) sidebarVerEl.textContent = `v${state.system?.versions?.setup || '...'}`;
|
|
351
|
+
|
|
349
352
|
const panelEl = $('.panel');
|
|
350
353
|
if (panelEl) panelEl.innerHTML = content();
|
|
351
354
|
|
|
@@ -777,7 +780,7 @@ function filesView() { return `<div class="files">${state.files.map(f=>`<article
|
|
|
777
780
|
function botSkillsPanel() {
|
|
778
781
|
const flags = state.featureFlags || {};
|
|
779
782
|
const skills = [
|
|
780
|
-
{ id: 'cron', title: 'Cron', desc: '
|
|
783
|
+
{ id: 'cron', title: 'Cron', desc: 'Native scheduler (SQLite) — cron guide in TOOLS.md' },
|
|
781
784
|
];
|
|
782
785
|
const plugins = [
|
|
783
786
|
{ id: 'openclaw-browser-automation', title: 'openclaw-browser-automation', desc: 'Smart Search + Browser (headless & Chrome thật)' },
|