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 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.0",
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
+ }