create-openclaw-bot 5.8.2 → 5.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/README.vi.md +2 -2
- package/dist/cli.js +11 -15
- package/dist/server/local-server.js +266 -188
- package/dist/setup/data/header.js +1 -1
- package/dist/setup/data/skills.js +2 -4
- package/dist/setup/shared/bot-config-gen.js +21 -16
- package/dist/setup/shared/workspace-gen.js +307 -475
- package/dist/web/app.js +107 -33
- package/dist/web/styles.css +477 -10
- package/node_modules/color-convert/CHANGELOG.md +54 -0
- package/package.json +1 -1
- package/dist/legacy-cli.js +0 -2812
- package/dist/setup.js +0 -6931
|
@@ -114,413 +114,326 @@ const workspaceRoot = /** @type {OpenClawWorkspaceRoot} */ (
|
|
|
114
114
|
function buildHeartbeatDoc(options = {}) {
|
|
115
115
|
const { isVi = true } = options;
|
|
116
116
|
return isVi
|
|
117
|
-
? `# HEARTBEAT\n\n- Không có tác vụ tồn đọng mặc định.\n- Giữ nguyên danh tính, vai trò, và tính cách đã có trong IDENTITY.md, SOUL.md, AGENTS.md.\n- Không tự onboarding lại user.\n- Nếu user chỉ mở đầu ngắn như "alo", trả lời ngắn gọn, đúng vai trò hiện tại.\n
|
|
118
|
-
: `# HEARTBEAT\n\n- No pending task by default.\n- Keep the identity, role, and personality already defined in IDENTITY.md, SOUL.md, and AGENTS.md.\n- Do not re-onboard the user.\n- If the user sends a short opener like "hi" or "alo", reply briefly and stay in character.\n
|
|
117
|
+
? `# HEARTBEAT\n\n- Không có tác vụ tồn đọng mặc định.\n- Giữ nguyên danh tính, vai trò, và tính cách đã có trong IDENTITY.md, SOUL.md, AGENTS.md.\n- Không tự onboarding lại user.\n- Nếu user chỉ mở đầu ngắn như "alo", trả lời ngắn gọn, đúng vai trò hiện tại.\n`
|
|
118
|
+
: `# HEARTBEAT\n\n- No pending task by default.\n- Keep the identity, role, and personality already defined in IDENTITY.md, SOUL.md, and AGENTS.md.\n- Do not re-onboard the user.\n- If the user sends a short opener like "hi" or "alo", reply briefly and stay in character.\n`;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
function buildBootstrapDoc(options = {}) {
|
|
122
122
|
const { isVi = true, botName = 'Bot' } = options;
|
|
123
123
|
return isVi
|
|
124
|
-
? `# BOOTSTRAP\n\n## Vai trò ngay khi khởi động\n- Bạn là **${botName}**.\n- Danh tính, vai trò, và phong cách của bạn đã được định nghĩa sẵn trong **IDENTITY.md**, **SOUL.md**, và **AGENTS.md**.\n- Dùng các file đó làm nguồn sự thật cao nhất cho persona của bạn trong workspace này.\n\n## Bắt buộc\n- Không hỏi user đặt lại tên, vibe, persona, emoji ký tên, hay kiểu trợ lý cho chính bạn.\n- Không nói kiểu "mới tỉnh dậy", "vừa online", "đang chọn danh tính", hay mở màn onboarding tương tự.\n- Không cư xử như một bot mới tạo trống rỗng.\n- Khi user chỉ nhắn ngắn như "alo", hãy chào ngắn gọn và trả lời đúng vai trò hiện tại.\n\n## Telegram\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ó reaction tool/action native thì ưu tiên dùng nó để thả \`👍\`, trừ khi \`👍\` đã có sẵn.\n- Không được nói là mình đã thả reaction nếu tool chưa chạy được hoặc reaction không hiện thật trên chat.\n- Nếu chat đích không hỗ trợ reaction thật hoặc tool không được expose, vẫn trả lời bình thường và không tranh luận về giới hạn kỹ thuật.\n`
|
|
125
|
-
: `# BOOTSTRAP\n\n## Startup role\n- You are **${botName}**.\n- Your identity, role, and style are already defined in **IDENTITY.md**, **SOUL.md**, and **AGENTS.md**.\n- Use those files as the highest-priority source of truth for your persona in this workspace.\n\n## Mandatory\n- Do not ask the user to redefine your name, vibe, persona, signature emoji, or assistant style.\n- Do not say you just woke up, just came online, are choosing your identity, or any similar onboarding line.\n- Do not behave like a blank freshly-created bot.\n- If the user only sends a short opener like "hi" or "alo", greet briefly and reply in your existing role.\n\n## Telegram\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- Never claim that you reacted unless the tool actually ran and the reaction is real on the chat.\n- If the target chat does not support real reactions or the tool is not exposed, still reply normally and do not mention the technical limitation.\n`;
|
|
124
|
+
? `# BOOTSTRAP\n\n## Vai trò ngay khi khởi động\n- Bạn là **${botName}**.\n- Danh tính, vai trò, và phong cách của bạn đã được định nghĩa sẵn trong **IDENTITY.md**, **SOUL.md**, và **AGENTS.md**.\n- Dùng các file đó làm nguồn sự thật cao nhất cho persona của bạn trong workspace này.\n\n## Bắt buộc\n- Không hỏi user đặt lại tên, vibe, persona, emoji ký tên, hay kiểu trợ lý cho chính bạn.\n- Không nói kiểu "mới tỉnh dậy", "vừa online", "đang chọn danh tính", hay mở màn onboarding tương tự.\n- Không cư xử như một bot mới tạo trống rỗng.\n- Khi user chỉ nhắn ngắn như "alo", hãy chào ngắn gọn và trả lời đúng vai trò hiện tại.\n\n## Telegram & Zalo\n- Trước MỖI phản hồi Telegram hoặc Zalo nhìn thấy bởi user, nếu turn hiện tại có reaction tool/action native thì ưu tiên dùng nó để thả \`👍\`, trừ khi \`👍\` đã có sẵn.\n- Không được nói là mình đã thả reaction nếu tool chưa chạy được hoặc reaction không hiện thật trên chat.\n- Nếu chat đích không hỗ trợ reaction thật hoặc tool không được expose, vẫn trả lời bình thường và không tranh luận về giới hạn kỹ thuật.\n`
|
|
125
|
+
: `# BOOTSTRAP\n\n## Startup role\n- You are **${botName}**.\n- Your identity, role, and style are already defined in **IDENTITY.md**, **SOUL.md**, and **AGENTS.md**.\n- Use those files as the highest-priority source of truth for your persona in this workspace.\n\n## Mandatory\n- Do not ask the user to redefine your name, vibe, persona, signature emoji, or assistant style.\n- Do not say you just woke up, just came online, are choosing your identity, or any similar onboarding line.\n- Do not behave like a blank freshly-created bot.\n- If the user only sends a short opener like "hi" or "alo", greet briefly and reply in your existing role.\n\n## Telegram & Zalo\n- Before EVERY user-visible Telegram or Zalo reply, if the current turn exposes a native reaction tool/action, use it to add \`👍\` first unless \`👍\` is already present.\n- Never claim that you reacted unless the tool actually ran and the reaction is real on the chat.\n- If the target chat does not support real reactions or the tool is not exposed, still reply normally and do not mention the technical limitation.\n`;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
128
|
+
function buildCronjobSkillMd(isVi = true) {
|
|
129
|
+
if (isVi) {
|
|
130
|
+
return `# ⏰ Cron / Lên lịch nhắc nhở (tool: \`cron\`)
|
|
131
|
+
- **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).
|
|
132
|
+
- **⛔ 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\`.
|
|
133
|
+
- **Khi tạo cronjob mới (action \`add\`):**
|
|
134
|
+
- **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 đó.
|
|
135
|
+
- 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.
|
|
136
|
+
- **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.
|
|
137
|
+
- **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ờ.
|
|
138
|
+
- **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\`.
|
|
139
|
+
- **Khi user yêu cầu tắt/bật/xóa cronjob:**
|
|
140
|
+
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.
|
|
141
|
+
2. **Bước 2 (Xử lý):**
|
|
142
|
+
- Để xóa: Gọi action \`remove\` với \`id\` tìm được.
|
|
143
|
+
- Để tắt/tạm dừng: Gọi action \`update\` với \`id\` và patch \`{"enabled": false}\`.
|
|
144
|
+
- Để bật lại: Gọi action \`update\` với \`id\` và patch \`{"enabled": true}\`.
|
|
145
|
+
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ế.
|
|
146
|
+
- 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.
|
|
147
|
+
- Khi thao tác tool cho cron/scheduler, **không điền \`current\` vào thư mục Session**.
|
|
148
|
+
- **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.
|
|
149
|
+
- **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\`.
|
|
150
|
+
- 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.`;
|
|
148
151
|
}
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
return `# ⏰ Cron / Scheduled Tasks (tool: \`cron\`)
|
|
153
|
+
- **Exact tool name:** The tool name is \`cron\` (never mistake it for \`native\` or external command lines).
|
|
154
|
+
- **⛔ 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.
|
|
155
|
+
- **When creating a new cronjob (action \`add\`):**
|
|
156
|
+
- **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.
|
|
157
|
+
- 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.
|
|
158
|
+
- **Session:** Always use \`sessionTarget: "isolated"\` for background jobs (reports, reminders, automated messages). Only use \`"main"\` for short system events/reminders.
|
|
159
|
+
- **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.
|
|
160
|
+
- **Delivery:** For jobs that should send results to chat, set \`delivery.mode: "announce"\` with \`delivery.channel\` and \`delivery.to\`.
|
|
161
|
+
- **When the user requests to disable/enable/delete a cronjob:**
|
|
162
|
+
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\`.
|
|
163
|
+
2. **Step 2 (Processing):**
|
|
164
|
+
- To delete: Call action \`remove\` with the \`id\` found.
|
|
165
|
+
- To disable/pause: Call action \`update\` with \`id\` and patch \`{"enabled": false}\`.
|
|
166
|
+
- To enable: Call action \`update\` with \`id\` and patch \`{"enabled": true}\`.
|
|
167
|
+
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.
|
|
168
|
+
- 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.
|
|
169
|
+
- When operating cron/scheduler tools, do **not** put \`current\` into the Session directory.
|
|
170
|
+
- **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.
|
|
171
|
+
- **One-shot jobs:** Use schedule kind \`"at"\` with an ISO 8601 timestamp. The job auto-deletes after successful run unless \`deleteAfterRun: false\` is set.
|
|
172
|
+
- Skip internal doc lookups such as \`cron-jobs.mdx\`; rely on the available tools and complete the scheduling task directly.`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function buildInfographicGeneratorSkillMd() {
|
|
176
|
+
return `---
|
|
177
|
+
name: infographic-generator
|
|
178
|
+
description: Tạo ảnh infographic, banner hoặc poster trực tiếp bằng 1 prompt gửi tới API tạo ảnh.
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
Khi người dùng yêu cầu tạo ảnh infographic, tin tức, cẩm nang, hoặc poster bằng tiếng Việt, hãy sử dụng skill này để gọi trực tiếp API tạo ảnh qua script \`image-generator.js\`. Phương pháp này tạo ra các tác phẩm thiết kế đồng nhất và tuyệt đẹp chỉ bằng một câu prompt chi tiết duy nhất.
|
|
151
182
|
|
|
152
|
-
|
|
153
|
-
const limit = parseInt(process.argv[3]) || 5;
|
|
154
|
-
const CDP_URL = 'http://127.0.0.1:9222';
|
|
183
|
+
## 🚀 1. LỆNH THỰC THI
|
|
155
184
|
|
|
156
|
-
|
|
157
|
-
|
|
185
|
+
Để tạo ảnh, hãy gọi tool \`exec\` để chạy lệnh:
|
|
186
|
+
\`node skills/infographic-generator/image-generator.js "<prompt chi tiết bằng tiếng Anh>" <tên_ảnh>.png\`
|
|
187
|
+
|
|
188
|
+
_(Ví dụ: \`node skills/infographic-generator/image-generator.js "..." output.png\`)_
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 📐 2. QUY ĐỊNH KÍCH THƯỚC & TỶ LỆ (ASPECT RATIO)
|
|
193
|
+
|
|
194
|
+
Khi gọi API, mặc định kích thước là tỷ lệ **1:1** (hình vuông). Tuy nhiên, hãy tùy biến linh hoạt theo yêu cầu của người dùng bằng cách điều chỉnh từ khóa mô tả tỷ lệ và khung hình trong prompt:
|
|
195
|
+
|
|
196
|
+
- **Mặc định (1:1)**: Thêm từ khóa \`square aspect ratio, 1:1 square canvas\` vào prompt. Phù hợp cho các infographic dạng ô lưới hoặc bài đăng mạng xã hội thông thường.
|
|
197
|
+
- **Poster dọc (Vertical Poster)**: Thêm từ khóa \`vertical poster aspect ratio, 2:3 portrait format, vertical infographic\` vào prompt. Phù hợp cho cẩm nang chi tiết có nhiều mục (3-9 mục).
|
|
198
|
+
- **Landscape (16:9)**: Thêm từ khóa \`16:9 landscape aspect ratio, wide horizontal banner\` vào prompt. Phù hợp cho banner nằm ngang, ảnh bìa.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## ✍️ 3. QUY ĐỊNH FOOTER BẮT BUỘC
|
|
203
|
+
|
|
204
|
+
Mọi ảnh infographic/poster được tạo ra bằng skill này bắt buộc phải có dòng chữ bản quyền nằm ở cạnh dưới, canh giữa:
|
|
205
|
+
|
|
206
|
+
- **Nội dung chữ bắt buộc**: \`"designed by Williams - trợ lý của tuanminhhole"\`
|
|
207
|
+
- **Cách mô tả trong prompt**: Thêm vào cuối prompt mô tả chi tiết:
|
|
208
|
+
_\`"At the bottom center of the image, there is a clean and tiny centered footer text that reads: 'designed by Williams - trợ lý của tuanminhhole'"\`_
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 🎨 4. BA PHONG CÁCH THIẾT KẾ CHỦ ĐẠO
|
|
213
|
+
|
|
214
|
+
Hãy chọn 1 trong 3 phong cách dưới đây tùy thuộc vào ngữ cảnh yêu cầu:
|
|
215
|
+
|
|
216
|
+
### Phong cách 1: Tin tức báo chí / News Editorial
|
|
217
|
+
|
|
218
|
+
- **Đặc điểm**: Bố cục chuyên nghiệp, chia nhiều cột dọc/ngang (multi-column), sử dụng các đường kẻ mỏng hoặc nét đứt mảnh để phân chia các ô tin tức rõ ràng.
|
|
219
|
+
- **Phông chữ**: Font tiêu đề Serif (có chân) sang trọng, font nội dung Sans-serif (không chân) hiện đại.
|
|
220
|
+
- **Minh họa**: Icon dạng vector phẳng (flat vector icons), tối giản, chuyên nghiệp.
|
|
221
|
+
- **Từ khóa prompt gợi ý**: \`news editorial infographic style, newspaper grid layout, clear divider lines, minimal serif headers, flat vector icons, professional business theme, clean corporate colors.\`
|
|
222
|
+
|
|
223
|
+
### Phong cách 2: Cẩm nang/Hướng dẫn chi tiết
|
|
224
|
+
|
|
225
|
+
- **Đặc điểm**: Bố cục lưới (ví dụ: 3x3 grid) gồm nhiều ô được đánh số thứ tự (1, 2, 3...). Mỗi ô có nền màu pastel nhẹ nhàng (như xanh lá nhạt, kem nhạt, vàng nhạt) với viền bo góc tròn mềm mại. Có hình mascot (như chú heo đất đeo kính, két sắt, nhân vật hoạt hình) xuất hiện làm điểm nhấn.
|
|
226
|
+
- **Phông chữ**: Font chữ tròn, thân thiện, rõ ràng.
|
|
227
|
+
- **Minh họa**: Icon hoạt hình 2D sống động, nhiều màu sắc.
|
|
228
|
+
- **Từ khóa prompt gợi ý**: \`detailed guide infographic poster, 3x3 numbered grid layout, rounded pastel cards, cute 2D cartoon mascot, playful vector icons, warm cream background, clear numbered badges.\`
|
|
229
|
+
|
|
230
|
+
### Phong cách 3: Layout Neo-Brutalism hoạt hình
|
|
231
|
+
|
|
232
|
+
- **Đặc điểm**: Đường viền đen dày nổi bật (thick dark borders), đổ bóng cứng màu đen (hard solid drop shadows), màu sắc tương phản mạnh mẽ (Neo-Brutalism), phong cách hoạt hình 2D phẳng, hiện đại và trẻ trung.
|
|
233
|
+
- **Phông chữ**: Font chữ in đậm, cá tính và không chân.
|
|
234
|
+
- **Minh họa**: Mascot và các icon phẳng nét vẽ dày cá tính.
|
|
235
|
+
- **Từ khóa prompt gợi ý**: \`neo-brutalism infographic poster, vector cartoon flat 2D style, thick dark solid borders, hard black drop shadows, bright vibrant background cards (yellow, cyan, lime green, orange), playful modern bold typography.\`
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## 🔤 5. QUY TẮC PHÒNG TRÁNH LỖI FONT TIẾNG VIỆT
|
|
240
|
+
|
|
241
|
+
Mô hình Gemini 3.1 Flash Image hỗ trợ ghi text tiếng Việt cực tốt, nhưng để tránh việc AI tự động dùng các font chữ lạ bị lỗi hiển thị dấu tiếng Việt (như phác, ngã, hỏi bị lệch phông), hãy áp dụng nghiêm ngặt các quy tắc sau:
|
|
242
|
+
|
|
243
|
+
1. **Chỉ định phông chữ tiêu chuẩn**: Trong prompt, ghi rõ tên các font chữ phổ biến hỗ trợ Unicode tiếng Việt tốt như: **Arial, Inter, Montserrat, Roboto, Plus Jakarta Sans, Fredoka** (chỉ dùng cho phong cách hoạt hình).
|
|
244
|
+
_Ví dụ: "in clean bold Arial font", "using modern Montserrat typeface"._
|
|
245
|
+
2. **Tránh phông chữ lạ**: Tuyệt đối **KHÔNG** sử dụng các từ khóa như \`decorative, script, handwritten, gothic, calligraphy, futuristic fonts\` vì chúng hầu như không hỗ trợ tiếng Việt và sẽ tạo ra chữ lỗi phông rất xấu.
|
|
246
|
+
3. **Định dạng Text rõ ràng**: Đặt toàn bộ các đoạn text tiếng Việt cần hiển thị trong dấu nháy đơn hoặc nháy kép để mô hình nhận diện chính xác phần văn bản cần viết.
|
|
247
|
+
_Ví dụ: \`At the top, the main title in bold Arial font reads: 'BÍ KÍP TRÁNH NÓNG MÙA HÈ'\`._
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## 📝 6. MẪU PROMPT CHUNG CHO BOT LLM (TÙY CHỈNH THEO YÊU CẦU)
|
|
252
|
+
|
|
253
|
+
Mẫu prompt này được đúc kết từ các prompt tiêu chuẩn giúp mô hình tạo ảnh hoạt động tối ưu nhất. Bot LLM sẽ tự động tùy biến các phần nằm trong dấu ngoặc vuông \`[...]\` dựa trên tiêu đề, nội dung, số lượng bố cục và màu sắc phù hợp với chủ đề của người dùng, trong khi các phần còn lại được giữ nguyên cố định (bao gồm phong cách vẽ và footer bản quyền).
|
|
254
|
+
|
|
255
|
+
### A. Công thức Prompt Tiếng Anh (Khuyên Dùng cho API)
|
|
256
|
+
|
|
257
|
+
\`\`\`text
|
|
258
|
+
An infographic poster with [Tỷ lệ khung hình] and [Loại nền].
|
|
259
|
+
Art style is modern illustration style mixed with hand-drawn elements.
|
|
260
|
+
At the top, the main title in clean bold [Tên Font tiếng Việt chuẩn] reads: '[TIÊU ĐỀ TIẾNG VIỆT LỚN]'.
|
|
261
|
+
The layout is divided into [Số lượng] cards or sections [Bố cục chia ô từ trên xuống dưới / Bố cục ô lưới / Quy trình cách thức].
|
|
262
|
+
The background and accent colors of the cards are [Màu sắc hài hòa tương ứng phù hợp với chủ đề].
|
|
263
|
+
Each card contains a clean flat vector illustration representing [Mô tả ngắn gọn hình vẽ minh họa] and a clear text label in bold [Tên Font tiếng Việt chuẩn] reads: '[NHÃN TIẾNG VIỆT CHO TỪNG Ô]'.
|
|
264
|
+
The text throughout the image must be clean, legible, and easy to read.
|
|
265
|
+
At the bottom center of the image, there is a clean and tiny centered footer text that reads: 'designed by Williams - trợ lý của tuanminhhole'.
|
|
266
|
+
High-resolution, high quality, professional infographic poster, no spelling mistakes.
|
|
267
|
+
\`\`\`
|
|
268
|
+
|
|
269
|
+
### B. Công thức Prompt Tiếng Việt (Phong cách gốc giống Ảnh mẫu)
|
|
270
|
+
|
|
271
|
+
\`\`\`text
|
|
272
|
+
Infographic [Khung hình/tỷ lệ], nền [Loại nền].
|
|
273
|
+
Phong cách minh họa hiện đại pha hand-drawn.
|
|
274
|
+
Tiêu đề lớn '[TIÊU ĐỀ TIẾNG VIỆT LỚN]'.
|
|
275
|
+
Bố cục chia [Số lượng] ô rõ ràng [từ trên xuống dưới / dạng lưới / quy trình cách thức].
|
|
276
|
+
Màu sắc hài hòa [Mô tả tông màu phù hợp].
|
|
277
|
+
Mỗi ô vẽ minh họa vector phẳng [Mô tả ngắn hình ảnh cần vẽ cho ô] và nhãn chữ '[NHÃN TIẾNG VIỆT]'.
|
|
278
|
+
Chữ rõ ràng, dễ đọc, không sai chính tả.
|
|
279
|
+
Cạnh dưới canh giữa có chữ nhỏ: 'designed by Williams - trợ lý của tuanminhhole'.
|
|
280
|
+
Ảnh chất lượng cao, sắc nét.
|
|
281
|
+
\`\`\`
|
|
282
|
+
`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function buildInfographicGeneratorJs() {
|
|
286
|
+
return `const fs = require('fs');
|
|
287
|
+
const path = require('path');
|
|
288
|
+
|
|
289
|
+
const prompt = process.argv[2];
|
|
290
|
+
const outputPath = process.argv[3] || 'image.png';
|
|
291
|
+
|
|
292
|
+
if (!prompt) {
|
|
293
|
+
console.error('Usage: node image-generator.js "<prompt>" [output_path]');
|
|
158
294
|
process.exit(1);
|
|
159
295
|
}
|
|
160
296
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
297
|
+
// Find openclaw.json path dynamically by walking up
|
|
298
|
+
let openclawJsonPath = '';
|
|
299
|
+
let currentDir = process.cwd();
|
|
300
|
+
for (let i = 0; i < 5; i++) {
|
|
301
|
+
const candidate = path.join(currentDir, 'openclaw.json');
|
|
302
|
+
if (fs.existsSync(candidate)) {
|
|
303
|
+
openclawJsonPath = candidate;
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
const candidateInDot = path.join(currentDir, '.openclaw', 'openclaw.json');
|
|
307
|
+
if (fs.existsSync(candidateInDot)) {
|
|
308
|
+
openclawJsonPath = candidateInDot;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
const parent = path.dirname(currentDir);
|
|
312
|
+
if (parent === currentDir) break;
|
|
313
|
+
currentDir = parent;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Resolve API Key and Base URL from openclaw.json
|
|
317
|
+
let apiKey = 'sk-50599bc9642941c0-obzd49-1940044a'; // default fallback key
|
|
318
|
+
let baseUrl = 'http://9router:20128/v1'; // default fallback URL
|
|
319
|
+
if (openclawJsonPath) {
|
|
165
320
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
// Fallback to standalone headless Chromium launch
|
|
172
|
-
browser = await chromium.launch({
|
|
173
|
-
headless: true,
|
|
174
|
-
args: [
|
|
175
|
-
'--no-sandbox',
|
|
176
|
-
'--disable-gpu',
|
|
177
|
-
'--disable-dev-shm-usage',
|
|
178
|
-
'--disable-blink-features=AutomationControlled'
|
|
179
|
-
]
|
|
180
|
-
});
|
|
181
|
-
isStandalone = true;
|
|
182
|
-
ctx = await browser.newContext({
|
|
183
|
-
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
|
|
184
|
-
});
|
|
321
|
+
const config = JSON.parse(fs.readFileSync(openclawJsonPath, 'utf8'));
|
|
322
|
+
const provider = config.models?.providers?.['9router'];
|
|
323
|
+
if (provider) {
|
|
324
|
+
if (provider.apiKey) apiKey = provider.apiKey;
|
|
325
|
+
if (provider.baseUrl) baseUrl = provider.baseUrl;
|
|
185
326
|
}
|
|
327
|
+
} catch (e) {}
|
|
328
|
+
}
|
|
186
329
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
330
|
+
const modelPriorityPatterns = [
|
|
331
|
+
/recraft-?v3/i,
|
|
332
|
+
/flux-pro-?(v1\\.1-)?ultra/i,
|
|
333
|
+
/flux-kontext-max/i,
|
|
334
|
+
/flux-pro-?(v1\\.1)?/i,
|
|
335
|
+
/flux-kontext-pro/i,
|
|
336
|
+
/recraft-?v2/i,
|
|
337
|
+
/recraft/i,
|
|
338
|
+
/ideogram-?v2/i,
|
|
339
|
+
/ideogram/i,
|
|
340
|
+
/runway.*turbo/i,
|
|
341
|
+
/runway/i,
|
|
342
|
+
/flux-?(1-)?dev/i,
|
|
343
|
+
/dall-e-3/i,
|
|
344
|
+
/stable-image-ultra/i,
|
|
345
|
+
/sd3\\.5-large-turbo/i,
|
|
346
|
+
/sd3\\.5-large/i,
|
|
347
|
+
/stable-diffusion-v35/i,
|
|
348
|
+
/sd3\\.5/i,
|
|
349
|
+
/stable-image-core/i,
|
|
350
|
+
/stable-diffusion-3/i,
|
|
351
|
+
/sd3/i,
|
|
352
|
+
/sd3\\.5-medium/i,
|
|
353
|
+
/flux-?(1-)?schnell/i,
|
|
354
|
+
/grok/i,
|
|
355
|
+
/gpt/i,
|
|
356
|
+
/minimax/i,
|
|
357
|
+
/gemini-3\\.1/i,
|
|
358
|
+
/gemini-3/i,
|
|
359
|
+
/gemini-2\\.5/i,
|
|
360
|
+
/gemini/i,
|
|
361
|
+
/sdxl/i,
|
|
362
|
+
/stable-diffusion/i,
|
|
363
|
+
/sdwebui/i,
|
|
364
|
+
/comfyui/i,
|
|
365
|
+
];
|
|
366
|
+
|
|
367
|
+
(async () => {
|
|
368
|
+
try {
|
|
369
|
+
// Query active image generation models to choose the best one
|
|
370
|
+
let selectedModel = '';
|
|
371
|
+
try {
|
|
372
|
+
const modelsResponse = await fetch(\`\${baseUrl}/models/image\`, {
|
|
373
|
+
headers: {
|
|
374
|
+
'Authorization': \`Bearer \${apiKey}\`
|
|
229
375
|
}
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
for (const item of items) {
|
|
241
|
-
const titleEl = item.querySelector('h2 a');
|
|
242
|
-
if (!titleEl) continue;
|
|
243
|
-
const title = titleEl.textContent || '';
|
|
244
|
-
const url = titleEl.href;
|
|
245
|
-
let snippet = '';
|
|
246
|
-
const snippetEl = item.querySelector('.b_caption p, .b_snippet, p');
|
|
247
|
-
if (snippetEl) {
|
|
248
|
-
snippet = snippetEl.textContent || '';
|
|
249
|
-
}
|
|
250
|
-
if (url && title) {
|
|
251
|
-
list.push({ title, url, snippet });
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
return list;
|
|
255
|
-
});
|
|
256
|
-
await page.close();
|
|
257
|
-
return res;
|
|
258
|
-
} catch (e) {
|
|
259
|
-
if (page) await page.close();
|
|
260
|
-
return [];
|
|
376
|
+
});
|
|
377
|
+
const modelsData = await modelsResponse.json();
|
|
378
|
+
if (modelsData && Array.isArray(modelsData.data) && modelsData.data.length > 0) {
|
|
379
|
+
const modelIds = modelsData.data.map(m => m.id);
|
|
380
|
+
for (const pattern of modelPriorityPatterns) {
|
|
381
|
+
const found = modelIds.find(id => pattern.test(id));
|
|
382
|
+
if (found) {
|
|
383
|
+
selectedModel = found;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
261
386
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
// DuckDuckGo
|
|
265
|
-
(async () => {
|
|
266
|
-
const page = await ctx.newPage();
|
|
267
|
-
try {
|
|
268
|
-
await page.goto('https://html.duckduckgo.com/html/?q=' + encodeURIComponent(query), { waitUntil: 'domcontentloaded', timeout: 10000 });
|
|
269
|
-
const res = await page.evaluate(() => {
|
|
270
|
-
const list = [];
|
|
271
|
-
const elements = document.querySelectorAll('.result');
|
|
272
|
-
for (const el of elements) {
|
|
273
|
-
const titleEl = el.querySelector('.result__title a');
|
|
274
|
-
const snippetEl = el.querySelector('.result__snippet');
|
|
275
|
-
if (titleEl) {
|
|
276
|
-
list.push({
|
|
277
|
-
title: titleEl.textContent.trim(),
|
|
278
|
-
url: titleEl.href,
|
|
279
|
-
snippet: snippetEl ? snippetEl.textContent.trim() : ''
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return list;
|
|
284
|
-
});
|
|
285
|
-
await page.close();
|
|
286
|
-
return res;
|
|
287
|
-
} catch (e) {
|
|
288
|
-
if (page) await page.close();
|
|
289
|
-
return [];
|
|
387
|
+
if (!selectedModel) {
|
|
388
|
+
selectedModel = modelIds[0];
|
|
290
389
|
}
|
|
291
|
-
})()
|
|
292
|
-
]);
|
|
293
|
-
|
|
294
|
-
// Deduplicate results by normalized URL
|
|
295
|
-
const allResults = [...googleResults, ...bingResults, ...ddgResults];
|
|
296
|
-
const uniqueResults = [];
|
|
297
|
-
const seenUrls = new Set();
|
|
298
|
-
for (const res of allResults) {
|
|
299
|
-
if (!res.url || !res.title) continue;
|
|
300
|
-
let normUrl = res.url.replace(/^(https?:\\/\\/)?(www\\.)?/, '').toLowerCase();
|
|
301
|
-
if (normUrl.endsWith('/')) normUrl = normUrl.slice(0, -1);
|
|
302
|
-
if (!seenUrls.has(normUrl)) {
|
|
303
|
-
seenUrls.add(normUrl);
|
|
304
|
-
uniqueResults.push(res);
|
|
305
390
|
}
|
|
391
|
+
} catch (e) {
|
|
392
|
+
console.warn('[ImageGen] Failed to auto-resolve active models, using fallback:', e.message);
|
|
306
393
|
}
|
|
307
394
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const scoredResults = uniqueResults.map(res => {
|
|
311
|
-
let score = 0;
|
|
312
|
-
// Base length score
|
|
313
|
-
score += Math.min(res.snippet.length / 50, 5);
|
|
314
|
-
|
|
315
|
-
if (isPriceQuery) {
|
|
316
|
-
// Number density check
|
|
317
|
-
const numCount = (res.snippet.match(/\\d+/g) || []).length;
|
|
318
|
-
score += Math.min(numCount * 2, 10);
|
|
319
|
-
|
|
320
|
-
// Priority keywords boost
|
|
321
|
-
if (/lượng|chỉ|triệu|nghìn|vnd|usd|sjc|xe|bán|mua|giá/i.test(res.snippet)) {
|
|
322
|
-
score += 8;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
return { ...res, score };
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
// Sort by score desc
|
|
329
|
-
scoredResults.sort((a, b) => b.score - a.score);
|
|
330
|
-
|
|
331
|
-
// Map back to output format and limit
|
|
332
|
-
const output = scoredResults.map(({ score, ...rest }) => rest).slice(0, limit);
|
|
333
|
-
console.log(JSON.stringify(output, null, 2));
|
|
334
|
-
|
|
335
|
-
} catch (err) {
|
|
336
|
-
console.error(JSON.stringify({ error: err.message }));
|
|
337
|
-
} finally {
|
|
338
|
-
if (browser && isStandalone) {
|
|
339
|
-
try {
|
|
340
|
-
await browser.close();
|
|
341
|
-
} catch(e) {}
|
|
395
|
+
if (!selectedModel) {
|
|
396
|
+
selectedModel = 'gemini/gemini-3.1-flash-image-preview'; // default fallback
|
|
342
397
|
}
|
|
343
|
-
}
|
|
344
|
-
})();
|
|
345
|
-
`;
|
|
346
|
-
}
|
|
347
398
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
console.log(
|
|
373
|
-
await page.goto(param1, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
374
|
-
await page.waitForTimeout(1500);
|
|
375
|
-
console.log('[Browser] Opened: ' + (await page.title()) + ' | ' + page.url());
|
|
376
|
-
} else if (action === 'get_url') {
|
|
377
|
-
console.log(page.url());
|
|
378
|
-
} else if (action === 'status') {
|
|
379
|
-
const allPages = ctx.pages();
|
|
380
|
-
console.log('[Browser] Connected! Tabs: ' + allPages.length);
|
|
381
|
-
console.log('[Browser] Current: ' + (await page.title()) + ' | ' + page.url());
|
|
382
|
-
} else if (action === 'get_text') {
|
|
383
|
-
const maxLen = parseInt(param1) || 4000;
|
|
384
|
-
const text = await page.evaluate(() => { document.querySelectorAll('script,style,noscript,svg').forEach(e => e.remove()); return document.body.innerText.trim(); });
|
|
385
|
-
console.log(text.substring(0, maxLen));
|
|
386
|
-
} else if (action === 'get_links') {
|
|
387
|
-
const filter = param1 || '';
|
|
388
|
-
const links = await page.evaluate((f) => { const a = Array.from(document.querySelectorAll('a[href]')).map(e => e.href).filter(h => h && h.startsWith('http')); return [...new Set(f ? a.filter(h => h.includes(f)) : a)]; }, filter);
|
|
389
|
-
console.log(JSON.stringify(links.slice(0, 50), null, 2));
|
|
390
|
-
} else if (action === 'get_posts') {
|
|
391
|
-
const posts = await page.evaluate(() => {
|
|
392
|
-
const results = [];
|
|
393
|
-
const articles = document.querySelectorAll('[role="article"]');
|
|
394
|
-
for (const article of articles) {
|
|
395
|
-
const textEl = article.querySelector('[data-ad-comet-preview="message"],[data-ad-preview="message"]');
|
|
396
|
-
const fullText = (textEl ? textEl.innerText.trim() : '') || article.innerText.substring(0, 800);
|
|
397
|
-
const allLinks = Array.from(article.querySelectorAll('a[href]'));
|
|
398
|
-
let permalink = '';
|
|
399
|
-
for (const a of allLinks) { const h = a.href || ''; if (h.includes('/posts/') || h.includes('/permalink/') || h.includes('story_fbid')) { permalink = h.split('?')[0]; break; } }
|
|
400
|
-
let author = '';
|
|
401
|
-
for (const el of article.querySelectorAll('a[role="link"] strong, h2 a, h3 a, h4 a')) { const n = el.innerText.trim(); if (n && n.length > 1 && n.length < 50) { author = n; break; } }
|
|
402
|
-
let timePosted = '';
|
|
403
|
-
const timeLinks = allLinks.filter(a => { const h = a.href || ''; return h.includes('/posts/') || h.includes('/permalink/'); });
|
|
404
|
-
if (timeLinks.length > 0) { const t = timeLinks[0].innerText.trim(); if (t && t.length < 30) timePosted = t; }
|
|
405
|
-
if (!timePosted) { const te = article.querySelector('abbr,[data-utime]'); if (te) timePosted = te.innerText.trim() || te.getAttribute('title') || ''; }
|
|
406
|
-
if (fullText.length > 20) results.push({ author: author || 'N/A', text: fullText.substring(0, 500), permalink: permalink || 'N/A', time: timePosted || 'N/A' });
|
|
407
|
-
}
|
|
408
|
-
return results;
|
|
409
|
-
});
|
|
410
|
-
console.log(posts.length === 0 ? '[Browser] No posts found. Try scroll then get_posts again.' : JSON.stringify(posts.slice(0, 10), null, 2));
|
|
411
|
-
} else if (action === 'evaluate') {
|
|
412
|
-
const code = process.argv.slice(3).join(' ');
|
|
413
|
-
if (!code) { console.log('[Browser] Usage: evaluate <js_code>'); process.exit(1); }
|
|
414
|
-
const result = await page.evaluate(code);
|
|
415
|
-
console.log(result !== undefined && result !== null ? (typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result)) : '[Browser] Done');
|
|
416
|
-
} else if (action === 'console') {
|
|
417
|
-
const msgs = []; page.on('console', m => msgs.push('[' + m.type() + '] ' + m.text()));
|
|
418
|
-
await page.waitForTimeout(2000);
|
|
419
|
-
console.log(msgs.length === 0 ? '[Browser] No console messages in 2s' : msgs.join('\\n'));
|
|
420
|
-
} else if (action === 'screenshot') {
|
|
421
|
-
const p = param1 || '/tmp/screenshot.png'; await page.screenshot({ path: p, fullPage: false }); console.log('[Browser] Screenshot: ' + p);
|
|
422
|
-
} else if (action === 'screenshot_full') {
|
|
423
|
-
const p = param1 || '/tmp/screenshot_full.png'; await page.screenshot({ path: p, fullPage: true }); console.log('[Browser] Full screenshot: ' + p);
|
|
424
|
-
} else if (action === 'pdf') {
|
|
425
|
-
const p = param1 || '/tmp/page.pdf'; await page.pdf({ path: p, format: 'A4' }); console.log('[Browser] PDF: ' + p);
|
|
426
|
-
} else if (action === 'click') {
|
|
427
|
-
await page.locator(param1).first().click({ timeout: 5000 }); await page.waitForTimeout(600); console.log('[Browser] Clicked: ' + param1);
|
|
428
|
-
} else if (action === 'fill') {
|
|
429
|
-
await page.locator(param1).first().fill(param2, { timeout: 5000 }); console.log('[Browser] Filled: ' + param1);
|
|
430
|
-
} else if (action === 'press') {
|
|
431
|
-
await page.keyboard.press(param1); await page.waitForTimeout(1000); console.log('[Browser] Pressed: ' + param1);
|
|
432
|
-
} else if (action === 'hover') {
|
|
433
|
-
await page.locator(param1).first().hover({ timeout: 5000 }); console.log('[Browser] Hovered: ' + param1);
|
|
434
|
-
} else if (action === 'select') {
|
|
435
|
-
await page.locator(param1).first().selectOption(param2, { timeout: 5000 }); console.log('[Browser] Selected: ' + param2);
|
|
436
|
-
} else if (action === 'upload') {
|
|
437
|
-
await page.locator(param1).first().setInputFiles(param2, { timeout: 5000 }); console.log('[Browser] Uploaded: ' + param2);
|
|
438
|
-
} else if (action === 'scroll') {
|
|
439
|
-
const px = parseInt(param1) || 800; await page.evaluate((p) => window.scrollBy(0, p), px); await page.waitForTimeout(2000); console.log('[Browser] Scrolled: ' + px + 'px');
|
|
440
|
-
} else if (action === 'wait') {
|
|
441
|
-
const ms = parseInt(param1) || 1000; await page.waitForTimeout(ms); console.log('[Browser] Waited: ' + ms + 'ms');
|
|
442
|
-
} else if (action === 'resize') {
|
|
443
|
-
const w = parseInt(param1) || 1280, h = parseInt(param2) || 720; await page.setViewportSize({ width: w, height: h }); console.log('[Browser] Resized: ' + w + 'x' + h);
|
|
444
|
-
} else if (action === 'tabs') {
|
|
445
|
-
const ap = ctx.pages(); for (let i = 0; i < ap.length; i++) { const t = await ap[i].title().catch(() => '(untitled)'); console.log('[' + i + '] ' + t + ' | ' + ap[i].url() + (ap[i] === page ? ' < current' : '')); }
|
|
446
|
-
} else if (action === 'new_tab') {
|
|
447
|
-
const np = await ctx.newPage(); if (param1) await np.goto(param1, { waitUntil: 'domcontentloaded', timeout: 30000 }); console.log('[Browser] New tab' + (param1 ? ': ' + param1 : ''));
|
|
448
|
-
} else if (action === 'switch_tab') {
|
|
449
|
-
const idx = parseInt(param1), ap = ctx.pages(); if (isNaN(idx) || idx < 0 || idx >= ap.length) { console.log('[Browser] Invalid index. Use tabs to list.'); } else { page = ap[idx]; await page.bringToFront(); console.log('[Browser] Switched to [' + idx + ']: ' + page.url()); }
|
|
450
|
-
} else if (action === 'close_tab') {
|
|
451
|
-
const ap = ctx.pages(), idx = param1 !== undefined ? parseInt(param1) : ap.indexOf(page); if (ap.length <= 1) { console.log('[Browser] Cannot close last tab.'); } else if (isNaN(idx) || idx < 0 || idx >= ap.length) { console.log('[Browser] Invalid index.'); } else { await ap[idx].close(); console.log('[Browser] Closed tab [' + idx + ']'); }
|
|
399
|
+
console.log(\`[ImageGen] Generating: "\${prompt}" using model "\${selectedModel}"...\`);
|
|
400
|
+
const response = await fetch(\`\${baseUrl}/images/generations\`, {
|
|
401
|
+
method: 'POST',
|
|
402
|
+
headers: {
|
|
403
|
+
'Content-Type': 'application/json',
|
|
404
|
+
'Authorization': \`Bearer \${apiKey}\`
|
|
405
|
+
},
|
|
406
|
+
body: JSON.stringify({
|
|
407
|
+
model: selectedModel,
|
|
408
|
+
prompt: prompt,
|
|
409
|
+
n: 1,
|
|
410
|
+
size: 'auto',
|
|
411
|
+
response_format: 'b64_json'
|
|
412
|
+
})
|
|
413
|
+
});
|
|
414
|
+
const data = await response.json();
|
|
415
|
+
if (data.error) {
|
|
416
|
+
console.error('[ImageGen] API Error:', data.error.message || data.error);
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
if (data.data && data.data[0] && data.data[0].b64_json) {
|
|
420
|
+
const buf = Buffer.from(data.data[0].b64_json, 'base64');
|
|
421
|
+
const absoluteOutputPath = path.isAbsolute(outputPath) ? outputPath : path.join(process.cwd(), outputPath);
|
|
422
|
+
fs.writeFileSync(absoluteOutputPath, buf);
|
|
423
|
+
console.log(\`[ImageGen] Saved image to: \${outputPath}\`);
|
|
452
424
|
} else {
|
|
453
|
-
console.
|
|
454
|
-
|
|
455
|
-
console.log(' Content: get_text [max] | get_links [filter] | get_posts | evaluate <js> | console');
|
|
456
|
-
console.log(' Export: screenshot [path] | screenshot_full [path] | pdf [path]');
|
|
457
|
-
console.log(' Interact: click <sel> | fill <sel> <txt> | press <key> | hover <sel> | select <sel> <val> | upload <sel> <path>');
|
|
458
|
-
console.log(' View: scroll [px] | wait <ms> | resize <w> <h>');
|
|
459
|
-
console.log(' Tabs: tabs | new_tab [url] | switch_tab <idx> | close_tab [idx]');
|
|
425
|
+
console.error('[ImageGen] No image data returned');
|
|
426
|
+
process.exit(1);
|
|
460
427
|
}
|
|
461
|
-
} catch(e) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
} finally { if (browser) await browser.close(); }
|
|
428
|
+
} catch (e) {
|
|
429
|
+
console.error('[ImageGen] Fetch Error:', e.message);
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
466
432
|
})();
|
|
467
433
|
`;
|
|
468
434
|
}
|
|
469
435
|
|
|
470
|
-
function buildBrowserDoc(options = {}) {
|
|
471
|
-
const { isVi = true, variant = 'wizard', workspaceRoot = '' } = options;
|
|
472
|
-
const wsRoot = workspaceRoot.replace(/\/+$/, '');
|
|
473
|
-
const btPath = wsRoot ? `${wsRoot}/browser-tool.js` : 'browser-tool.js';
|
|
474
|
-
let modeHeading = '';
|
|
475
|
-
if (variant === 'cli-server') {
|
|
476
|
-
modeHeading = isVi
|
|
477
|
-
? `# 🌍 Trình duyệt ảo (Browser Automation)\n\n## 💡 Hướng dẫn vận hành:\n- **Script điều khiển:** \`browser-tool.js\` (Mọi câu lệnh browser đều chạy qua script này).\n- **Môi trường chạy:**\n - **Trên VPS / Linux Server (Headless):** Trình duyệt chạy ngầm hoàn toàn độc lập (Headless) bên trong Docker / Server qua Xvfb. Không thể mở màn hình Chrome thật.\n - **Trên Máy tính cá nhân (Windows/Mac) - Dù chạy Docker hay Native:**\n - **Mặc định:** Chạy ngầm (headless) cực kỳ ổn định.\n - **Chế độ quan sát (Xem bot click):** Nếu bạn muốn xem trực tiếp Chrome thật hoạt động trên màn hình, hãy chạy file \`start-chrome-debug.bat\` (trên Windows) hoặc \`start-chrome-debug.sh\` (trên Mac) ở máy của bạn **trước khi** bot kết nối! Bot sẽ tự động chuyển sang điều khiển màn hình Chrome thật của bạn.\n- **Kết nối mặc định:** \`http://127.0.0.1:9222\`\n\n`
|
|
478
|
-
: `# 🌍 Browser Automation\n\n## 💡 Operating Guide:\n- **Control script:** \`browser-tool.js\` (All browser commands are executed through this script).\n- **Running environment:**\n - **On VPS / Linux Server (Headless Server Mode):** The browser runs fully headless and isolated inside Docker / Server via Xvfb. No GUI Chrome can be launched.\n - **On Personal Computers (Windows/Mac) - Docker or Native:**\n - **Default:** Runs headless and stable in the background.\n - **Observer Mode (Visual Chrome GUI):** If you want to see the real Chrome window being controlled, run \`start-chrome-debug.bat\` (on Windows) or \`start-chrome-debug.sh\` (on Mac) on your host machine **before** the bot connects! The bot will automatically hook into your real desktop Chrome.\n- **Default endpoint:** \`http://127.0.0.1:9222\`\n\n`;
|
|
479
|
-
} else {
|
|
480
|
-
modeHeading = isVi
|
|
481
|
-
? `# 🌍 Hướng dẫn Browser (Chrome CDP)\n- **Script điều khiển:** \`browser-tool.js\`\n- **Kết nối Chrome debug:** \`http://127.0.0.1:9222\`\n- **Xem trực quan:** Hãy chạy file \`start-chrome-debug.bat\` (trên Windows) hoặc \`start-chrome-debug.sh\` (trên Mac) để mở Chrome chế độ Debug.\n\n`
|
|
482
|
-
: `# 🌍 Browser Guide (Chrome CDP)\n- **Control script:** \`browser-tool.js\`\n- **Chrome debug endpoint:** \`http://127.0.0.1:9222\`\n- **Visual interface:** Run \`start-chrome-debug.bat\` (on Windows) or \`start-chrome-debug.sh\` (on Mac) to open Chrome in Debug mode.\n\n`;
|
|
483
|
-
}
|
|
484
436
|
|
|
485
|
-
return `${modeHeading}# Navigation
|
|
486
|
-
node ${btPath} status
|
|
487
|
-
node ${btPath} open "https://google.com"
|
|
488
|
-
node ${btPath} get_url
|
|
489
|
-
|
|
490
|
-
# ⭐ Content extraction — LUÔN dùng get_posts cho Facebook
|
|
491
|
-
node ${btPath} get_posts
|
|
492
|
-
node ${btPath} get_text
|
|
493
|
-
node ${btPath} get_text 8000
|
|
494
|
-
node ${btPath} get_links
|
|
495
|
-
node ${btPath} get_links "/posts/"
|
|
496
|
-
node ${btPath} evaluate "document.title"
|
|
497
|
-
node ${btPath} console
|
|
498
|
-
|
|
499
|
-
# Screenshots & export
|
|
500
|
-
node ${btPath} screenshot
|
|
501
|
-
node ${btPath} screenshot_full
|
|
502
|
-
node ${btPath} pdf
|
|
503
|
-
|
|
504
|
-
# Interactions
|
|
505
|
-
node ${btPath} click "button.submit"
|
|
506
|
-
node ${btPath} fill "input[name='q']" "search"
|
|
507
|
-
node ${btPath} press "Enter"
|
|
508
|
-
node ${btPath} hover "a.link"
|
|
509
|
-
node ${btPath} select "select#id" "value"
|
|
510
|
-
node ${btPath} upload "input[type=file]" "/tmp/photo.jpg"
|
|
511
|
-
|
|
512
|
-
# Scrolling & viewport
|
|
513
|
-
node ${btPath} scroll
|
|
514
|
-
node ${btPath} scroll 1500
|
|
515
|
-
node ${btPath} wait 3000
|
|
516
|
-
node ${btPath} resize 1920 1080
|
|
517
|
-
|
|
518
|
-
# Tab management
|
|
519
|
-
node ${btPath} tabs
|
|
520
|
-
node ${btPath} new_tab "https://example.com"
|
|
521
|
-
node ${btPath} switch_tab 1
|
|
522
|
-
node ${btPath} close_tab 2`;
|
|
523
|
-
}
|
|
524
437
|
|
|
525
438
|
function buildSecurityRules(isVi = true) {
|
|
526
439
|
if (isVi) {
|
|
@@ -580,101 +493,19 @@ node ${btPath} close_tab 2`;
|
|
|
580
493
|
browserDocVariant = '',
|
|
581
494
|
} = options;
|
|
582
495
|
|
|
583
|
-
const skillsSection = skillListStr || (isVi ? '- _(Chưa có skill nào)_' : '- _(No skills installed)_');
|
|
584
|
-
|
|
585
|
-
const browserRef = hasBrowser
|
|
586
|
-
? (browserDocVariant === 'cli-server'
|
|
587
|
-
? (isVi
|
|
588
|
-
? `\n\n## 🌐 Browser Automation
|
|
589
|
-
- Xem hướng dẫn chi tiết tại **BROWSER.md**
|
|
590
|
-
- Script điều khiển: \`browser-tool.js\`
|
|
591
|
-
- Chế độ hiện tại:
|
|
592
|
-
- **Trên VPS / Linux Server:** Chạy ngầm độc lập qua Docker hoặc Xvfb.
|
|
593
|
-
- **Trên Windows/Mac (Docker hoặc Native):** Chạy ngầm mặc định, hoặc chạy file \`start-chrome-debug.bat\` / \`start-chrome-debug.sh\` để xem trình duyệt trực quan trên màn hình.
|
|
594
|
-
- Kết nối mặc định: \`http://127.0.0.1:9222\`
|
|
595
|
-
- **Tìm kiếm Web:** Nếu không có công cụ Web Search (hoặc Web Search không khả dụng/bị lỗi), hãy **luôn sử dụng ngay công cụ terminal (exec/run_command) để chạy lệnh: \`node search-tool.js "<từ khóa>" 5\`**! Lệnh này sẽ tự động chạy ngầm qua DuckDuckGo/Google/Bing bằng trình duyệt ngầm tàng hình của bạn và trả về kết quả JSON sạch ngay lập tức. Tuyệt đối KHÔNG được mở trình duyệt thủ công, chụp ảnh màn hình hay click tìm kiếm bằng tay từng bước!
|
|
596
|
-
- Nếu browser lỗi, thử lại 1 lần rồi mới báo user với lỗi cụ thể`
|
|
597
|
-
: `\n\n## 🌐 Browser Automation
|
|
598
|
-
- See detailed guide at **BROWSER.md**
|
|
599
|
-
- Control script: \`browser-tool.js\`
|
|
600
|
-
- Current mode:
|
|
601
|
-
- **On VPS / Linux Server:** Runs headless via Docker or Xvfb.
|
|
602
|
-
- **On Windows/Mac (Docker or Native):** Runs headless by default, or run \`start-chrome-debug.bat\` / \`start-chrome-debug.sh\` to see the GUI.
|
|
603
|
-
- Default endpoint: \`http://127.0.0.1:9222\`
|
|
604
|
-
- **Web Searching:** If the Web Search tool is unavailable or fails, **always use your terminal execution tool (exec/run_command) to run: \`node search-tool.js "<query>" 5\`**! This will automatically execute the search via DuckDuckGo/Google/Bing under stealth browser mode and return a clean JSON result immediately. Never open the browser manually, take screenshots, or click the search button step-by-step!
|
|
605
|
-
- If browser fails, retry once before reporting the concrete error to the user`)
|
|
606
|
-
: (isVi
|
|
607
|
-
? `\n\n## 🌐 Browser Automation
|
|
608
|
-
- Xem hướng dẫn chi tiết tại **BROWSER.md**
|
|
609
|
-
- Script điều khiển: \`browser-tool.js\`
|
|
610
|
-
- Kết nối Chrome debug: \`http://127.0.0.1:9222\`
|
|
611
|
-
- **Tìm kiếm Web:** Hãy **luôn sử dụng ngay công cụ terminal (exec/run_command) để chạy lệnh: \`node search-tool.js "<từ khóa>" 5\`**! Lệnh này sẽ tự động chạy ngầm qua DuckDuckGo/Google/Bing bằng trình duyệt ngầm tàng hình của bạn và trả về kết quả JSON sạch ngay lập tức. Tuyệt đối KHÔNG được mở trình duyệt thủ công, chụp ảnh màn hình hay click tìm kiếm bằng tay từng bước!
|
|
612
|
-
- Nếu tool/profile hỏi đường browser desktop, dùng profile \`host-chrome\` trước`
|
|
613
|
-
: `\n\n## 🌐 Browser Automation
|
|
614
|
-
- See detailed guide at **BROWSER.md**
|
|
615
|
-
- Control script: \`browser-tool.js\`
|
|
616
|
-
- Chrome debug endpoint: \`http://127.0.0.1:9222\`
|
|
617
|
-
- **Web Searching:** If the Web Search tool is unavailable or fails, **always use your terminal execution tool (exec/run_command) to run: \`node search-tool.js "<query>" 5\`**! This will automatically execute the search via DuckDuckGo/Google/Bing under stealth browser mode and return a clean JSON result immediately. Never open the browser manually, take screenshots, or click the search button step-by-step!
|
|
618
|
-
- If a desktop browser profile is needed, use the \`host-chrome\` profile first`))
|
|
619
|
-
: '';
|
|
620
|
-
|
|
621
|
-
const telegramSection = (variant === 'relay')
|
|
622
|
-
? (isVi
|
|
623
|
-
? `\n\n## Telegram\n- Đã bật \`reactionLevel:minimal\`, \`replyToMode:first\`, \`actions.sendMessage\`, và \`actions.reactions\`.\n- Trước MỖI phản hồi Telegram nhìn thấy bởi user, hãy dùng **tool/action native của OpenClaw** để thả \`👍\` trước nếu chat hiện tại hỗ trợ reaction và nếu \`👍\` chưa có sẵn.\n- Không được nói "mình vừa thả like rồi" nếu turn hiện tại chưa gọi được tool reaction hoặc reaction không hiện thật trên chat.\n- Không tự mô tả việc đi gọi Telegram Bot API thủ công.\n- Nếu tool reaction không khả dụng trong turn hiện tại hoặc chat đích không hỗ trợ reaction thật, tiếp tục trả lời bình thường thay vì tranh cãi về kỹ thuật nội bộ.\n- Khi nhận handoff từ bot khác: trả lời công khai bằng chính account Telegram của mình, ưu tiên dùng outbound Telegram action.`
|
|
624
|
-
: `\n\n## Telegram\n- Configured with \`reactionLevel:minimal\`, \`replyToMode:first\`, \`actions.sendMessage\`, and \`actions.reactions\`.\n- Before EVERY user-visible Telegram reply, use the **native OpenClaw tool/action** to add \`👍\` first when the current chat supports reactions and \`👍\` is not already present.\n- Never claim "I already liked it" unless the current turn actually executed the reaction and the reaction is real on the target chat.\n- Do not describe or roleplay manual Telegram Bot API calls.\n- If the reaction tool is unavailable in the current turn or the target chat does not support real reactions, continue replying normally instead of discussing internal limitations.\n- When receiving a handoff from another bot: reply publicly from your own Telegram account, prefer outbound Telegram action.`)
|
|
625
|
-
: '';
|
|
626
|
-
|
|
627
|
-
const cronSection = hasScheduler
|
|
628
|
-
? (isVi
|
|
629
|
-
? `\n\n## \u23F0 Cron / Lên lịch nhắc nhở (tool: \`cron\`)
|
|
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
|
-
- **Khi tạo cronjob mới (action \`add\`):**
|
|
632
|
-
- **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
|
-
- 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.
|
|
634
|
-
- **Khi user yêu cầu tắt/bật/xóa cronjob:**
|
|
635
|
-
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
|
-
2. **Bước 2 (Xử lý):**
|
|
637
|
-
- Để xóa: Gọi action \`remove\` với \`id\` tìm được.
|
|
638
|
-
- Để tắt/tạm dừng: Gọi action \`update\` với \`id\` và patch \`{"enabled": false}\`.
|
|
639
|
-
- Để bật lại: Gọi action \`update\` với \`id\` và patch \`{"enabled": true}\`.
|
|
640
|
-
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
|
-
- 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
|
-
- 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ố \`group:\` trước ID của group (ví dụ: \`group:3815464776067464419\` hoặc \`group:xxxx\`). Tuyệt đối không được chỉ điền ID thuần túy vì hệ thống sẽ hiểu nhầm đó là một DM chat cá nhân (direct message) và gửi sai địa chỉ.
|
|
644
|
-
- 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
|
-
: `\n\n## \u23F0 Cron / Scheduled Tasks (tool: \`cron\`)
|
|
646
|
-
- **Exact tool name:** The tool name is \`cron\` (never mistake it for \`native\` or external command lines).
|
|
647
|
-
- **When creating a new cronjob (action \`add\`):**
|
|
648
|
-
- **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
|
-
- 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.
|
|
650
|
-
- **When the user requests to disable/enable/delete a cronjob:**
|
|
651
|
-
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
|
-
2. **Step 2 (Processing):**
|
|
653
|
-
- To delete: Call action \`remove\` with the \`id\` found.
|
|
654
|
-
- To disable/pause: Call action \`update\` with \`id\` and patch \`{"enabled": false}\`.
|
|
655
|
-
- To enable: Call action \`update\` with \`id\` and patch \`{"enabled": true}\`.
|
|
656
|
-
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
|
-
- 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
|
-
- 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 \`group:\` prefix before the group ID (e.g., \`group:3815464776067464419\` or \`group:xxxx\`). Never specify just the numeric ID, as the system will interpret it as a private DM and deliver to the wrong destination.
|
|
660
|
-
- Skip internal doc lookups such as \`cron-jobs.mdx\`; rely on the available tools and complete the scheduling task directly.`)
|
|
661
|
-
: '';
|
|
662
|
-
|
|
663
|
-
const zaloModSection = '';
|
|
664
|
-
|
|
665
496
|
const dmOverride = isVi
|
|
666
|
-
? `\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.`
|
|
667
|
-
: `\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.`;
|
|
497
|
+
? `\n\n## Override: DM và Telegram/Zalo 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 hoặc Zalo 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.`
|
|
498
|
+
: `\n\n## Override: DM and Telegram/Zalo 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 or Zalo 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.`;
|
|
668
499
|
|
|
669
500
|
if (variant === 'relay') {
|
|
670
501
|
return isVi
|
|
671
|
-
? `# Hướng dẫn dùng tool\n\n##
|
|
672
|
-
: `# Tool Usage Guide\n\n##
|
|
502
|
+
? `# Hướng dẫn dùng tool\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.\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}/\`.\n\n## 📁 Kỹ năng (Skills)\n- Xem chi tiết hướng dẫn các kỹ năng được cài đặt tại thư mục [skills](./skills/).\n${dmOverride}\n`
|
|
503
|
+
: `# Tool Usage Guide\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}/\`.\n\n## 📁 Skills\n- See detailed guidelines of installed skills in the [skills](./skills/) directory.\n${dmOverride}\n`;
|
|
673
504
|
}
|
|
674
505
|
|
|
675
506
|
return isVi
|
|
676
|
-
? `# Hướng dẫn sử dụng Tools\n\n##
|
|
677
|
-
: `# Tool Usage Guide\n\n##
|
|
507
|
+
? `# Hướng dẫn sử dụng Tools\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\n\n## 📁 Kỹ năng (Skills)\n- Xem chi tiết hướng dẫn các kỹ năng được cài đặt tại thư mục [skills](./skills/).\n\n## 📁 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## ⚠️ 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`
|
|
508
|
+
: `# Tool Usage Guide\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\n\n## 📁 Skills\n- See detailed guidelines of installed skills in the [skills](./skills/) directory.\n\n## 📁 File & Workspace\n- Bot can read/write files in workspace: \`${workspacePath}\`\n\n## ⚠️ 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`;
|
|
678
509
|
}
|
|
679
510
|
function buildTeamsDoc(options = {}) {
|
|
680
511
|
const {
|
|
@@ -715,6 +546,7 @@ node ${btPath} close_tab 2`;
|
|
|
715
546
|
* @property {string} [teamRosterFormatted]
|
|
716
547
|
* @property {string} [emoji]
|
|
717
548
|
* @property {boolean} [hasScheduler]
|
|
549
|
+
* @property {boolean} [hasImageGen]
|
|
718
550
|
* @property {boolean} [hasZaloMod]
|
|
719
551
|
*/
|
|
720
552
|
|
|
@@ -749,6 +581,7 @@ node ${btPath} close_tab 2`;
|
|
|
749
581
|
teamRosterFormatted = '',
|
|
750
582
|
emoji = '',
|
|
751
583
|
hasScheduler = false,
|
|
584
|
+
hasImageGen = false,
|
|
752
585
|
hasZaloMod = false,
|
|
753
586
|
} = opts;
|
|
754
587
|
|
|
@@ -769,20 +602,19 @@ node ${btPath} close_tab 2`;
|
|
|
769
602
|
'HEARTBEAT.md': buildHeartbeatDoc({ isVi }),
|
|
770
603
|
'BOOTSTRAP.md': buildBootstrapDoc({ isVi, botName }),
|
|
771
604
|
'DREAMS.md': buildDreamsDoc({ isVi }),
|
|
772
|
-
'search-tool.js': buildSearchToolJs(),
|
|
773
605
|
};
|
|
774
606
|
|
|
775
607
|
if (isMultiBot) {
|
|
776
608
|
files['TEAMS.md'] = buildTeamsDoc({ isVi, teamRosterFormatted, otherAgents });
|
|
777
609
|
}
|
|
778
610
|
|
|
779
|
-
if (
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
files['
|
|
611
|
+
if (hasScheduler) {
|
|
612
|
+
files['skills/cronjob/SKILL.md'] = buildCronjobSkillMd(isVi);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (hasImageGen) {
|
|
616
|
+
files['skills/infographic-generator/SKILL.md'] = buildInfographicGeneratorSkillMd();
|
|
617
|
+
files['skills/infographic-generator/image-generator.js'] = buildInfographicGeneratorJs();
|
|
786
618
|
}
|
|
787
619
|
|
|
788
620
|
return files;
|
|
@@ -797,13 +629,13 @@ node ${btPath} close_tab 2`;
|
|
|
797
629
|
buildDreamsDoc,
|
|
798
630
|
buildHeartbeatDoc,
|
|
799
631
|
buildBootstrapDoc,
|
|
800
|
-
buildSearchToolJs,
|
|
801
|
-
buildBrowserToolJs,
|
|
802
|
-
buildBrowserDoc,
|
|
803
632
|
buildSecurityRules,
|
|
804
633
|
buildAgentsDoc,
|
|
805
634
|
buildToolsDoc,
|
|
806
635
|
buildTeamsDoc,
|
|
636
|
+
buildCronjobSkillMd,
|
|
637
|
+
buildInfographicGeneratorSkillMd,
|
|
638
|
+
buildInfographicGeneratorJs,
|
|
807
639
|
buildWorkspaceFileMap,
|
|
808
640
|
};
|
|
809
641
|
|