create-openclaw-bot 5.13.8 → 5.14.0
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 +18 -3
- package/README.vi.md +18 -3
- package/dist/server/local-server.js +32 -24
- package/dist/setup/shared/bot-config-gen.js +46 -5
- package/dist/setup/shared/docker-gen.js +32 -0
- package/dist/setup/shared/workspace-gen.js +16 -0
- package/dist/web/app.js +2 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*Run one command → open the dashboard → your bot is live. Windows · macOS · Linux · VPS — Docker-powered, auto-installed for you.*
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.
|
|
10
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.14.0-0EA5E9?style=for-the-badge" alt="Version 5.14.0" /></a>
|
|
11
11
|
<a href="https://github.com/tuanminhhole/openclaw-setup?tab=MIT-1-ov-file"><img src="https://img.shields.io/badge/LICENSE-MIT-success?style=for-the-badge" alt="MIT License" /></a>
|
|
12
12
|
<a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/v/create-openclaw-bot?style=for-the-badge&label=CLI&color=2563EB&logo=npm&logoColor=white" alt="NPM Version" /></a>
|
|
13
13
|
<a href="https://github.com/tuanminhhole/openclaw-setup/stargazers"><img src="https://img.shields.io/github/stars/tuanminhhole/openclaw-setup?style=for-the-badge&color=eab308&logo=github&logoColor=white" alt="GitHub Stars" /></a>
|
|
@@ -37,11 +37,26 @@
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
-
## 🆕 What's New in v5.
|
|
40
|
+
## 🆕 What's New in v5.14.0
|
|
41
|
+
|
|
42
|
+
- 🛠️ **Bots build their own skills**: ask your bot for a reusable capability ("make me a skill for X") and it writes the skill and puts it to work in the same reply — no approval step, no config editing. The skill is saved to `skills/<name>/SKILL.md` in the bot's workspace and loads itself. **Existing bots pick this up automatically on update.**
|
|
43
|
+
|
|
44
|
+
<details>
|
|
45
|
+
<summary><b>Previous: What's new in v5.13.9</b></summary>
|
|
46
|
+
|
|
47
|
+
- 🧠 **Always-on memory for every bot (learning-memory)**: a new context-engine plugin injects your curated `MEMORY.md` + `USER.md` into **every** turn — including group chats, which the default recall skips. Bots stop forgetting context and rules over time: no more chiming in when nobody called them, or re-asking things you already told them. Auto-installed for new bots + a 1-click card in the dashboard.
|
|
48
|
+
- 🧹 **Retired TencentDB Agent Memory** in favor of the always-on engine above (no external service needed). Existing bots are auto-migrated on update — the old learning-memory *skill* and the TencentDB plugin are removed automatically.
|
|
49
|
+
|
|
50
|
+
</details>
|
|
51
|
+
|
|
52
|
+
<details>
|
|
53
|
+
<summary><b>Previous: What's new in v5.13.4</b></summary>
|
|
41
54
|
|
|
42
55
|
- ⚙️ **New Settings page**: pick your theme (light/dark toggle), language (VI/EN) and timezone right from the dashboard — available in both the sidebar and the mobile nav bar.
|
|
43
56
|
- 🕒 **Schedules & cron run in the correct local time**: newly created bots carry an explicit timezone, so reminders/cron no longer drift a day off around midnight (a UTC-vs-local bug that could schedule into the past). Scheduling guidance was hardened end-to-end (local time + timezone, explicit delivery target, raw group id).
|
|
44
57
|
|
|
58
|
+
</details>
|
|
59
|
+
|
|
45
60
|
<details>
|
|
46
61
|
<summary><b>Previous: What's new in v5.13.0</b></summary>
|
|
47
62
|
|
|
@@ -336,7 +351,7 @@ Companion repos to build a complete, self-running AI assistant:
|
|
|
336
351
|
- [openclaw-n8n-facebook-poster](https://github.com/tuanminhhole/openclaw-n8n-facebook-poster) — Auto-post to Facebook via n8n
|
|
337
352
|
|
|
338
353
|
**🧩 Skills**
|
|
339
|
-
- [openclaw-
|
|
354
|
+
- [openclaw-learning-memory](https://github.com/tuanminhhole/openclaw-learning-memory) — Always-on memory context engine (injects curated MEMORY.md + USER.md into every turn, incl. groups)
|
|
340
355
|
- [openclaw-skill-infographic](https://github.com/tuanminhhole/openclaw-skill-infographic) — Infographic generation
|
|
341
356
|
|
|
342
357
|
---
|
package/README.vi.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*Chạy một lệnh → mở dashboard → bot lên sóng. Windows · macOS · Linux · VPS — chạy trên Docker, tự cài giúp bạn.*
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.
|
|
10
|
+
<a href="https://github.com/tuanminhhole/openclaw-setup/releases"><img src="https://img.shields.io/badge/RELEASE-v5.14.0-0EA5E9?style=for-the-badge" alt="Version 5.14.0" /></a>
|
|
11
11
|
<a href="https://github.com/tuanminhhole/openclaw-setup?tab=MIT-1-ov-file"><img src="https://img.shields.io/badge/LICENSE-MIT-success?style=for-the-badge" alt="MIT License" /></a>
|
|
12
12
|
<a href="https://www.npmjs.com/package/create-openclaw-bot"><img src="https://img.shields.io/npm/v/create-openclaw-bot?style=for-the-badge&label=CLI&color=2563EB&logo=npm&logoColor=white" alt="NPM Version" /></a>
|
|
13
13
|
<a href="https://github.com/tuanminhhole/openclaw-setup/stargazers"><img src="https://img.shields.io/github/stars/tuanminhhole/openclaw-setup?style=for-the-badge&color=eab308&logo=github&logoColor=white" alt="GitHub Stars" /></a>
|
|
@@ -37,11 +37,26 @@
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
-
## 🆕 Có gì mới trong v5.
|
|
40
|
+
## 🆕 Có gì mới trong v5.14.0
|
|
41
|
+
|
|
42
|
+
- 🛠️ **Bot tự tạo skill cho chính nó**: nhờ bot làm một kỹ năng dùng lại ("tạo cho anh skill X") là nó viết xong và dùng được ngay trong cùng câu trả lời — không còn bước chờ duyệt, không phải sửa cấu hình. Skill lưu vào `skills/<tên>/SKILL.md` trong workspace của bot và tự nạp. **Bot cũ tự nhận khi cập nhật.**
|
|
43
|
+
|
|
44
|
+
<details>
|
|
45
|
+
<summary><b>Trước đó: Có gì mới trong v5.13.9</b></summary>
|
|
46
|
+
|
|
47
|
+
- 🧠 **Trí nhớ always-on cho mọi bot (learning-memory)**: plugin context-engine mới nạp `MEMORY.md` + `USER.md` đã chắt lọc vào **MỌI lượt** — kể cả trong nhóm (điều recall mặc định bỏ qua). Bot hết quên ngữ cảnh/quy tắc theo thời gian: không còn "nói leo" khi không ai gọi, không hỏi lại điều bạn đã dặn. Tự cài cho bot mới + có card 1-chạm trên dashboard.
|
|
48
|
+
- 🧹 **Bỏ TencentDB Agent Memory**, thay bằng engine always-on ở trên (không cần dịch vụ ngoài). Bot cũ tự migrate khi cập nhật — *skill* learning-memory cũ và plugin TencentDB được gỡ tự động.
|
|
49
|
+
|
|
50
|
+
</details>
|
|
51
|
+
|
|
52
|
+
<details>
|
|
53
|
+
<summary><b>Trước đó: Có gì mới trong v5.13.4</b></summary>
|
|
41
54
|
|
|
42
55
|
- ⚙️ **Trang Cài đặt mới**: chọn giao diện (nút gạt Sáng/Tối), ngôn ngữ (VI/EN) và múi giờ ngay trong dashboard — có ở cả sidebar lẫn thanh điều hướng mobile.
|
|
43
56
|
- 🕒 **Lịch & cron chạy đúng giờ địa phương**: bot tạo mới nhận múi giờ rõ ràng, nên nhắc hẹn/cron không còn lệch 1 ngày quanh nửa đêm (lỗi UTC-vs-local có thể đặt lịch vào quá khứ). Đã siết hướng dẫn lập lịch toàn diện (giờ local + múi giờ, nơi gửi rõ ràng, ID nhóm dạng thô).
|
|
44
57
|
|
|
58
|
+
</details>
|
|
59
|
+
|
|
45
60
|
<details>
|
|
46
61
|
<summary><b>Trước đó: Có gì mới trong v5.13.0</b></summary>
|
|
47
62
|
|
|
@@ -336,7 +351,7 @@ Các repo đi kèm để bạn dựng một trợ lý AI "tự vận hành" hoà
|
|
|
336
351
|
- [openclaw-n8n-facebook-poster](https://github.com/tuanminhhole/openclaw-n8n-facebook-poster) — Tự động đăng Facebook qua n8n
|
|
337
352
|
|
|
338
353
|
**🧩 Skill**
|
|
339
|
-
- [openclaw-
|
|
354
|
+
- [openclaw-learning-memory](https://github.com/tuanminhhole/openclaw-learning-memory) — Context engine trí nhớ always-on (nạp MEMORY.md + USER.md vào mọi lượt, kể cả nhóm)
|
|
340
355
|
- [openclaw-skill-infographic](https://github.com/tuanminhhole/openclaw-skill-infographic) — Tạo infographic
|
|
341
356
|
|
|
342
357
|
---
|
|
@@ -1032,14 +1032,16 @@ function ensureZaloModPluginConfig(entry, cfg) {
|
|
|
1032
1032
|
const gwPort = Number(cfg.gateway?.port) || state.gatewayPort || 18789;
|
|
1033
1033
|
entry.config.dashboardPort = gwPort + 1;
|
|
1034
1034
|
}
|
|
1035
|
-
//
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1035
|
+
// Seed the default bot's identity under bots.default (per-bot shape). zalo-mod
|
|
1036
|
+
// treats bots.<profile> as the canonical source — do NOT write legacy top-level
|
|
1037
|
+
// botName/zaloDisplayNames (they get stripped by the plugin's normalizer anyway).
|
|
1038
|
+
const firstAgentName = cfg.agents?.list?.[0]?.name;
|
|
1039
|
+
if (firstAgentName) {
|
|
1040
|
+
entry.config.bots = entry.config.bots || {};
|
|
1041
|
+
const def = entry.config.bots.default = entry.config.bots.default || {};
|
|
1042
|
+
if (!def.botName) def.botName = firstAgentName;
|
|
1043
|
+
if (!Array.isArray(def.zaloDisplayNames) || def.zaloDisplayNames.length === 0) def.zaloDisplayNames = [firstAgentName];
|
|
1044
|
+
if (!def.slashPrefix) def.slashPrefix = String(firstAgentName).toLowerCase().replace(/[^a-z0-9-]/g, '') || 'bot';
|
|
1043
1045
|
}
|
|
1044
1046
|
}
|
|
1045
1047
|
|
|
@@ -3126,8 +3128,8 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
|
|
|
3126
3128
|
// from <workspace>/skills, so each bot's copy is independent. Toggling here affects ONLY the
|
|
3127
3129
|
// active bot's workspace folder. The global skills.entries[id].enabled flag is kept true
|
|
3128
3130
|
// while ANY bot still has the folder (openclaw needs it true to load the skill at all).
|
|
3129
|
-
if (kind === 'skill' &&
|
|
3130
|
-
const slugMap = { 'image-gen': 'infographic-generator'
|
|
3131
|
+
if (kind === 'skill' && id === 'image-gen') {
|
|
3132
|
+
const slugMap = { 'image-gen': 'infographic-generator' };
|
|
3131
3133
|
const slug = slugMap[id];
|
|
3132
3134
|
const rel = workspaceRelForAgent(agent, cfg, projectDir) || `workspace-${agent.id}`;
|
|
3133
3135
|
const folder = join(projectDir, '.openclaw', rel, 'skills', slug);
|
|
@@ -3284,7 +3286,9 @@ async function applyFeatureToggle(projectDir, agentId, kind, id, enabled) {
|
|
|
3284
3286
|
// Most plugins are ClawHub packages (installed as `clawhub:<id>`). A few ship as plain npm
|
|
3285
3287
|
// packages and need their real package spec instead. Map id → install spec here.
|
|
3286
3288
|
const PLUGIN_NPM_SPEC = {
|
|
3287
|
-
|
|
3289
|
+
// learning-memory ships on ClawHub as `openclaw-learning-memory` (manifest id is
|
|
3290
|
+
// `learning-memory`, used as the config key). Install by the full package spec.
|
|
3291
|
+
'learning-memory': 'clawhub:openclaw-learning-memory',
|
|
3288
3292
|
};
|
|
3289
3293
|
const pluginInstallSpec = (id) => PLUGIN_NPM_SPEC[id] || `clawhub:${id}`;
|
|
3290
3294
|
|
|
@@ -3292,7 +3296,6 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
3292
3296
|
if (kind === 'skill') {
|
|
3293
3297
|
const skillSlugMap = {
|
|
3294
3298
|
'image-gen': 'infographic-generator',
|
|
3295
|
-
'learning-memory': 'learning-memory',
|
|
3296
3299
|
};
|
|
3297
3300
|
const slug = skillSlugMap[id] || id;
|
|
3298
3301
|
|
|
@@ -3379,6 +3382,11 @@ async function installFeature(projectDir, agentId, kind, id) {
|
|
|
3379
3382
|
sendLog('[zalo-connect] Restarting container to apply...');
|
|
3380
3383
|
await run('docker', ['restart', getBotContainerName(projectDir)], { shell: false }).catch(() => {});
|
|
3381
3384
|
}
|
|
3385
|
+
// Drop cached container extension versions so the card reflects the just-installed
|
|
3386
|
+
// version immediately (extver cache lives until explicitly cleared, like the
|
|
3387
|
+
// general install path below does).
|
|
3388
|
+
probeCacheClear(`extver:${projectDir}`);
|
|
3389
|
+
probeCacheClear(`runtime:${projectDir}`);
|
|
3382
3390
|
return { ok: true, id: 'zalo-connect' };
|
|
3383
3391
|
}
|
|
3384
3392
|
const installSpec = pluginInstallSpec(id);
|
|
@@ -3675,14 +3683,13 @@ async function getFeatureFlags(projectDir, agentId = '') {
|
|
|
3675
3683
|
// THIS agent's workspace (not the global skills.entries flag, which would leak across bots).
|
|
3676
3684
|
const imageGenOn = isSkillFolderExists(projectDir, aid, 'infographic-generator', cfg);
|
|
3677
3685
|
const webSearchOn = isEnabled(['duckduckgo']);
|
|
3678
|
-
const learningMemoryOn = isSkillFolderExists(projectDir, aid, 'learning-memory', cfg);
|
|
3679
3686
|
const aliases = {
|
|
3680
3687
|
browser: ['openclaw-browser-automation', 'browser-automation'],
|
|
3681
3688
|
zalo: ['openclaw-zalo-mod', 'zalo-mod'],
|
|
3682
3689
|
crawler: ['openclaw-facebook-crawler', 'openclaw-n8n-facebook-crawler', 'n8n-facebook-crawler'],
|
|
3683
3690
|
poster: ['openclaw-n8n-facebook-poster', 'openclaw-facebook-poster', 'facebook-poster'],
|
|
3684
3691
|
fbMessenger: ['openclaw-fb-messenger', 'fb-messenger'],
|
|
3685
|
-
|
|
3692
|
+
learningMemory: ['learning-memory', 'openclaw-learning-memory'],
|
|
3686
3693
|
zaloConnect: ['zalo-connect', 'openclaw-zalo-connect'],
|
|
3687
3694
|
};
|
|
3688
3695
|
const flags = {
|
|
@@ -3690,13 +3697,12 @@ async function getFeatureFlags(projectDir, agentId = '') {
|
|
|
3690
3697
|
'skill:cron': cronOn,
|
|
3691
3698
|
'skill:image-gen': imageGenOn,
|
|
3692
3699
|
'skill:web-search': webSearchOn,
|
|
3693
|
-
'skill:learning-memory': learningMemoryOn,
|
|
3694
3700
|
'plugin:openclaw-browser-automation': isEnabled(aliases.browser),
|
|
3695
3701
|
'plugin:openclaw-zalo-mod': isEnabled(aliases.zalo),
|
|
3696
3702
|
'plugin:openclaw-facebook-crawler': isEnabled(aliases.crawler),
|
|
3697
3703
|
'plugin:openclaw-n8n-facebook-poster': isEnabled(aliases.poster),
|
|
3698
3704
|
'plugin:openclaw-fb-messenger': isEnabled(aliases.fbMessenger),
|
|
3699
|
-
'plugin:memory
|
|
3705
|
+
'plugin:learning-memory': isEnabled(aliases.learningMemory),
|
|
3700
3706
|
'plugin:zalo-connect': isEnabled(aliases.zaloConnect),
|
|
3701
3707
|
};
|
|
3702
3708
|
const extensionsDir = join(projectDir || '', '.openclaw', 'extensions');
|
|
@@ -3706,7 +3712,6 @@ async function getFeatureFlags(projectDir, agentId = '') {
|
|
|
3706
3712
|
extensionDirExists(aliases) || isInstalledByRecord(aliases);
|
|
3707
3713
|
const installed = {
|
|
3708
3714
|
'skill:image-gen': isSkillFolderExists(projectDir, aid, 'infographic-generator', cfg),
|
|
3709
|
-
'skill:learning-memory': isSkillFolderExists(projectDir, aid, 'learning-memory', cfg),
|
|
3710
3715
|
'plugin:openclaw-browser-automation': isActuallyInstalled(aliases.browser),
|
|
3711
3716
|
'plugin:openclaw-zalo-mod': isActuallyInstalled(aliases.zalo),
|
|
3712
3717
|
'plugin:openclaw-facebook-crawler': isActuallyInstalled(aliases.crawler),
|
|
@@ -3715,31 +3720,34 @@ async function getFeatureFlags(projectDir, agentId = '') {
|
|
|
3715
3720
|
// proof of install — require a real extension dir or an install record instead.
|
|
3716
3721
|
'plugin:openclaw-fb-messenger': extensionDirExists(aliases.fbMessenger)
|
|
3717
3722
|
|| aliases.fbMessenger.some((a) => installedKeys.has(a) || Array.from(installedSpecs).some((spec) => spec.includes(a))),
|
|
3718
|
-
'plugin:memory
|
|
3723
|
+
'plugin:learning-memory': isActuallyInstalled(aliases.learningMemory),
|
|
3719
3724
|
'plugin:zalo-connect': isActuallyInstalled(aliases.zaloConnect),
|
|
3720
3725
|
};
|
|
3721
3726
|
const versions = {
|
|
3722
3727
|
'skill:image-gen': await getInstalledSkillVersion(projectDir, aid, 'infographic-generator', cfg),
|
|
3723
|
-
'skill:learning-memory': await getInstalledSkillVersion(projectDir, aid, 'learning-memory', cfg),
|
|
3724
3728
|
'plugin:openclaw-browser-automation': await getInstalledPluginVersion(projectDir, aliases.browser),
|
|
3725
3729
|
'plugin:openclaw-zalo-mod': await getInstalledPluginVersion(projectDir, aliases.zalo),
|
|
3726
3730
|
'plugin:openclaw-facebook-crawler': await getInstalledPluginVersion(projectDir, aliases.crawler),
|
|
3727
3731
|
'plugin:openclaw-n8n-facebook-poster': await getInstalledPluginVersion(projectDir, aliases.poster),
|
|
3728
3732
|
'plugin:openclaw-fb-messenger': await getInstalledPluginVersion(projectDir, aliases.fbMessenger),
|
|
3729
|
-
'plugin:memory
|
|
3733
|
+
'plugin:learning-memory': await getInstalledPluginVersion(projectDir, aliases.learningMemory),
|
|
3730
3734
|
'plugin:zalo-connect': await getInstalledPluginVersion(projectDir, aliases.zaloConnect),
|
|
3731
3735
|
};
|
|
3732
|
-
// Docker:
|
|
3733
|
-
//
|
|
3736
|
+
// Docker: the container's extensions volume is the SOURCE OF TRUTH for installed
|
|
3737
|
+
// plugin versions — clawhub/plugin installs run inside the container, so a host copy
|
|
3738
|
+
// (bind-mounted .openclaw or a stale installs.json) can lag behind after an update.
|
|
3739
|
+
// When the container reports a version, it OVERRIDES the host value (not just fills
|
|
3740
|
+
// empties) so the card shows the actually-installed version, not a stale one. Native
|
|
3741
|
+
// installs return {} here, so host values are kept.
|
|
3734
3742
|
const containerExtVersions = await getContainerExtensionVersions(projectDir);
|
|
3735
3743
|
if (Object.keys(containerExtVersions).length) {
|
|
3736
|
-
const fillVer = (key, names) => {
|
|
3744
|
+
const fillVer = (key, names) => { for (const n of names) { if (containerExtVersions[n]) { versions[key] = containerExtVersions[n]; break; } } };
|
|
3737
3745
|
fillVer('plugin:openclaw-browser-automation', aliases.browser);
|
|
3738
3746
|
fillVer('plugin:openclaw-zalo-mod', aliases.zalo);
|
|
3739
3747
|
fillVer('plugin:openclaw-facebook-crawler', aliases.crawler);
|
|
3740
3748
|
fillVer('plugin:openclaw-n8n-facebook-poster', aliases.poster);
|
|
3741
3749
|
fillVer('plugin:openclaw-fb-messenger', aliases.fbMessenger);
|
|
3742
|
-
fillVer('plugin:memory
|
|
3750
|
+
fillVer('plugin:learning-memory', aliases.learningMemory);
|
|
3743
3751
|
fillVer('plugin:zalo-connect', aliases.zaloConnect);
|
|
3744
3752
|
}
|
|
3745
3753
|
const zaloBackend = fresh.channels?.['zalo-connect']?.enabled ? 'zalo-connect' : '';
|
|
@@ -114,10 +114,34 @@
|
|
|
114
114
|
// nửa đêm và đặt cron/lịch vào quá khứ. Lấy tz người dùng chọn ở UI Cài đặt (mặc định VN).
|
|
115
115
|
userTimezone,
|
|
116
116
|
compaction: { mode: 'safeguard' },
|
|
117
|
-
// Trim stale tool results before
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
|
|
117
|
+
// Trim stale tool results from context before each LLM call. Lowers token
|
|
118
|
+
// cost on long sessions AND keeps a heavy multi-step turn (deep research +
|
|
119
|
+
// file/PDF gen) from piling up tool output that overflows the model window
|
|
120
|
+
// mid tool-loop. These bots run on 9router (no Anthropic prompt cache), so a
|
|
121
|
+
// short 2m TTL lets pruning engage sooner inside a long turn — no cache
|
|
122
|
+
// downside. The stable system prompt (AGENTS.md/SOUL.md…) is never pruned.
|
|
123
|
+
contextPruning: { mode: 'cache-ttl', ttl: '2m' },
|
|
124
|
+
// Compress vision inputs — these bots are image-heavy (infographics, PDF
|
|
125
|
+
// charts, screenshots). "efficient" keeps vision-token cost down so an
|
|
126
|
+
// image-rich turn is far less likely to blow past the context window.
|
|
127
|
+
imageQuality: 'efficient',
|
|
128
|
+
// Downscale image payloads when sanitizing transcript/tool-result content
|
|
129
|
+
// (OpenClaw default is 1200px). Verified failure mode: the bot generated 4K
|
|
130
|
+
// infographics for a PDF and then read them back with the image tool — each
|
|
131
|
+
// read landed a ~100KB base64 blob in the transcript, and the overflow
|
|
132
|
+
// precheck counts that base64 as plain text (~14k tokens per image). Eight of
|
|
133
|
+
// them in one turn = 214k estimated tokens against a 180k budget → abort with
|
|
134
|
+
// "nothing to compact". 1024px keeps charts/screenshots legible to vision
|
|
135
|
+
// while roughly halving the payload.
|
|
136
|
+
imageMaxDimensionPx: 1024,
|
|
137
|
+
// Cap each persisted tool result. The model-context auto default is huge
|
|
138
|
+
// (64k chars/result at 200k tokens), so one heavy turn (e.g. deep research
|
|
139
|
+
// with 40+ web fetches) can pile up 200k+ tokens of tool output in a SINGLE
|
|
140
|
+
// unsplittable turn — which then can't be compacted ("nothing to compact")
|
|
141
|
+
// and poisons the session until a manual reset. 12k chars (~3k tokens) per
|
|
142
|
+
// result keeps per-source content substantial while making that runaway
|
|
143
|
+
// impossible. Applies to persisted results + overflow recovery.
|
|
144
|
+
contextLimits: { toolResultMaxChars: 12000 },
|
|
121
145
|
// Agent-TURN budget (seconds). 120 was too short for multi-step cloud turns (OCR +
|
|
122
146
|
// file gen + tool calls). 900 = OpenClaw's own native default and the practical max
|
|
123
147
|
// we use. This is the turn budget — a DIFFERENT layer from 9router's per-request
|
|
@@ -240,8 +264,17 @@
|
|
|
240
264
|
const skillEntries = buildSkillsEntries(skills, selectedSkills);
|
|
241
265
|
// NOTE: the "zalo-actions" skill was removed — its guidance now lives in the `zalo-connect`
|
|
242
266
|
// tool description itself (fork ≥ v3.0.2), so no per-bot skill entry is needed anymore.
|
|
267
|
+
//
|
|
268
|
+
// Skill Workshop `approvalPolicy: "auto"` lets the assistant author its own
|
|
269
|
+
// workspace skills end-to-end when the user asks ("tạo skill X"): create the
|
|
270
|
+
// proposal AND apply it in one turn, without a separate operator approval.
|
|
271
|
+
// Apply is still scanner-gated + workspace-scoped + rollback-tracked, so this
|
|
272
|
+
// stays safe. Workspace SKILL.md files auto-load via the skills watcher — no
|
|
273
|
+
// per-skill openclaw.json entry is needed. `autonomous.enabled` stays false so
|
|
274
|
+
// the bot only creates skills on explicit request, never spontaneously.
|
|
275
|
+
cfg.skills = { workshop: { approvalPolicy: 'auto' } };
|
|
243
276
|
if (Object.keys(skillEntries).length > 0) {
|
|
244
|
-
cfg.skills =
|
|
277
|
+
cfg.skills.entries = skillEntries;
|
|
245
278
|
}
|
|
246
279
|
|
|
247
280
|
// ── plugins (memory-core dreaming + openclaw-zalo-mod) ────────────────────────────
|
|
@@ -371,6 +404,12 @@
|
|
|
371
404
|
|
|
372
405
|
const allow = ['memory-core'];
|
|
373
406
|
|
|
407
|
+
// Always-on memory: the learning-memory context engine injects the curated
|
|
408
|
+
// MEMORY.md + USER.md into every turn (including group sessions, which the default
|
|
409
|
+
// recall excludes). Enabled for every bot; the plugin is installed at creation time.
|
|
410
|
+
entries['learning-memory'] = { enabled: true };
|
|
411
|
+
allow.push('learning-memory');
|
|
412
|
+
|
|
374
413
|
// Zalo Personal → zalo-connect channel plugin (pinned install happens in the
|
|
375
414
|
// entrypoint / creation flow). openclaw-zalo-mod stays a separate opt-in plugin.
|
|
376
415
|
if (isZaloPersonal(channelKey)) {
|
|
@@ -395,6 +434,8 @@
|
|
|
395
434
|
|
|
396
435
|
const plugins = { entries };
|
|
397
436
|
plugins.allow = allow;
|
|
437
|
+
// Select learning-memory as the active context engine (always-on memory).
|
|
438
|
+
plugins.slots = { contextEngine: 'learning-memory' };
|
|
398
439
|
if (allow.length) plugins.bundledDiscovery = 'compat';
|
|
399
440
|
|
|
400
441
|
return { plugins };
|
|
@@ -256,6 +256,28 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
256
256
|
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);}`;
|
|
257
257
|
|
|
258
258
|
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','plugins','tools'];for(const k of keep){if(bk[k]&&!c[k])c[k]=bk[k];}const gp=Number(process.env.OPENCLAW_GATEWAY_PORT||process.env.OPENCLAW_PORT)||c.gateway?.port||bk.gateway?.port||18789;const a=new Set(['http://localhost:'+gp,'http://127.0.0.1:'+gp,'http://0.0.0.0:'+gp]);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+':'+gp);}}c.tools=Object.assign({},c.tools,{profile:'full',exec:{host:'gateway',security:'full',ask:'off'}});c.gateway=Object.assign({},c.gateway,{port:gp,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);}`;
|
|
259
|
+
// One-shot migration for projects created by an older setup: the learning-memory
|
|
260
|
+
// SKILL and the memory-tencentdb plugin are deprecated (replaced by the learning-memory
|
|
261
|
+
// context-engine plugin). Drop their config entries so they stop loading; the skill
|
|
262
|
+
// folders + tencentdb install are removed by the shell steps that follow.
|
|
263
|
+
const deprecatedCleanupScript = `const fs=require('fs'),path=require('path');const p=path.join(process.cwd(),'.openclaw','openclaw.json');if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));let ch=false;if(c.plugins&&c.plugins.entries&&c.plugins.entries['memory-tencentdb']){delete c.plugins.entries['memory-tencentdb'];ch=true;}if(c.plugins&&Array.isArray(c.plugins.allow)){const n=c.plugins.allow.filter(x=>x!=='memory-tencentdb'&&x!=='@tencentdb-agent-memory/memory-tencentdb');if(n.length!==c.plugins.allow.length){c.plugins.allow=n;ch=true;}}if(c.skills&&c.skills.entries&&c.skills.entries['learning-memory']){delete c.skills.entries['learning-memory'];ch=true;}if(ch)fs.writeFileSync(p,JSON.stringify(c,null,2));}`;
|
|
264
|
+
// One-shot upgrade for projects created by an older setup (their openclaw.json was
|
|
265
|
+
// generated before these defaults existed, and bot-config-gen only runs when a bot is
|
|
266
|
+
// created/regenerated — so a plain rebuild would never pick them up):
|
|
267
|
+
// • skills.workshop.approvalPolicy:'auto' → the assistant can author a workspace
|
|
268
|
+
// skill end-to-end on request instead of stopping at "proposal awaiting approval".
|
|
269
|
+
// • imageMaxDimensionPx / imageQuality / contextLimits.toolResultMaxChars → keep one
|
|
270
|
+
// heavy turn (deep research, 4K chart read-back) from overflowing the context
|
|
271
|
+
// window mid tool-loop, which cannot be compacted and poisons the session.
|
|
272
|
+
// Each key is only filled in when absent, so an operator's own tuning is never clobbered.
|
|
273
|
+
const contextDefaultsScript = `const fs=require('fs'),path=require('path');const p=path.join(process.cwd(),'.openclaw','openclaw.json');if(fs.existsSync(p)){const c=JSON.parse(fs.readFileSync(p,'utf8'));let ch=false;c.skills=c.skills||{};c.skills.workshop=c.skills.workshop||{};if(!c.skills.workshop.approvalPolicy){c.skills.workshop.approvalPolicy='auto';ch=true;}const d=(c.agents&&c.agents.defaults)?c.agents.defaults:null;if(d){if(d.imageMaxDimensionPx===undefined){d.imageMaxDimensionPx=1024;ch=true;}if(d.imageQuality===undefined){d.imageQuality='efficient';ch=true;}d.contextLimits=d.contextLimits||{};if(d.contextLimits.toolResultMaxChars===undefined){d.contextLimits.toolResultMaxChars=12000;ch=true;}}if(ch)fs.writeFileSync(p,JSON.stringify(c,null,2));}`;
|
|
274
|
+
// Companion backfill for the same older projects: their TOOLS.md was generated before the
|
|
275
|
+
// skill-authoring / long-turn guidance existed, and workspace files are only written when a
|
|
276
|
+
// bot is created — so a rebuild alone leaves the assistant stopping at "proposal awaiting
|
|
277
|
+
// approval" even once approvalPolicy is 'auto'. (TOOLS.md is listed in AGENTS.md as required
|
|
278
|
+
// reading, so guidance lands there rather than in the main manual.) Appends the block once
|
|
279
|
+
// per workspace, keyed off the `skill_workshop` marker, never rewriting a file that has it.
|
|
280
|
+
const agentsGuidanceScript = `const fs=require('fs'),path=require('path');const vi=['','## 🛠️ Tự tạo skill & việc dài','','**Tự tạo skill:** Khi user nhờ tạo một kỹ năng/quy trình dùng lại (vd _"tạo skill X"_), bạn TỰ làm từ đầu đến cuối — dùng tool **skill_workshop**: tạo proposal RỒI **apply luôn trong cùng lượt** (môi trường này bật approvalPolicy auto, không cần chờ ai duyệt). Skill ghi vào skills/<tên>/SKILL.md và **tự nạp** qua watcher — KHÔNG cần sửa openclaw.json. Đừng dừng ở _"đã tạo proposal, chờ duyệt"_. KHÔNG nhúng bí mật vào skill — đọc từ biến môi trường lúc chạy.','','**Việc dài, nhiều bước (research sâu, dựng báo cáo/PDF):** ảnh/chart bạn vừa tạo thì **đừng đọc lại** bằng tool ảnh (chèn thẳng file vào tài liệu; kiểm bằng file tồn tại / build thành công) — đọc lại ảnh lớn ngốn ngữ cảnh gấp nhiều lần bạn tưởng. Tạo hình cạnh dài khoảng 1280px, đừng 4K trừ khi user yêu cầu. Research nhiều nguồn thì dùng **sessions_spawn** mỗi nhánh một subagent, bảo chúng **ghi dữ liệu thô ra file** và **chỉ trả tóm tắt ngắn kèm đường dẫn**; bạn tổng hợp từ file.',''].join('\\n');const en=['','## 🛠️ Authoring skills & long work','','**Authoring skills:** When the user asks for a reusable capability (e.g. _"make a skill for X"_), do it end-to-end — use the **skill_workshop** tool: create the proposal AND **apply it in the same turn** (this environment sets approvalPolicy auto, so no separate approval is needed). The skill lands in skills/<name>/SKILL.md and **auto-loads** via the watcher — no openclaw.json edit required. Do not stop at _"proposal created, awaiting approval"_. Never hardcode secrets into a skill — read them from environment variables at runtime.','','**Long multi-step work (deep research, building reports/PDFs):** do not read back an image or chart you just made (drop the file straight in; verify via file exists / build succeeded) — re-reading a large image costs far more context than it looks. Render at about 1280px on the long edge, skip 4K unless asked. For multi-source research, use **sessions_spawn** with one subagent per branch, have them **write raw findings to files** and **return only a short summary plus paths**; synthesize from the files.',''].join('\\n');const root=path.join(process.cwd(),'.openclaw');let dirs=[];try{dirs=fs.readdirSync(root).filter(n=>n.indexOf('workspace')===0);}catch(e){}for(const d of dirs){const f=path.join(root,d,'TOOLS.md');try{if(!fs.existsSync(f))continue;const cur=fs.readFileSync(f,'utf8');if(cur.indexOf('skill_workshop')!==-1)continue;const isVi=/Kỹ năng|Ghi chú|Xử lý lỗi|môi trường/.test(cur);fs.appendFileSync(f,(cur.endsWith('\\n')?'':'\\n')+(isVi?vi:en));}catch(e){}}`;
|
|
259
281
|
const securityCompatScript = `const fs=require('fs'),path=require('path');const scopes=['operator.admin','operator.pairing','operator.approvals'];function uniq(a){return Array.from(new Set([...(Array.isArray(a)?a:[]),...scopes]));}function walk(v){if(!v||typeof v!=='object')return;if(Array.isArray(v)){v.forEach(walk);return;}if(Array.isArray(v.scopes)||Array.isArray(v.approvedScopes)){v.scopes=uniq(v.scopes);v.approvedScopes=uniq(v.approvedScopes);}Object.values(v).forEach(walk);}const home=process.env.OPENCLAW_HOME||path.join(process.cwd(),'.openclaw');const state=process.env.OPENCLAW_STATE_DIR||home;const cfgPath=path.join(process.cwd(),'.openclaw','openclaw.json');if(fs.existsSync(cfgPath)){const c=JSON.parse(fs.readFileSync(cfgPath,'utf8'));const p=c.models&&c.models.providers&&c.models.providers['9router'];if(p){p.request=Object.assign({},p.request,{allowPrivateNetwork:true});}fs.writeFileSync(cfgPath,JSON.stringify(c,null,2));}for(const root of Array.from(new Set([home,state]))){const f=path.join(root,'devices','paired.json');if(fs.existsSync(f)){const d=JSON.parse(fs.readFileSync(f,'utf8'));walk(d);fs.writeFileSync(f,JSON.stringify(d,null,2));}}`;
|
|
260
282
|
|
|
261
283
|
const runtimeParts = runtimeCommandParts.filter(Boolean);
|
|
@@ -323,6 +345,16 @@ if(touched){console.log('[patch-9router] Applied Codex compatibility patch.');}e
|
|
|
323
345
|
const zaloConnectSpec = common.ZALO_CONNECT_PLUGIN_SPEC || 'clawhub:openclaw-zalo-connect';
|
|
324
346
|
runtimeParts.push(`ensure_plugin zalo-connect "${zaloConnectSpec}"`);
|
|
325
347
|
}
|
|
348
|
+
// Always-on memory context engine for every bot (see bot-config-gen: plugins.slots
|
|
349
|
+
// .contextEngine = "learning-memory"). ensure_plugin skips if already installed.
|
|
350
|
+
runtimeParts.push('ensure_plugin learning-memory "clawhub:openclaw-learning-memory"');
|
|
351
|
+
// Migrate off the deprecated learning-memory SKILL + memory-tencentdb plugin (older setups).
|
|
352
|
+
runtimeParts.push(`node - <<'NODE'\n${deprecatedCleanupScript}\nNODE`);
|
|
353
|
+
runtimeParts.push('find "$OPENCLAW_HOME" -maxdepth 4 -type d -path "*/skills/learning-memory" -exec rm -rf {} + 2>/dev/null || true');
|
|
354
|
+
runtimeParts.push('if [ -d "$OPENCLAW_HOME/extensions/memory-tencentdb" ] || openclaw plugins list 2>/dev/null | grep -q memory-tencentdb; then openclaw plugins uninstall memory-tencentdb --force 2>/dev/null || true; fi');
|
|
355
|
+
// Backfill skill-authoring + context defaults for configs from an older setup (see above).
|
|
356
|
+
runtimeParts.push(`node - <<'NODE'\n${contextDefaultsScript}\nNODE`);
|
|
357
|
+
runtimeParts.push(`node - <<'NODE'\n${agentsGuidanceScript}\nNODE`);
|
|
326
358
|
runtimeParts.push('openclaw gateway run');
|
|
327
359
|
const runtimeScript = ['#!/bin/sh', 'set -e', ...runtimeParts].join('\n');
|
|
328
360
|
let browserInstall = '';
|
|
@@ -1231,6 +1231,14 @@ Skills dùng chung. Setup là của riêng bạn. Tách ra nghĩa là cập nh
|
|
|
1231
1231
|
|
|
1232
1232
|
- Xem chi tiết hướng dẫn các kỹ năng được cài đặt tại thư mục [skills](./skills/).
|
|
1233
1233
|
|
|
1234
|
+
**🛠️ Tự tạo skill:** Khi user nhờ tạo một kỹ năng/quy trình dùng lại (vd _"tạo skill X"_), bạn TỰ làm được từ đầu đến cuối — dùng tool \`skill_workshop\`: tạo proposal RỒI **apply luôn trong cùng lượt** (môi trường này đã bật \`approvalPolicy: auto\` nên không cần chờ ai duyệt). Skill sẽ được ghi vào \`skills/<tên>/SKILL.md\` trong workspace và **tự nạp** qua watcher — KHÔNG cần sửa \`openclaw.json\`. Đừng dừng ở bước _"đã tạo proposal, chờ sếp duyệt rồi áp dụng"_: user bảo tạo nghĩa là tạo xong dùng được ngay. Tuyệt đối KHÔNG nhúng bí mật (mật khẩu/token/tài khoản) vào nội dung skill — đọc chúng từ biến môi trường lúc chạy.
|
|
1235
|
+
|
|
1236
|
+
**🧠 Việc dài, nhiều bước (research sâu, dựng báo cáo/PDF):** cửa sổ ngữ cảnh là tài nguyên — một lượt nhồi quá nhiều sẽ vỡ giữa đường và KHÔNG nén lại được. Ba quy tắc:
|
|
1237
|
+
|
|
1238
|
+
- **Ảnh/chart bạn vừa tạo thì đừng đọc lại** bằng tool ảnh để "xem cho chắc". Chèn thẳng file vào tài liệu; muốn kiểm thì xem file có tồn tại / build thành công. Đọc lại một ảnh lớn tốn ngữ cảnh gấp nhiều lần bạn tưởng.
|
|
1239
|
+
- **Tạo hình ở cỡ vừa đủ** (cạnh dài ~1280px là đẹp cho báo cáo). Đừng làm 4K trừ khi user yêu cầu rõ.
|
|
1240
|
+
- **Research nhiều nguồn thì chia việc:** \`sessions_spawn\` mỗi nhánh một subagent (mỗi con có cửa sổ riêng), bảo chúng **ghi dữ liệu thô ra file** trong workspace và **chỉ trả về tóm tắt ngắn kèm đường dẫn file**. Bạn tổng hợp từ file. Đừng tự mình kéo hàng chục trang web vào một lượt.
|
|
1241
|
+
|
|
1234
1242
|
## 📁 File & Workspace
|
|
1235
1243
|
|
|
1236
1244
|
- Bot có thể đọc/ghi file trong thư mục workspace: \`${workspacePath}\`
|
|
@@ -1282,6 +1290,14 @@ Skills are shared. Your setup is yours. Keeping them apart means you can update
|
|
|
1282
1290
|
|
|
1283
1291
|
- See detailed guidelines of installed skills in the [skills](./skills/) directory.
|
|
1284
1292
|
|
|
1293
|
+
**🛠️ Authoring skills:** When the user asks you to build a reusable capability (e.g. _"make a skill for X"_), you can do it end-to-end — use the \`skill_workshop\` tool: create the proposal AND **apply it in the same turn** (this environment sets \`approvalPolicy: auto\`, so no separate approval is needed). The skill lands in \`skills/<name>/SKILL.md\` in the workspace and **auto-loads** via the watcher — no \`openclaw.json\` edit required. Don't stop at _"proposal created, awaiting approval"_: if they asked you to build it, finish it so it works right away. Never hardcode secrets (passwords/tokens/accounts) into a skill's contents — read them from environment variables at runtime.
|
|
1294
|
+
|
|
1295
|
+
**🧠 Long multi-step work (deep research, building reports/PDFs):** the context window is a budget — cram too much into one turn and it blows up mid-flight, with nothing left to compact. Three rules:
|
|
1296
|
+
|
|
1297
|
+
- **Don't read back an image or chart you just made** to "double-check" it. Drop the file straight into the document; verify by checking the file exists or that the build succeeded. Re-reading a large image costs far more context than it looks.
|
|
1298
|
+
- **Render images at a sane size** (~1280px on the long edge looks great in a report). Skip 4K unless the user explicitly asks.
|
|
1299
|
+
- **Split multi-source research:** \`sessions_spawn\` one subagent per branch (each gets its own window), have them **write raw findings to files** in the workspace and **return only a short summary plus the file paths**. You synthesize from the files. Don't pull dozens of web pages into a single turn yourself.
|
|
1300
|
+
|
|
1285
1301
|
## 📁 File & Workspace
|
|
1286
1302
|
|
|
1287
1303
|
- Bot can read/write files in workspace: \`${workspacePath}\`
|
package/dist/web/app.js
CHANGED
|
@@ -1061,11 +1061,10 @@ function botSkillsPanel() {
|
|
|
1061
1061
|
{ id: 'cron', title: 'Cron', desc: 'Native scheduler (SQLite) — cron guide in TOOLS.md' },
|
|
1062
1062
|
{ id: 'image-gen', title: 'Tạo ảnh Infographic', desc: 'Tạo ảnh infographic, poster tự động qua 9Router' },
|
|
1063
1063
|
{ id: 'web-search', title: 'Web Search', desc: 'Tìm kiếm web thời gian thực (DuckDuckGo)' },
|
|
1064
|
-
{ id: 'learning-memory', title: 'Siêu Trí Nhớ Dài Hạn (learning-memory)', desc: 'Tự động ghi nhớ bài học vào MEMORY.md, tự đóng gói và tiến hóa kỹ năng mới vào skills/' },
|
|
1065
1064
|
];
|
|
1066
1065
|
const plugins = [
|
|
1067
1066
|
{ id: 'zalo-connect', title: 'OpenClaw Zalo Connect', desc: t('Kênh Zalo cá nhân (zca-js) — BẮT BUỘC cho bot Zalo. Tự cài khi tạo bot Zalo đầu tiên; bấm Cập nhật để lên bản mới nhất.', 'Personal Zalo channel (zca-js) — REQUIRED for Zalo bots. Auto-installed with your first Zalo bot; click Update for the latest version.'), channels: ['zalo-personal'] },
|
|
1068
|
-
{ id: 'memory
|
|
1067
|
+
{ id: 'learning-memory', title: 'Siêu Trí Nhớ Dài Hạn (learning-memory)', desc: t('Bộ nhớ always-on: nạp MEMORY.md + USER.md đã chắt lọc vào MỌI lượt — kể cả trong nhóm — nên bot không quên ngữ cảnh/quy tắc. Tự cài khi tạo bot.', 'Always-on memory: injects a curated MEMORY.md + USER.md into every turn — including group chats — so the bot stops forgetting context and rules. Auto-installed on bot creation.') },
|
|
1069
1068
|
{ id: 'openclaw-browser-automation', title: 'openclaw-browser-automation', desc: 'Smart Search + Browser (headless & Chrome thật)' },
|
|
1070
1069
|
{ id: 'openclaw-zalo-mod', title: 'openclaw-zalo-mod', desc: 'Zalo group helpers', channels: ['zalo-personal'], openWebPort: 18790, openWebPath: '/dashboard' },
|
|
1071
1070
|
{ id: 'openclaw-fb-messenger', title: 'openclaw-fb-messenger', desc: t('Kênh Facebook Messenger — webhook + Graph API (bắt buộc cho bot Messenger)', 'Facebook Messenger channel — webhook + Graph API (required for Messenger bots)'), channels: ['fb-messenger'] },
|
|
@@ -1085,7 +1084,7 @@ function botSkillsPanel() {
|
|
|
1085
1084
|
const key = `${group}:${item.id}`;
|
|
1086
1085
|
const loading = !!state.featureLoading[key];
|
|
1087
1086
|
const locked = !!state.featureLocked?.[key];
|
|
1088
|
-
const requiresInstall = group === 'plugin' || (group === 'skill' &&
|
|
1087
|
+
const requiresInstall = group === 'plugin' || (group === 'skill' && item.id === 'image-gen');
|
|
1089
1088
|
const isInstalled = !requiresInstall || !!state.featureInstalled?.[key] || locked;
|
|
1090
1089
|
|
|
1091
1090
|
const version = requiresInstall && isInstalled ? (state.featureVersions?.[key] || '') : '';
|