abs-zalo-bot 0.8.0 → 0.8.2
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 +22 -0
- package/package.json +1 -1
- package/src/message_debounce.js +42 -0
- package/src/quote_resolver.js +65 -0
package/README.md
CHANGED
|
@@ -188,6 +188,28 @@ For profile-aware quality, select `gateway_skill = "your-owner-authored-hermes-s
|
|
|
188
188
|
|
|
189
189
|
---
|
|
190
190
|
|
|
191
|
+
## 🎭 5-Layer Agent Scaffolding (`templates/zalo-agent-scaffolding/`)
|
|
192
|
+
|
|
193
|
+
Turn raw command-line AI into a warm, reliable, enterprise-grade Zalo AI Assistant in 60 seconds across 5 structured knowledge layers:
|
|
194
|
+
|
|
195
|
+
| Layer | File | Purpose |
|
|
196
|
+
| :--- | :--- | :--- |
|
|
197
|
+
| **1. Soul** | `template_soul.md` | Core service philosophy, patience, empathy & outcome-driven mindset |
|
|
198
|
+
| **2. Persona** | `template_persona.md` | Natural Vietnamese mobile chat tone, anti-AI-slop & F-shape reading layout |
|
|
199
|
+
| **3. Identity** | `template_identity.md` | Role definition, RBAC boundaries, autonomous actions vs owner approval |
|
|
200
|
+
| **4. Memory** | `template_memory.md` | Durable facts storage for recurring customers without context bloat |
|
|
201
|
+
| **5. Context** | `template_context.md` | Business catalog, pricing packages, sales policies & intake workflows |
|
|
202
|
+
|
|
203
|
+
### ⚡ 1-Command Setup
|
|
204
|
+
To scaffold a complete 5-layer profile for your Hermes Agent, simply run:
|
|
205
|
+
```bash
|
|
206
|
+
bash templates/zalo-agent-scaffolding/setup.sh my-zalo-assistant
|
|
207
|
+
hermes --profile my-zalo-assistant
|
|
208
|
+
```
|
|
209
|
+
*Read full documentation at [`docs/5-layer-agent-framework.md`](docs/5-layer-agent-framework.md).*
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
191
213
|
## 🧠 Hermes Agent Starter Kit (`hermes-plugin/starter-kit/`)
|
|
192
214
|
|
|
193
215
|
Ready-to-use "Digital Brain" template for Hermes Agent:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abs-zalo-bot",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ABS Zalo Agent Engine — Free, Transparent & Autonomous Zalo AI Agent Engine for Hermes, Claude Code & Codex. Dual Personal QR + Official OA, Group Administration, Lead Intel, Polls, Reactions & MCP Server.",
|
|
6
6
|
"author": "teddiesloco",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Debounce — gom bọt chat liên tiếp từ cùng 1 người trong cùng 1 luồng.
|
|
3
|
+
*
|
|
4
|
+
* Người Việt thường nhắn 3–4 câu ngắn liên tục trong 1–2 giây:
|
|
5
|
+
* "anh ơi" → "giá tour Phú Quý" → "cho 3 người cuối tuần"
|
|
6
|
+
* Nếu không gom lại, Agent sẽ chạy 3 LLM call song song, trả lời đè nhau.
|
|
7
|
+
*
|
|
8
|
+
* YAGNI: Không cần queue phức tạp. Map<key, {timer, texts[]}> là đủ.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const DEFAULT_WINDOW_MS = 1_800; // 1.8s — đủ để người gõ thêm câu, không làm khách đợi lâu
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {Map} pending - Shared state: Map<key, {timer, parts, resolve}>
|
|
15
|
+
* @param {string} key - "<accountId>:<sourceId>:<senderId>"
|
|
16
|
+
* @param {string} text - Nội dung tin nhắn vừa tới
|
|
17
|
+
* @param {Function} flush - Callback khi cửa sổ đóng: flush(key, combinedText)
|
|
18
|
+
* @param {number} [windowMs]
|
|
19
|
+
*/
|
|
20
|
+
export function debounceMessage(pending, key, text, flush, windowMs = DEFAULT_WINDOW_MS) {
|
|
21
|
+
const entry = pending.get(key);
|
|
22
|
+
if (entry) {
|
|
23
|
+
clearTimeout(entry.timer);
|
|
24
|
+
entry.parts.push(text);
|
|
25
|
+
} else {
|
|
26
|
+
pending.set(key, { parts: [text], timer: null });
|
|
27
|
+
}
|
|
28
|
+
const slot = pending.get(key);
|
|
29
|
+
slot.timer = setTimeout(() => {
|
|
30
|
+
pending.delete(key);
|
|
31
|
+
flush(key, slot.parts.join("\n"));
|
|
32
|
+
}, windowMs);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Xây debounce key từ normalized event.
|
|
37
|
+
*/
|
|
38
|
+
export function debounceKey(event) {
|
|
39
|
+
return `${event.account_id}:${event.source_id}:${event.sender_id}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { DEFAULT_WINDOW_MS };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quote Resolution — giải mã tin nhắn trích dẫn (reply/quote) trong nhóm Zalo.
|
|
3
|
+
*
|
|
4
|
+
* Khi khách quote một ảnh cũ rồi gõ "Cái này còn hàng không?", Zalo gửi:
|
|
5
|
+
* { quote: { msgId, content: { ... } } }
|
|
6
|
+
* Nếu Agent chỉ đọc text hiện tại, nó sẽ hỏi ngược "Cái nào ạ?" — trải nghiệm tệ.
|
|
7
|
+
*
|
|
8
|
+
* Module này extract nội dung quoted và dán vào context prompt dưới dạng:
|
|
9
|
+
* [Trích dẫn] <nội dung tin gốc>
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { extractText } from "./schema.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Giải mã payload quote từ Zalo message data.
|
|
16
|
+
* @param {object|undefined} quote - data.quote từ raw Zalo message
|
|
17
|
+
* @returns {string} - "" nếu không có / không đọc được
|
|
18
|
+
*/
|
|
19
|
+
export function resolveQuoteText(quote) {
|
|
20
|
+
if (!quote) return "";
|
|
21
|
+
|
|
22
|
+
// Zalo quote payload có thể chứa content.title hoặc content trực tiếp
|
|
23
|
+
const content = quote.content ?? quote.msg ?? quote;
|
|
24
|
+
|
|
25
|
+
// Text thẳng
|
|
26
|
+
if (typeof content === "string" && content.trim()) {
|
|
27
|
+
return content.trim().slice(0, 500);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Content là object (ảnh, file, tin nhắn có cấu trúc)
|
|
31
|
+
if (typeof content === "object" && content !== null) {
|
|
32
|
+
// Ưu tiên title (sticker name, file name, album title)
|
|
33
|
+
const title = content.title || content.name || "";
|
|
34
|
+
const body = extractText(content);
|
|
35
|
+
// extractText thường trả về title khi không có body thật — loại trùng
|
|
36
|
+
const combined = (body && body !== title)
|
|
37
|
+
? [title, body].filter(Boolean).join(" — ").trim()
|
|
38
|
+
: title.trim();
|
|
39
|
+
if (combined) return combined.slice(0, 500);
|
|
40
|
+
|
|
41
|
+
// Fallback: loại media
|
|
42
|
+
const mediaType =
|
|
43
|
+
content.type === 2 ? "[Ảnh]"
|
|
44
|
+
: content.type === 3 ? "[Video]"
|
|
45
|
+
: content.type === 5 ? "[File]"
|
|
46
|
+
: content.type === 6 ? "[Nhãn dán]"
|
|
47
|
+
: content.type === 8 ? "[Audio]"
|
|
48
|
+
: "[Nội dung đa phương tiện]";
|
|
49
|
+
return mediaType;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return "";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Nếu event có quoted content, gắn thêm prefix [Trích dẫn] vào text để Agent hiểu ngữ cảnh.
|
|
57
|
+
* @param {object} event - Normalized event từ schema.js
|
|
58
|
+
* @param {object|undefined} rawQuote - data.quote từ raw Zalo message
|
|
59
|
+
* @returns {string} - text đã kèm ngữ cảnh trích dẫn
|
|
60
|
+
*/
|
|
61
|
+
export function enrichTextWithQuote(event, rawQuote) {
|
|
62
|
+
const quoteText = resolveQuoteText(rawQuote);
|
|
63
|
+
if (!quoteText) return event.text;
|
|
64
|
+
return `[Trích dẫn] ${quoteText}\n${event.text}`;
|
|
65
|
+
}
|