@ardennguyen/zalo-agent-cli 2.2.0 → 2.2.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 +6 -6
- package/package.json +2 -2
- package/src/commands/mcp.js +42 -0
- package/src/core/db.js +44 -0
- package/src/mcp/mcp-config.js +1 -0
- package/src/mcp/mcp-http-transport.js +40 -0
- package/src/mcp/mcp-tools.js +1080 -42
- package/src/mcp/message-buffer.js +62 -1
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ Xây dựng trên [zca-js](https://github.com/RFS-ADRENO/zca-js).
|
|
|
50
50
|
> zalo-agent mcp start --account <ID> # Ghim tài khoản (chạy nhiều tài khoản live cùng lúc)
|
|
51
51
|
> zalo-agent mcp start --http 3847 --auth your-secret # HTTP (VPS)
|
|
52
52
|
> ```
|
|
53
|
-
>
|
|
53
|
+
> 21 tools: `zalo_get_messages` · `zalo_send_message` · `zalo_list_threads` · `zalo_search_threads` · `zalo_mark_read` · `zalo_get_history` · `zalo_view_media` · `zalo_react` · `zalo_undo` · `zalo_get_group_members` · `zalo_list_conversations` · `zalo_coverage` · `zalo_sync` · `zalo_sync_status` · `zalo_search_messages` · `zalo_lookup_user` · `zalo_forward_message` · `zalo_delete_message` · `zalo_reminder` · `zalo_note` · `zalo_poll`
|
|
54
54
|
|
|
55
55
|
[Sync qua MCP](docs/mcp-sync.vi.md) · [Ghim đa tài khoản live](docs/account-pinning.vi.md).
|
|
56
56
|
> Auto-reconnect · thread filter · noise reduction · tự tải media · thông báo qua nhóm Zalo · chạy song song nhiều tài khoản (`--account <ID>`)
|
|
@@ -124,7 +124,7 @@ Tất cả lệnh hỗ trợ `--json`. Tài liệu đầy đủ: **[Wiki](https:
|
|
|
124
124
|
| `label` | 2 | Nhãn hội thoại | [Nhãn](https://github.com/ardennguyen/zalo-agent-cli/wiki/Nh%C3%A3n) |
|
|
125
125
|
| `catalog` | 9 | zBusiness — danh mục sản phẩm | [zBusiness](https://github.com/ardennguyen/zalo-agent-cli/wiki/zBusiness) |
|
|
126
126
|
| `listen` | 1 | Lắng nghe real-time, webhook, JSONL, ghi vào cache cục bộ | [Lắng nghe](https://github.com/ardennguyen/zalo-agent-cli/wiki/L%E1%BA%AFng-Nghe) |
|
|
127
|
-
| `mcp` | 1 | MCP server (stdio/HTTP) —
|
|
127
|
+
| `mcp` | 1 | MCP server (stdio/HTTP) — 21 tools cho AI agent | [MCP Server](https://github.com/ardennguyen/zalo-agent-cli/wiki/MCP-Server-(VN)) |
|
|
128
128
|
| **`oa`** | **32** | **Zalo Official Account API v3.0 — OAuth, tin nhắn, follower, tag, bài viết, cửa hàng, webhook** | **[Official Account](https://github.com/ardennguyen/zalo-agent-cli/wiki/Official-Account-(VN))** |
|
|
129
129
|
|
|
130
130
|
Xem thêm: [Đa tài khoản & Proxy](https://github.com/ardennguyen/zalo-agent-cli/wiki/%C4%90a-T%C3%A0i-Kho%E1%BA%A3n-&-Proxy) · [Cài đặt VPS](https://github.com/ardennguyen/zalo-agent-cli/wiki/C%C3%A0i-%C4%90%E1%BA%B7t-VPS) · [Thẻ chuyển khoản & QR](https://github.com/ardennguyen/zalo-agent-cli/wiki/Th%E1%BA%BB-Chuy%E1%BB%83n-Kho%E1%BA%A3n-&-QR) · [Bộ nhớ đệm & Đồng bộ](https://github.com/ardennguyen/zalo-agent-cli/wiki/B%E1%BB%99-Nh%E1%BB%9B-%C4%90%E1%BB%87m-&-%C4%90%E1%BB%93ng-B%E1%BB%99) · [Bảo mật](https://github.com/ardennguyen/zalo-agent-cli/wiki/B%E1%BA%A3o-M%E1%BA%ADt)
|
|
@@ -137,7 +137,7 @@ Xem thêm: [Đa tài khoản & Proxy](https://github.com/ardennguyen/zalo-agent-
|
|
|
137
137
|
- Đa tài khoản với proxy riêng biệt (1:1), device fingerprint riêng cho từng tài khoản; chạy **nhiều tài khoản live đồng thời** (mỗi tài khoản một tiến trình `mcp start` hoặc `listen` kèm `--account <ID>`, và truyền `--account <ID>` / `ZALO_ACCOUNT` trên mọi lệnh CLI mà không ghi đè `accounts.json`)
|
|
138
138
|
- **190 lệnh** phủ hết tính năng Zalo cá nhân
|
|
139
139
|
- **Zalo Official Account (OA) API v3.0** — OAuth login, gửi tin nhắn, follower, tag, bài viết, cửa hàng, webhook listener, multi-OA
|
|
140
|
-
- **MCP server** (stdio + HTTP) —
|
|
140
|
+
- **MCP server** (stdio + HTTP) — 21 tools cho Claude Code và các MCP client: đọc, gửi (văn bản, link preview, danh thiếp, vào My Documents, đánh dấu Quan trọng/Khẩn cấp), tìm kiếm tin nhắn trong cache, tra cứu người dùng, thả cảm xúc, thu hồi, chuyển tiếp, xóa tin, nhắc hẹn, ghi chú/ghim tin, bình chọn, xem thành viên nhóm, liệt kê hội thoại gần đây và kiểm tra độ đầy đủ của cache
|
|
141
141
|
- **Bộ nhớ đệm cục bộ (SQLite)** — `listen` ghi mọi tin nhắn vào `zalo.db`, `msg history` đọc từ cache, `sync` (hoặc `sync-mobile`) khôi phục toàn bộ lịch sử từ điện thoại vào `zalo.db` (giải mã transfer-sync-v2; xác nhận một lần trên điện thoại), mặc định lấy **toàn bộ** lịch sử (không còn mốc chặn 2024-01-01); thêm `--days <n>` hoặc `--from <YYYY-MM-DD>` nếu chỉ cần một khoảng. Dọn dẹp: `sync-media --prune <days|all>` giải phóng dung lượng nhưng giữ nguyên nội dung tin nhắn, `--prune-orphans` nhắm vào hội thoại tài khoản không còn, và `conv forget` xoá hẳn bản sao cục bộ của một hội thoại — không cái nào gửi gì tới Zalo. Luồng đồng bộ chỉ mang **liên kết** media chứ không mang file, nên sau khi lưu tin nhắn, `sync-mobile` **tự động tải media về** (thêm `--messages-only` nếu chỉ muốn tin nhắn); `sync-media` chạy lại/tiếp tục bước tải này, không cần xác nhận trên điện thoại. Ghi chú, tin ghim, bình chọn và nhắc hẹn nằm ngoài luồng tin nhắn — dùng `sync-boards`
|
|
142
142
|
- Tự động tải media (ảnh/audio/video) — một downloader duy nhất cho `listen`, `msg history`, `mcp start` và các lệnh sync, lưu vào `~/.zalo-agent-cli/accounts/<id>/media/<threadId>/` (hoặc tách riêng credentials, database `zalo.db`, media và OA sang các ổ đĩa/mount khác nhau qua `--credentials-dir`, `--data-dir`, `--media-dir`, `--oa-dir` / biến môi trường `ZALO_*_DIR` trong `~/.zalo-agent-cli/.env`; nếu dùng kèm `zalo-mcp`, chỉ nên giữ cấu hình PORT trong `zalo-mcp/.env` và đặt toàn bộ đường dẫn lưu trữ vào `<CONFIG_DIR>/.env`)
|
|
143
143
|
- Thẻ chuyển khoản (55+ ngân hàng VN) & QR VietQR
|
|
@@ -215,7 +215,7 @@ CLI tool for Zalo automation — multi-account, proxy support, bank transfers, Q
|
|
|
215
215
|
> zalo-agent mcp start --account <ID> # Pin account (run multiple live accounts at once)
|
|
216
216
|
> zalo-agent mcp start --http 3847 --auth your-secret # HTTP (VPS)
|
|
217
217
|
> ```
|
|
218
|
-
>
|
|
218
|
+
> 21 tools: `zalo_get_messages` · `zalo_send_message` · `zalo_list_threads` · `zalo_search_threads` · `zalo_mark_read` · `zalo_get_history` · `zalo_view_media` · `zalo_react` · `zalo_undo` · `zalo_get_group_members` · `zalo_list_conversations` · `zalo_coverage` · `zalo_sync` · `zalo_sync_status` · `zalo_search_messages` · `zalo_lookup_user` · `zalo_forward_message` · `zalo_delete_message` · `zalo_reminder` · `zalo_note` · `zalo_poll`
|
|
219
219
|
|
|
220
220
|
[MCP sync](docs/mcp-sync.md) · [Multi-account pinning](docs/account-pinning.md).
|
|
221
221
|
> Auto-reconnect · thread filter · noise reduction · media auto-download · Zalo-group notifications · concurrent multi-account (`--account <ID>`)
|
|
@@ -255,7 +255,7 @@ Full docs: **[Wiki](https://github.com/ardennguyen/zalo-agent-cli/wiki)** · [Fu
|
|
|
255
255
|
| `label` | 2 | Conversation labels | [Labels](https://github.com/ardennguyen/zalo-agent-cli/wiki/Labels) |
|
|
256
256
|
| `catalog` | 9 | zBusiness catalogs & products | [Catalog](https://github.com/ardennguyen/zalo-agent-cli/wiki/Catalog) |
|
|
257
257
|
| `listen` | 1 | Real-time listener, webhook, JSONL, local cache writes | [Listener](https://github.com/ardennguyen/zalo-agent-cli/wiki/Listener) |
|
|
258
|
-
| `mcp` | 1 | MCP server (stdio/HTTP) —
|
|
258
|
+
| `mcp` | 1 | MCP server (stdio/HTTP) — 21 tools for AI agents | [MCP Server](https://github.com/ardennguyen/zalo-agent-cli/wiki/MCP-Server) |
|
|
259
259
|
| **`oa`** | **32** | **Zalo Official Account API v3.0 — OAuth, messaging, followers, tags, articles, store, webhook** | **[Official Account](https://github.com/ardennguyen/zalo-agent-cli/wiki/Official-Account)** |
|
|
260
260
|
|
|
261
261
|
See also: [Multi-Account & Proxy](https://github.com/ardennguyen/zalo-agent-cli/wiki/Multi-Account-&-Proxy) · [VPS Setup](https://github.com/ardennguyen/zalo-agent-cli/wiki/VPS-Setup) · [Bank Card & QR Payments](https://github.com/ardennguyen/zalo-agent-cli/wiki/Bank-Card-&-QR-Payments) · [Local Cache & Sync](https://github.com/ardennguyen/zalo-agent-cli/wiki/Local-Cache-&-Sync) · [Security](https://github.com/ardennguyen/zalo-agent-cli/wiki/Security)
|
|
@@ -266,7 +266,7 @@ See also: [Multi-Account & Proxy](https://github.com/ardennguyen/zalo-agent-cli/
|
|
|
266
266
|
- Multi-account with a dedicated proxy (1:1) and a per-account device fingerprint; run **multiple live accounts simultaneously** (one `mcp start` or `listen` process per account with `--account <ID>`, plus `--account <ID>` / `ZALO_ACCOUNT` on any CLI command without mutating `accounts.json`)
|
|
267
267
|
- **190 commands** covering the personal-account Zalo surface
|
|
268
268
|
- **Zalo Official Account (OA) API v3.0** — OAuth login, messaging, followers, tags, articles, store, webhook listener, multi-OA
|
|
269
|
-
- **MCP server** (stdio + HTTP) —
|
|
269
|
+
- **MCP server** (stdio + HTTP) — 21 tools for Claude Code and other MCP clients: read, send (text, link previews, contact cards, to My Documents too, marked Important/Urgent), search cached messages, look up users, react, recall, forward, delete, reminders, group notes/pins, group polls, list a group's members, list recent conversations, and check how complete the cache is
|
|
270
270
|
- **Local SQLite cache** — `listen` writes every message to `zalo.db`, `msg history` reads from it, and `sync` (or `sync-mobile`) restores full history from the phone into `zalo.db` (transfer-sync-v2 decrypt; one confirmation on the phone), defaulting to **all** history (the old 2024-01-01 floor is gone), with `--days <n>` or `--from <YYYY-MM-DD>` to narrow it. Housekeeping: `sync-media --prune <days|all>` reclaims disk while keeping message text, `--prune-orphans` targets conversations the account no longer has, and `conv forget` removes a conversation's local copy entirely — none of which contact Zalo. The sync stream carries media **links**, not files, so a `sync-mobile` run **downloads its attachments automatically** once the messages are stored (`--messages-only` skips it); `sync-media` re-runs or resumes that fetch on its own, with no phone confirmation. Notes, pinned messages, polls and reminders live outside the message stream — `sync-boards` fetches those
|
|
271
271
|
- Automatic media download — one downloader shared by `listen`, `msg history`, `mcp start` and the sync commands, saving to `~/.zalo-agent-cli/accounts/<id>/media/<threadId>/` (or separate credentials, `zalo.db`, media, and OA credentials onto independent volumes/mounts via `--credentials-dir`, `--data-dir`, `--media-dir`, `--oa-dir` / `ZALO_*_DIR` in `~/.zalo-agent-cli/.env`; when using `zalo-mcp`, keep `zalo-mcp/.env` for PORT settings only and place all storage/engine settings in `<CONFIG_DIR>/.env`)
|
|
272
272
|
- Bank cards (55+ VN banks) and VietQR payment images
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ardennguyen/zalo-agent-cli",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"description": "CLI tool for Zalo automation — multi-account, proxy, bank transfers, QR payments, Official Account API v3.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -77,6 +77,6 @@
|
|
|
77
77
|
},
|
|
78
78
|
"allowScripts": {
|
|
79
79
|
"better-sqlite3@13.0.3": true,
|
|
80
|
-
"@ardennguyen/zalo-agent-cli@2.
|
|
80
|
+
"@ardennguyen/zalo-agent-cli@2.2.2": true
|
|
81
81
|
}
|
|
82
82
|
}
|
package/src/commands/mcp.js
CHANGED
|
@@ -90,6 +90,38 @@ export function normalizeMessage(msg, info = null) {
|
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Fire-and-forget HTTP POST for an incoming buffered MCP message when a
|
|
95
|
+
* webhook URL is configured (`-w, --webhook <url>` or `webhook` in
|
|
96
|
+
* `mcp-config.json`). Never blocks or throws into the listener loop.
|
|
97
|
+
*
|
|
98
|
+
* @param {string|null} webhookUrl
|
|
99
|
+
* @param {object} message - Normalized message object
|
|
100
|
+
* @param {object} [extra] - Additional top-level fields (e.g. `{ accountId }`)
|
|
101
|
+
* @param {Function} [fetchFn=fetch] - Injectable fetch for unit testing
|
|
102
|
+
* @returns {Promise<boolean>} True when dispatched, false when skipped or failed
|
|
103
|
+
*/
|
|
104
|
+
export async function postMCPWebhook(webhookUrl, message, extra = {}, fetchFn = fetch) {
|
|
105
|
+
if (!webhookUrl) return false;
|
|
106
|
+
const payload = {
|
|
107
|
+
event: "message",
|
|
108
|
+
...extra,
|
|
109
|
+
...message,
|
|
110
|
+
};
|
|
111
|
+
try {
|
|
112
|
+
await fetchFn(webhookUrl, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: { "Content-Type": "application/json" },
|
|
115
|
+
body: JSON.stringify(payload),
|
|
116
|
+
signal: AbortSignal.timeout(5000),
|
|
117
|
+
});
|
|
118
|
+
return true;
|
|
119
|
+
} catch (e) {
|
|
120
|
+
console.error(`[mcp] Webhook failed: ${e.message}`);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
93
125
|
export function registerMCPCommands(program) {
|
|
94
126
|
const mcp = program.command("mcp").description("MCP server for AI agent integration");
|
|
95
127
|
|
|
@@ -104,6 +136,10 @@ export function registerMCPCommands(program) {
|
|
|
104
136
|
.option("--http <port>", "Use HTTP transport on specified port (default: stdio)")
|
|
105
137
|
.option("--auth <token>", "Bearer token for HTTP auth (only with --http)")
|
|
106
138
|
.option("--host <address>", "HTTP bind address (default: 127.0.0.1, only with --http)")
|
|
139
|
+
.option(
|
|
140
|
+
"-w, --webhook <url>",
|
|
141
|
+
"POST each watched incoming message as JSON to this URL (or set webhook in mcp-config.json)",
|
|
142
|
+
)
|
|
107
143
|
.option(
|
|
108
144
|
"--no-delivered-receipts",
|
|
109
145
|
"Do not acknowledge received messages as delivered (every Zalo client does; seen receipts are never sent)",
|
|
@@ -281,6 +317,10 @@ export function registerMCPCommands(program) {
|
|
|
281
317
|
|
|
282
318
|
// Setup notifier (sends to Zalo group when agent is offline)
|
|
283
319
|
const notifier = new ZaloNotifier(sessionApi, config);
|
|
320
|
+
const webhookUrl = opts.webhook?.trim() || config.webhook?.trim() || null;
|
|
321
|
+
if (webhookUrl) {
|
|
322
|
+
console.error(`[mcp] Webhook enabled: ${webhookUrl}`);
|
|
323
|
+
}
|
|
284
324
|
|
|
285
325
|
// Delivered receipts: the same shared implementation `listen` uses
|
|
286
326
|
// (AGENTS.md §13 -- the two listeners must not differ). Diagnostics
|
|
@@ -366,6 +406,7 @@ export function registerMCPCommands(program) {
|
|
|
366
406
|
if (!filter.shouldKeep(normalized)) continue;
|
|
367
407
|
buffer.push(normalized.threadId, normalized);
|
|
368
408
|
notifier.onMessage(normalized);
|
|
409
|
+
postMCPWebhook(webhookUrl, normalized, { accountId: activeAcc.ownId });
|
|
369
410
|
}
|
|
370
411
|
},
|
|
371
412
|
});
|
|
@@ -433,6 +474,7 @@ export function registerMCPCommands(program) {
|
|
|
433
474
|
|
|
434
475
|
buffer.push(normalized.threadId, normalized);
|
|
435
476
|
notifier.onMessage(normalized);
|
|
477
|
+
postMCPWebhook(webhookUrl, normalized, { accountId: activeAcc.ownId });
|
|
436
478
|
console.error(`[mcp] Buffered ${normalized.threadType} msg from ${normalized.threadId}`);
|
|
437
479
|
});
|
|
438
480
|
|
package/src/core/db.js
CHANGED
|
@@ -621,6 +621,50 @@ export function getMessageById(msgId) {
|
|
|
621
621
|
return db.prepare("SELECT * FROM messages WHERE msgId = ?").get(String(msgId)) || null;
|
|
622
622
|
}
|
|
623
623
|
|
|
624
|
+
/**
|
|
625
|
+
* Search cached messages by text substring across one or all conversations, newest first.
|
|
626
|
+
*
|
|
627
|
+
* Purely local SQLite query — never touches the Zalo network. Excludes recalled/deleted
|
|
628
|
+
* tombstones (`type = 'deleted'`) and escapes SQLite LIKE wildcards (`%`, `_`, `\`) so literal
|
|
629
|
+
* characters in `query` match as literals.
|
|
630
|
+
*
|
|
631
|
+
* @param {object} opts
|
|
632
|
+
* @param {string} opts.query - Substring to match in message text (case-insensitive for ASCII)
|
|
633
|
+
* @param {string} [opts.threadId] - Restrict to one conversation
|
|
634
|
+
* @param {string} [opts.senderId] - Restrict to one sender UID
|
|
635
|
+
* @param {number} [opts.since] - Only messages at or after this epoch-ms timestamp
|
|
636
|
+
* @param {number} [opts.until] - Only messages at or before this epoch-ms timestamp
|
|
637
|
+
* @param {number} [opts.limit=50] - Max messages to return
|
|
638
|
+
* @returns {Array<object>}
|
|
639
|
+
*/
|
|
640
|
+
export function searchMessages(opts = {}) {
|
|
641
|
+
if (!db) throw new Error("Database not initialized");
|
|
642
|
+
const { query, threadId, senderId, since, until, limit = 50 } = opts;
|
|
643
|
+
const escaped = String(query ?? "").replace(/[\\%_]/g, "\\$&");
|
|
644
|
+
const where = ["COALESCE(type, '') != 'deleted'", "text IS NOT NULL", "text LIKE ? ESCAPE '\\'"];
|
|
645
|
+
const params = [`%${escaped}%`];
|
|
646
|
+
if (threadId) {
|
|
647
|
+
where.push("threadId = ?");
|
|
648
|
+
params.push(String(threadId));
|
|
649
|
+
}
|
|
650
|
+
if (senderId) {
|
|
651
|
+
where.push("senderId = ?");
|
|
652
|
+
params.push(String(senderId));
|
|
653
|
+
}
|
|
654
|
+
if (Number.isFinite(since)) {
|
|
655
|
+
where.push("timestamp >= ?");
|
|
656
|
+
params.push(Number(since));
|
|
657
|
+
}
|
|
658
|
+
if (Number.isFinite(until)) {
|
|
659
|
+
where.push("timestamp <= ?");
|
|
660
|
+
params.push(Number(until));
|
|
661
|
+
}
|
|
662
|
+
params.push(limit);
|
|
663
|
+
return db
|
|
664
|
+
.prepare(`SELECT * FROM messages WHERE ${where.join(" AND ")} ORDER BY timestamp DESC LIMIT ?`)
|
|
665
|
+
.all(...params);
|
|
666
|
+
}
|
|
667
|
+
|
|
624
668
|
/**
|
|
625
669
|
* Most recently active threads, newest first.
|
|
626
670
|
*
|
package/src/mcp/mcp-config.js
CHANGED
|
@@ -79,6 +79,46 @@ export function createHTTPServer(registerToolsFn, deps, port, authToken, host =
|
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
+
// Long-poll endpoint for zero-CPU event-driven wakeup (protected by auth middleware above)
|
|
83
|
+
app.get("/events/wait", async (req, res) => {
|
|
84
|
+
const rawTimeout = Number(req.query.timeout);
|
|
85
|
+
const timeoutSec = Number.isFinite(rawTimeout) && rawTimeout > 0 ? Math.min(rawTimeout, 300) : 60;
|
|
86
|
+
const threadId =
|
|
87
|
+
typeof req.query.threadId === "string" && req.query.threadId.trim() ? req.query.threadId.trim() : null;
|
|
88
|
+
const sinceRaw = req.query.since !== undefined ? Number(req.query.since) : undefined;
|
|
89
|
+
const since = Number.isFinite(sinceRaw) ? sinceRaw : undefined;
|
|
90
|
+
|
|
91
|
+
const ac = new AbortController();
|
|
92
|
+
res.on("close", () => ac.abort());
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const msg = await deps.buffer.waitForMessage({
|
|
96
|
+
threadId,
|
|
97
|
+
since,
|
|
98
|
+
timeoutMs: timeoutSec * 1000,
|
|
99
|
+
signal: ac.signal,
|
|
100
|
+
});
|
|
101
|
+
if (res.writableEnded) return;
|
|
102
|
+
if (msg) {
|
|
103
|
+
res.json({
|
|
104
|
+
status: "message",
|
|
105
|
+
message: msg,
|
|
106
|
+
cursor: msg._cursor,
|
|
107
|
+
});
|
|
108
|
+
} else {
|
|
109
|
+
res.json({
|
|
110
|
+
status: "timeout",
|
|
111
|
+
message: null,
|
|
112
|
+
cursor: deps.buffer.readCursor(),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (!res.headersSent) {
|
|
117
|
+
res.status(500).json({ error: err.message });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
82
122
|
// Health check — no auth required
|
|
83
123
|
app.get("/health", (req, res) => {
|
|
84
124
|
res.json({
|