hyperclaw 4.0.2 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +54 -3
  2. package/dist/a2ui-protocol-CfBI44-Q.js +75 -0
  3. package/dist/agents-routing-ChHiZp36.js +327 -0
  4. package/dist/agents-routing-ChqZ6l2S.js +4 -0
  5. package/dist/api-keys-guide-BCcOl0Q7.js +149 -0
  6. package/dist/audit-BaIiyWFu.js +441 -0
  7. package/dist/bounty-tools-DWudyZie.js +211 -0
  8. package/dist/browser-tools-BsTeGMnX.js +5 -0
  9. package/dist/browser-tools-D8_rLe2p.js +179 -0
  10. package/dist/claw-tasks-CgTsiNE8.js +80 -0
  11. package/dist/connector-5N0-X_xs.js +194 -0
  12. package/dist/connector-B3v0qcXg.js +425 -0
  13. package/dist/connector-B8R3iBY1.js +280 -0
  14. package/dist/connector-BAM-08NN.js +189 -0
  15. package/dist/connector-BC8FIVu4.js +181 -0
  16. package/dist/connector-BDmwwaVc.js +213 -0
  17. package/dist/connector-BGjbBy69.js +225 -0
  18. package/dist/connector-BO2SRzfG.js +218 -0
  19. package/dist/connector-BfXky0L3.js +167 -0
  20. package/dist/connector-BiiSJpx3.js +192 -0
  21. package/dist/connector-BnDmIhIu.js +85 -0
  22. package/dist/connector-C1HSoUyk.js +189 -0
  23. package/dist/connector-CKQHZOXg.js +568 -0
  24. package/dist/connector-CRl-iidy.js +239 -0
  25. package/dist/connector-Ci9glMD-.js +340 -0
  26. package/dist/connector-CjtZIEDj.js +181 -0
  27. package/dist/connector-Ck6JtOsX.js +531 -0
  28. package/dist/connector-D8Kelee0.js +286 -0
  29. package/dist/connector-DAnRJ0oP.js +162 -0
  30. package/dist/connector-DXTp5PE8.js +508 -0
  31. package/dist/connector-Dih6dUPP.js +173 -0
  32. package/dist/connector-DqTH_tPX.js +182 -0
  33. package/dist/connector-DrnEiiyP.js +419 -0
  34. package/dist/connector-DtR5GGTX.js +167 -0
  35. package/dist/connector-Tky_qS_K.js +350 -0
  36. package/dist/connector-ZSc3oTTy.js +305 -0
  37. package/dist/connector-sW5yhU1m.js +498 -0
  38. package/dist/connector-u3ICd3Ic.js +552 -0
  39. package/dist/cost-tracker-DD9wtWsr.js +103 -0
  40. package/dist/credentials-store-C6ir0Dae.js +4 -0
  41. package/dist/credentials-store-H13LqOwJ.js +77 -0
  42. package/dist/cron-tasks-Bli7Kzd2.js +82 -0
  43. package/dist/daemon-Bg4GtCmc.js +318 -0
  44. package/dist/daemon-DhmwY8k4.js +5 -0
  45. package/dist/delivery-BmIYy9VQ.js +4 -0
  46. package/dist/delivery-pWUPBp1F.js +95 -0
  47. package/dist/destructive-gate-D6vWOdEl.js +101 -0
  48. package/dist/developer-keys-CPWT7Q6S.js +8 -0
  49. package/dist/developer-keys-DrrcUqFa.js +127 -0
  50. package/dist/doctor-BvCe8BBk.js +230 -0
  51. package/dist/doctor-CxyPLYsJ.js +6 -0
  52. package/dist/engine-CEDSqXfw.js +256 -0
  53. package/dist/engine-Da4JMNpI.js +7 -0
  54. package/dist/env-resolve-CiXbWYwe.js +10 -0
  55. package/dist/env-resolve-CmGWhWXJ.js +115 -0
  56. package/dist/extraction-tools-HOZstZ0y.js +91 -0
  57. package/dist/extraction-tools-m4lmAv7l.js +5 -0
  58. package/dist/form_data-Cz040rio.js +8657 -0
  59. package/dist/gmail-watch-setup-Du7DVV7S.js +40 -0
  60. package/dist/health-B-asI__D.js +6 -0
  61. package/dist/health-Ds2YlpTB.js +152 -0
  62. package/dist/heartbeat-engine-BYT5ayQH.js +83 -0
  63. package/dist/hub-D0XwdjM-.js +515 -0
  64. package/dist/hub-LiD5Iztb.js +6 -0
  65. package/dist/hyperclawbot-zvczQgKx.js +505 -0
  66. package/dist/inference-BKVkBREb.js +6 -0
  67. package/dist/inference-DCXH4Q3x.js +922 -0
  68. package/dist/knowledge-graph-iBG76fvm.js +131 -0
  69. package/dist/loader-CC45xGpC.js +4 -0
  70. package/dist/loader-CnEdOyjT.js +400 -0
  71. package/dist/logger-ybOp7VOC.js +83 -0
  72. package/dist/manager-03ipO9R0.js +105 -0
  73. package/dist/manager-BpDfbDjg.js +117 -0
  74. package/dist/manager-Bxl0sqlh.js +4 -0
  75. package/dist/manager-CrVDn6eN.js +6 -0
  76. package/dist/manager-FCgF1plu.js +218 -0
  77. package/dist/manager-rgCsaWT1.js +40 -0
  78. package/dist/mcp-CfoSU4Uz.js +139 -0
  79. package/dist/mcp-loader-DkRBsLpk.js +94 -0
  80. package/dist/memory-BlHL7JCO.js +4 -0
  81. package/dist/memory-DsS_eFvJ.js +270 -0
  82. package/dist/memory-auto-BkvtSFUw.js +5 -0
  83. package/dist/memory-auto-Bnz_-1wP.js +306 -0
  84. package/dist/memory-integration-cSYkZyEo.js +91 -0
  85. package/dist/moltbook-BtLDZTfM.js +81 -0
  86. package/dist/node-Dw2Gi-cP.js +222 -0
  87. package/dist/nodes-registry-B8dmrlLv.js +52 -0
  88. package/dist/oauth-flow-DQPvMHRH.js +150 -0
  89. package/dist/oauth-provider-Uo4Nib_c.js +110 -0
  90. package/dist/observability-BV-Yx0V9.js +89 -0
  91. package/dist/onboard-0WoDxbv_.js +10 -0
  92. package/dist/onboard-BXNXCQp4.js +4070 -0
  93. package/dist/orchestrator-DmnEvMaL.js +189 -0
  94. package/dist/orchestrator-RI3bpqqc.js +6 -0
  95. package/dist/pairing-6iM27aD8.js +196 -0
  96. package/dist/pairing-dGoiGepK.js +4 -0
  97. package/dist/pc-access-CgCsYrpt.js +8 -0
  98. package/dist/pc-access-_iH2aorG.js +819 -0
  99. package/dist/pending-approval-CUXjysAo.js +22 -0
  100. package/dist/reminders-store-Drjed_-h.js +58 -0
  101. package/dist/renderer-BVQrd0_g.js +225 -0
  102. package/dist/rules-BE4GV6cV.js +103 -0
  103. package/dist/run-main.js +1607 -443
  104. package/dist/runner-DatMMYYE.js +1271 -0
  105. package/dist/sdk/index.js +2 -2
  106. package/dist/sdk/index.mjs +2 -2
  107. package/dist/security-BqNyT4ID.js +4 -0
  108. package/dist/security-tpgqPWWH.js +73 -0
  109. package/dist/server-D4wVHiX9.js +4 -0
  110. package/dist/server-Dh3JlBFB.js +1255 -0
  111. package/dist/session-store-BUiPz0Vv.js +5 -0
  112. package/dist/session-store-is4B6qmD.js +113 -0
  113. package/dist/sessions-tools-CbUTFe4i.js +5 -0
  114. package/dist/sessions-tools-CeqD7iil.js +95 -0
  115. package/dist/skill-loader-BaNLVmJy.js +7 -0
  116. package/dist/skill-loader-HgpF6Vqs.js +159 -0
  117. package/dist/skill-runtime-CJN24QPW.js +102 -0
  118. package/dist/skill-runtime-w1ig_lcw.js +5 -0
  119. package/dist/src-BxPHKO5x.js +63 -0
  120. package/dist/src-DIc-L2IG.js +20 -0
  121. package/dist/src-g_rNx5rh.js +458 -0
  122. package/dist/sub-agent-tools-CHQoHz9c.js +39 -0
  123. package/dist/theme-DcxwcUgZ.js +180 -0
  124. package/dist/theme-cx0fkgWC.js +8 -0
  125. package/dist/tool-policy-CNT-mF2Z.js +189 -0
  126. package/dist/tts-elevenlabs-BRosZv-f.js +61 -0
  127. package/dist/update-check-C2Dz85wJ.js +81 -0
  128. package/dist/vision-BMmiIKy7.js +121 -0
  129. package/dist/vision-tools-DVuYc17I.js +51 -0
  130. package/dist/vision-tools-U3YC4L-g.js +5 -0
  131. package/dist/voice-transcription-B555DbWR.js +138 -0
  132. package/dist/website-watch-tools-DFMrJU-R.js +139 -0
  133. package/dist/website-watch-tools-Du3W5sN7.js +5 -0
  134. package/package.json +1 -1
@@ -0,0 +1,425 @@
1
+ const require_chunk = require('./chunk-jS-bbMI5.js');
2
+ const chalk = require_chunk.__toESM(require("chalk"));
3
+ const fs_extra = require_chunk.__toESM(require("fs-extra"));
4
+ const path = require_chunk.__toESM(require("path"));
5
+ const os = require_chunk.__toESM(require("os"));
6
+ const https = require_chunk.__toESM(require("https"));
7
+ const events = require_chunk.__toESM(require("events"));
8
+
9
+ //#region extensions/zalo/src/connector.ts
10
+ const STATE_BASE = path.default.join(os.default.homedir(), ".hyperclaw");
11
+ const BOT_API_HOST = "bot.zaloplatforms.com";
12
+ const BOT_API_BASE = "/v3";
13
+ const TEXT_CHUNK = 2e3;
14
+ const DEFAULT_MEDIA_MB = 5;
15
+ const PAIRING_TTL_MS = 60 * 60 * 1e3;
16
+ const DEDUP_TTL_MS = 6e4;
17
+ function zaloApi(token, method, apiPath, body) {
18
+ return new Promise((resolve, reject) => {
19
+ const payload = body ? JSON.stringify(body) : null;
20
+ const qs = method === "GET" && !body ? "" : "";
21
+ const req = https.default.request({
22
+ hostname: BOT_API_HOST,
23
+ port: 443,
24
+ path: `${BOT_API_BASE}${apiPath}${qs}`,
25
+ method,
26
+ headers: {
27
+ Authorization: `Bot ${token}`,
28
+ Accept: "application/json",
29
+ ...payload ? {
30
+ "Content-Type": "application/json",
31
+ "Content-Length": Buffer.byteLength(payload)
32
+ } : {}
33
+ }
34
+ }, (res) => {
35
+ let data = "";
36
+ res.on("data", (c) => data += c);
37
+ res.on("end", () => {
38
+ try {
39
+ const r = JSON.parse(data);
40
+ if (r.error && r.error !== 0) reject(new Error(r.message || `Zalo error ${r.error}`));
41
+ else resolve(r);
42
+ } catch {
43
+ reject(new Error("Zalo: invalid JSON"));
44
+ }
45
+ });
46
+ });
47
+ req.on("error", reject);
48
+ req.setTimeout(35e3, () => {
49
+ req.destroy();
50
+ reject(new Error("Zalo API timeout"));
51
+ });
52
+ if (payload) req.write(payload);
53
+ req.end();
54
+ });
55
+ }
56
+ function zaloApiQuery(token, apiPath, params) {
57
+ return new Promise((resolve, reject) => {
58
+ const qs = new URLSearchParams(Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)]))).toString();
59
+ const req = https.default.request({
60
+ hostname: BOT_API_HOST,
61
+ port: 443,
62
+ path: `${BOT_API_BASE}${apiPath}?${qs}`,
63
+ method: "GET",
64
+ headers: {
65
+ Authorization: `Bot ${token}`,
66
+ Accept: "application/json"
67
+ }
68
+ }, (res) => {
69
+ let data = "";
70
+ res.on("data", (c) => data += c);
71
+ res.on("end", () => {
72
+ try {
73
+ const r = JSON.parse(data);
74
+ if (r.error && r.error !== 0) reject(new Error(r.message || `Zalo error ${r.error}`));
75
+ else resolve(r);
76
+ } catch {
77
+ reject(new Error("Zalo: invalid JSON"));
78
+ }
79
+ });
80
+ });
81
+ req.on("error", reject);
82
+ req.setTimeout(4e4, () => {
83
+ req.destroy();
84
+ reject(new Error("Zalo poll timeout"));
85
+ });
86
+ req.end();
87
+ });
88
+ }
89
+ var ZaloAccount = class extends events.EventEmitter {
90
+ cfg;
91
+ accountId;
92
+ token = "";
93
+ running = false;
94
+ offset = 0;
95
+ mediaMaxMb;
96
+ /** Dedup set: "eventName:messageId" → expiry timestamp */
97
+ dedupMap = /* @__PURE__ */ new Map();
98
+ constructor(accountId, cfg) {
99
+ super();
100
+ this.accountId = accountId;
101
+ this.cfg = {
102
+ dmPolicy: "pairing",
103
+ allowFrom: [],
104
+ groupPolicy: "allowlist",
105
+ groupAllowFrom: [],
106
+ mediaMaxMb: DEFAULT_MEDIA_MB,
107
+ pendingPairingTs: {},
108
+ ...cfg
109
+ };
110
+ this.cfg.approvedPairings = this.cfg.approvedPairings ?? [];
111
+ this.cfg.pendingPairings = this.cfg.pendingPairings ?? {};
112
+ this.mediaMaxMb = this.cfg.mediaMaxMb ?? DEFAULT_MEDIA_MB;
113
+ }
114
+ async resolveToken() {
115
+ this.token = this.cfg.botToken || (this.cfg.tokenFile ? (await fs_extra.default.readFile(this.cfg.tokenFile, "utf8")).trim() : "");
116
+ if (!this.token) throw new Error(`Zalo [${this.accountId}]: botToken or tokenFile is required`);
117
+ }
118
+ async connect() {
119
+ await this.resolveToken();
120
+ await this.loadState();
121
+ this.running = true;
122
+ const useWebhook = !!this.cfg.webhookUrl;
123
+ if (useWebhook) {
124
+ await this.registerWebhook();
125
+ console.log(chalk.default.green(` 🔵 Zalo [${this.accountId}]: webhook mode — ${this.cfg.webhookUrl}`));
126
+ } else {
127
+ console.log(chalk.default.green(` 🔵 Zalo [${this.accountId}]: long-polling started`));
128
+ this.pollLoop();
129
+ }
130
+ this.emit("connected", {
131
+ accountId: this.accountId,
132
+ mode: useWebhook ? "webhook" : "polling"
133
+ });
134
+ }
135
+ disconnect() {
136
+ this.running = false;
137
+ }
138
+ async registerWebhook() {
139
+ if (!this.cfg.webhookUrl || !this.cfg.webhookSecret) return;
140
+ try {
141
+ await zaloApi(this.token, "POST", "/webhook", {
142
+ url: this.cfg.webhookUrl,
143
+ secret_token: this.cfg.webhookSecret
144
+ });
145
+ } catch (e) {
146
+ console.log(chalk.default.yellow(` ⚠ Zalo [${this.accountId}]: webhook registration: ${e.message}`));
147
+ }
148
+ }
149
+ async pollLoop() {
150
+ while (this.running) try {
151
+ const resp = await zaloApiQuery(this.token, "/event", {
152
+ timeout: 30,
153
+ offset: this.offset
154
+ });
155
+ const events$1 = Array.isArray(resp?.updates) ? resp.updates : Array.isArray(resp?.result) ? resp.result : Array.isArray(resp) ? resp : [];
156
+ for (const event of events$1) {
157
+ const id = event.update_id ?? event.id;
158
+ if (id != null && id >= this.offset) this.offset = id + 1;
159
+ await this.handleEvent(event);
160
+ }
161
+ } catch (e) {
162
+ if (this.running) {
163
+ console.log(chalk.default.yellow(` ⚠ Zalo [${this.accountId}] poll: ${e.message}`));
164
+ await new Promise((r) => setTimeout(r, 5e3));
165
+ }
166
+ }
167
+ }
168
+ async handleWebhook(rawBody, secretHeader) {
169
+ if (!this.verifyWebhookSecret(secretHeader)) {
170
+ console.log(chalk.default.yellow(` ⚠ Zalo [${this.accountId}]: invalid webhook secret`));
171
+ return;
172
+ }
173
+ let event;
174
+ try {
175
+ event = JSON.parse(rawBody);
176
+ } catch {
177
+ return;
178
+ }
179
+ await this.handleEvent(event);
180
+ }
181
+ verifyWebhookSecret(headerValue) {
182
+ if (!this.cfg.webhookSecret) return true;
183
+ return headerValue === this.cfg.webhookSecret;
184
+ }
185
+ async handleEvent(event) {
186
+ if (!event) return;
187
+ const eventName = event.event_name || event.type || "";
188
+ const messageId = String(event.message?.msg_id || event.message_id || event.id || "");
189
+ if (messageId) {
190
+ const key = `${eventName}:${messageId}`;
191
+ const now = Date.now();
192
+ if (this.dedupMap.has(key)) return;
193
+ this.dedupMap.set(key, now + DEDUP_TTL_MS);
194
+ if (this.dedupMap.size > 1e3) {
195
+ for (const [k, exp] of this.dedupMap) if (exp < now) this.dedupMap.delete(k);
196
+ }
197
+ }
198
+ const sender = event.sender?.id || event.from?.id || event.user_id || "";
199
+ const groupId = event.message?.group_id || event.group_id || "";
200
+ const isGroup = !!groupId;
201
+ let text = "";
202
+ switch (eventName) {
203
+ case "user_send_text":
204
+ case "message":
205
+ text = event.message?.text || event.text || "";
206
+ break;
207
+ case "user_send_image":
208
+ case "message_image": {
209
+ const url = event.message?.attachments?.[0]?.payload?.url || event.image_url || "";
210
+ const size = event.message?.attachments?.[0]?.payload?.filesize || 0;
211
+ const maxBytes = this.mediaMaxMb * 1024 * 1024;
212
+ if (size > maxBytes) return;
213
+ text = url ? `[image:${url}]` : "[image]";
214
+ break;
215
+ }
216
+ case "user_send_sticker":
217
+ case "message_sticker": return;
218
+ default: return;
219
+ }
220
+ if (!text || !sender) return;
221
+ if (isGroup) {
222
+ if (!this.checkGroupPolicy(groupId, sender)) return;
223
+ this.emit("message", {
224
+ channelId: "zalo",
225
+ accountId: this.accountId,
226
+ chatId: groupId,
227
+ from: sender,
228
+ text,
229
+ isDM: false
230
+ });
231
+ } else {
232
+ const allowed = await this.checkDMPolicy(sender, text);
233
+ if (!allowed) return;
234
+ this.emit("message", {
235
+ channelId: "zalo",
236
+ accountId: this.accountId,
237
+ chatId: sender,
238
+ from: sender,
239
+ text,
240
+ isDM: true
241
+ });
242
+ }
243
+ }
244
+ async checkDMPolicy(from, text) {
245
+ const policy = this.cfg.dmPolicy ?? "pairing";
246
+ const allowFrom = this.cfg.allowFrom ?? [];
247
+ this.pruneExpiredCodes();
248
+ switch (policy) {
249
+ case "disabled": return false;
250
+ case "open": return true;
251
+ case "allowlist":
252
+ if (allowFrom.includes(from) || allowFrom.includes("*")) return true;
253
+ await this.sendText(from, "HyperClaw: Not on allowlist.");
254
+ return false;
255
+ case "pairing": {
256
+ if (this.cfg.approvedPairings.includes(from)) return true;
257
+ const upper = text.trim().toUpperCase().match(/[A-Z0-9]{6}/)?.[0];
258
+ if (upper && this.cfg.pendingPairings[upper]) {
259
+ this.cfg.approvedPairings.push(from);
260
+ delete this.cfg.pendingPairings[upper];
261
+ delete this.cfg.pendingPairingTs[upper];
262
+ await this.saveState();
263
+ await this.sendText(from, "Paired!");
264
+ this.emit("pairing:approved", {
265
+ userId: from,
266
+ channelId: "zalo",
267
+ accountId: this.accountId
268
+ });
269
+ return true;
270
+ }
271
+ const code = Array.from({ length: 6 }, () => "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"[Math.floor(Math.random() * 32)]).join("");
272
+ this.cfg.pendingPairings[code] = from;
273
+ this.cfg.pendingPairingTs[code] = Date.now();
274
+ await this.saveState();
275
+ await this.sendText(from, `Pairing code: ${code}\nApprove: hyperclaw pairing approve zalo ${code}\n(expires in 1 hour)`);
276
+ return false;
277
+ }
278
+ }
279
+ return false;
280
+ }
281
+ pruneExpiredCodes() {
282
+ const now = Date.now();
283
+ const ts = this.cfg.pendingPairingTs;
284
+ for (const code of Object.keys(ts)) if (now - ts[code] > PAIRING_TTL_MS) {
285
+ delete this.cfg.pendingPairings[code];
286
+ delete ts[code];
287
+ }
288
+ }
289
+ checkGroupPolicy(groupId, userId) {
290
+ const policy = this.cfg.groupPolicy ?? "allowlist";
291
+ if (policy === "disabled") return false;
292
+ if (policy === "open") {
293
+ const list$1 = this.cfg.groupAllowFrom ?? this.cfg.allowFrom ?? [];
294
+ return list$1.length === 0 || list$1.includes(userId);
295
+ }
296
+ const list = this.cfg.groupAllowFrom?.length ? this.cfg.groupAllowFrom : this.cfg.allowFrom ?? [];
297
+ return list.includes(groupId) || list.includes(userId);
298
+ }
299
+ async sendMessage(chatId, text) {
300
+ const chunks = [];
301
+ for (let i = 0; i < text.length; i += TEXT_CHUNK) chunks.push(text.slice(i, i + TEXT_CHUNK));
302
+ for (const chunk of chunks) await this.sendText(chatId, chunk);
303
+ }
304
+ async sendText(to, text) {
305
+ await zaloApi(this.token, "POST", "/message/sendtext", {
306
+ to,
307
+ text
308
+ }).catch((e) => console.error(`[zalo:${this.accountId}] sendText: ${e.message}`));
309
+ }
310
+ async sendPhoto(to, imageUrl, caption) {
311
+ await zaloApi(this.token, "POST", "/message/sendimage", {
312
+ to,
313
+ image_url: imageUrl,
314
+ ...caption ? { caption } : {}
315
+ }).catch((e) => console.error(`[zalo:${this.accountId}] sendPhoto: ${e.message}`));
316
+ }
317
+ approvePairing(code) {
318
+ const upper = code.toUpperCase();
319
+ if (!this.cfg.pendingPairings[upper]) return false;
320
+ this.cfg.approvedPairings.push(this.cfg.pendingPairings[upper]);
321
+ delete this.cfg.pendingPairings[upper];
322
+ delete this.cfg.pendingPairingTs[upper];
323
+ this.saveState();
324
+ return true;
325
+ }
326
+ stateFile() {
327
+ return path.default.join(STATE_BASE, `zalo-bot-state-${this.accountId}.json`);
328
+ }
329
+ async loadState() {
330
+ try {
331
+ const s = await fs_extra.default.readJson(this.stateFile());
332
+ this.offset = s.offset || 0;
333
+ if (s.p) this.cfg.pendingPairings = s.p;
334
+ if (s.a) this.cfg.approvedPairings = s.a;
335
+ if (s.pts) this.cfg.pendingPairingTs = s.pts;
336
+ } catch {}
337
+ }
338
+ async saveState() {
339
+ await fs_extra.default.ensureDir(STATE_BASE);
340
+ await fs_extra.default.writeJson(this.stateFile(), {
341
+ offset: this.offset,
342
+ p: this.cfg.pendingPairings,
343
+ a: this.cfg.approvedPairings,
344
+ pts: this.cfg.pendingPairingTs
345
+ }, { spaces: 2 });
346
+ }
347
+ isRunning() {
348
+ return this.running;
349
+ }
350
+ };
351
+ var ZaloConnector = class extends events.EventEmitter {
352
+ config;
353
+ accounts = [];
354
+ constructor(config) {
355
+ super();
356
+ this.config = config;
357
+ }
358
+ async connect() {
359
+ const sharedState = {
360
+ approvedPairings: this.config.approvedPairings ?? [],
361
+ pendingPairings: this.config.pendingPairings ?? {},
362
+ pendingPairingTs: this.config.pendingPairingTs ?? {}
363
+ };
364
+ const accountEntries = Object.entries(this.config.accounts || {});
365
+ if (accountEntries.length === 0) {
366
+ const acct = new ZaloAccount("default", {
367
+ ...this.config,
368
+ ...sharedState
369
+ });
370
+ this.wire(acct);
371
+ await acct.connect();
372
+ this.accounts.push(acct);
373
+ } else for (const [id, acctCfg] of accountEntries) {
374
+ if (acctCfg.enabled === false) continue;
375
+ const merged = {
376
+ ...this.config,
377
+ ...acctCfg,
378
+ mediaMaxMb: this.config.mediaMaxMb,
379
+ ...sharedState
380
+ };
381
+ if (!merged.botToken && !merged.tokenFile) {
382
+ console.error(`[zalo] Account "${id}" has no botToken or tokenFile — skipping`);
383
+ continue;
384
+ }
385
+ const acct = new ZaloAccount(id, merged);
386
+ this.wire(acct);
387
+ try {
388
+ await acct.connect();
389
+ this.accounts.push(acct);
390
+ } catch (e) {
391
+ console.error(`[zalo] Account "${id}" failed: ${e.message}`);
392
+ }
393
+ }
394
+ }
395
+ wire(acct) {
396
+ acct.on("message", (msg) => this.emit("message", msg));
397
+ acct.on("connected", (info) => this.emit("connected", info));
398
+ acct.on("pairing:approved", (info) => this.emit("pairing:approved", info));
399
+ }
400
+ async sendMessage(chatId, text) {
401
+ const acct = this.accounts[0];
402
+ if (!acct) throw new Error("Zalo: no connected account");
403
+ await acct.sendMessage(chatId, text);
404
+ }
405
+ /** Called by gateway webhook handler for webhook mode accounts. */
406
+ async handleWebhook(rawBody, secretHeader) {
407
+ for (const acct of this.accounts) if (acct.verifyWebhookSecret(secretHeader)) {
408
+ await acct.handleWebhook(rawBody, secretHeader);
409
+ return;
410
+ }
411
+ }
412
+ approvePairing(code) {
413
+ return this.accounts.some((a) => a.approvePairing(code));
414
+ }
415
+ disconnect() {
416
+ for (const a of this.accounts) a.disconnect();
417
+ this.accounts = [];
418
+ }
419
+ isRunning() {
420
+ return this.accounts.some((a) => a.isRunning());
421
+ }
422
+ };
423
+
424
+ //#endregion
425
+ exports.ZaloConnector = ZaloConnector;
@@ -0,0 +1,280 @@
1
+ const require_chunk = require('./chunk-jS-bbMI5.js');
2
+ const chalk = require_chunk.__toESM(require("chalk"));
3
+ const fs_extra = require_chunk.__toESM(require("fs-extra"));
4
+ const path = require_chunk.__toESM(require("path"));
5
+ const os = require_chunk.__toESM(require("os"));
6
+ const https = require_chunk.__toESM(require("https"));
7
+ const events = require_chunk.__toESM(require("events"));
8
+
9
+ //#region extensions/telegram/src/connector.ts
10
+ function tgDownloadFile(token, filePath) {
11
+ return new Promise((resolve, reject) => {
12
+ const req = https.default.request({
13
+ hostname: "api.telegram.org",
14
+ port: 443,
15
+ path: `/file/bot${token}/${filePath}`,
16
+ method: "GET"
17
+ }, (res) => {
18
+ const chunks = [];
19
+ res.on("data", (c) => chunks.push(c));
20
+ res.on("end", () => resolve(Buffer.concat(chunks)));
21
+ });
22
+ req.on("error", reject);
23
+ req.setTimeout(3e4, () => {
24
+ req.destroy();
25
+ reject(new Error("Download timeout"));
26
+ });
27
+ req.end();
28
+ });
29
+ }
30
+ function tgRequest(token, method, body) {
31
+ return new Promise((resolve, reject) => {
32
+ const payload = body ? JSON.stringify(body) : null;
33
+ const req = https.default.request({
34
+ hostname: "api.telegram.org",
35
+ port: 443,
36
+ path: `/bot${token}/${method}`,
37
+ method: payload ? "POST" : "GET",
38
+ headers: payload ? {
39
+ "Content-Type": "application/json",
40
+ "Content-Length": Buffer.byteLength(payload)
41
+ } : {}
42
+ }, (res) => {
43
+ let data = "";
44
+ res.on("data", (c) => data += c);
45
+ res.on("end", () => {
46
+ try {
47
+ const r = JSON.parse(data);
48
+ if (r.ok) resolve(r.result);
49
+ else reject(new Error(r.description || "Telegram API error"));
50
+ } catch {
51
+ reject(new Error("Invalid JSON"));
52
+ }
53
+ });
54
+ });
55
+ req.on("error", reject);
56
+ req.setTimeout(35e3, () => {
57
+ req.destroy();
58
+ reject(new Error("Timeout"));
59
+ });
60
+ if (payload) req.write(payload);
61
+ req.end();
62
+ });
63
+ }
64
+ const STATE_FILE = path.default.join(os.default.homedir(), ".hyperclaw", "telegram-state.json");
65
+ function sleep(ms) {
66
+ return new Promise((r) => setTimeout(r, ms));
67
+ }
68
+ function generateCode() {
69
+ const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
70
+ return Array.from({ length: 6 }, () => chars[Math.floor(Math.random() * chars.length)]).join("");
71
+ }
72
+ function chunkText(text, max) {
73
+ if (text.length <= max) return [text];
74
+ const chunks = [];
75
+ let i = 0;
76
+ while (i < text.length) {
77
+ let end = Math.min(i + max, text.length);
78
+ if (end < text.length) {
79
+ const nl = text.lastIndexOf("\n", end);
80
+ if (nl > i) end = nl + 1;
81
+ }
82
+ chunks.push(text.slice(i, end));
83
+ i = end;
84
+ }
85
+ return chunks;
86
+ }
87
+ var TelegramConnector = class extends events.EventEmitter {
88
+ token;
89
+ config;
90
+ offset = 0;
91
+ running = false;
92
+ botInfo = null;
93
+ constructor(token, config) {
94
+ super();
95
+ this.token = token;
96
+ this.config = {
97
+ token,
98
+ dmPolicy: "allowlist",
99
+ allowFrom: [],
100
+ pendingPairings: {},
101
+ approvedPairings: [],
102
+ ...config
103
+ };
104
+ }
105
+ async connect() {
106
+ this.botInfo = await tgRequest(this.token, "getMe");
107
+ console.log(chalk.default.green(` 🦅 Telegram: @${this.botInfo?.username} connected`));
108
+ await this.loadState();
109
+ this.running = true;
110
+ this.pollLoop();
111
+ this.emit("connected", this.botInfo);
112
+ }
113
+ async disconnect() {
114
+ this.running = false;
115
+ await this.saveState();
116
+ }
117
+ async pollLoop() {
118
+ while (this.running) try {
119
+ const updates = await tgRequest(this.token, "getUpdates", {
120
+ offset: this.offset,
121
+ timeout: 30,
122
+ allowed_updates: ["message"]
123
+ });
124
+ for (const u of updates) {
125
+ this.offset = u.update_id + 1;
126
+ await this.handleUpdate(u).catch((e) => console.log(chalk.default.yellow(` ⚠ ${e.message}`)));
127
+ }
128
+ } catch (e) {
129
+ if (this.running) {
130
+ console.log(chalk.default.yellow(` ⚠ Telegram poll: ${e.message}`));
131
+ await sleep(5e3);
132
+ }
133
+ }
134
+ }
135
+ async handleUpdate(u) {
136
+ const msg = u.message || u.edited_message;
137
+ if (!msg) return;
138
+ const text = msg.text?.trim() || "";
139
+ if (!text && !msg.voice) return;
140
+ const userId = String(msg.from?.id || "");
141
+ const isDM = msg.chat.type === "private";
142
+ const isGroup = msg.chat.type === "group" || msg.chat.type === "supergroup";
143
+ if (isDM && !await this.checkDMPolicy(userId, msg.chat.id, text || "[voice note]")) return;
144
+ let finalText = text || "[voice note]";
145
+ if (isGroup) {
146
+ const groupAllowFrom = this.config.groupAllowFrom ?? [];
147
+ if (groupAllowFrom.length > 0 && !groupAllowFrom.includes(String(msg.chat.id))) return;
148
+ const activation = this.config.groupActivation ?? "mention";
149
+ if (activation === "mention") {
150
+ const botUsername = this.botInfo?.username ? `@${this.botInfo.username}`.toLowerCase() : "";
151
+ const mentioned = botUsername && text.toLowerCase().includes(botUsername);
152
+ const isReplyToBot = msg.reply_to_message?.from?.is_bot && msg.reply_to_message.from.id === this.botInfo?.id;
153
+ if (!mentioned && !isReplyToBot) return;
154
+ if (mentioned && botUsername) finalText = text.replace(new RegExp(botUsername, "gi"), "").trim() || finalText;
155
+ }
156
+ }
157
+ let audioPath;
158
+ if (msg?.voice?.file_id) try {
159
+ const file = await tgRequest(this.token, "getFile", { file_id: msg.voice.file_id });
160
+ const buf = await tgDownloadFile(this.token, file.file_path);
161
+ const ext = (file.file_path || "").split(".").pop() || "oga";
162
+ const tmp = path.default.join(os.default.tmpdir(), `hyperclaw-tg-voice-${msg.message_id}.${ext}`);
163
+ await fs_extra.default.writeFile(tmp, buf);
164
+ audioPath = tmp;
165
+ } catch (e) {
166
+ console.log(chalk.default.yellow(` ⚠ Telegram voice download failed: ${e.message}`));
167
+ }
168
+ this.emit("message", {
169
+ id: String(msg.message_id),
170
+ channelId: "telegram",
171
+ from: userId,
172
+ fromUsername: msg.from?.username,
173
+ chatId: msg.chat.id,
174
+ text: finalText,
175
+ audioPath,
176
+ timestamp: new Date(msg.date * 1e3).toISOString(),
177
+ isDM
178
+ });
179
+ }
180
+ async checkDMPolicy(userId, chatId, text) {
181
+ if (this.config.dmPolicy === "none") return false;
182
+ if (this.config.dmPolicy === "open") return true;
183
+ if (this.config.dmPolicy === "allowlist") {
184
+ if (this.config.allowFrom.includes(userId)) return true;
185
+ await this.sendMessage(chatId, `🦅 *HyperClaw*\n\nYou are not on the allowlist.`);
186
+ return false;
187
+ }
188
+ if (this.config.dmPolicy === "pairing") {
189
+ if (this.config.approvedPairings.includes(userId)) return true;
190
+ const upper = text.trim().toUpperCase();
191
+ if (this.config.pendingPairings[upper]) {
192
+ this.config.approvedPairings.push(userId);
193
+ delete this.config.pendingPairings[upper];
194
+ await this.saveState();
195
+ await this.sendMessage(chatId, `🦅 *Paired!* You can now send messages.`);
196
+ this.emit("pairing:approved", {
197
+ userId,
198
+ channelId: "telegram"
199
+ });
200
+ return true;
201
+ }
202
+ const code = generateCode();
203
+ this.config.pendingPairings[code] = userId;
204
+ await this.saveState();
205
+ await this.sendMessage(chatId, `🦅 *HyperClaw Pairing*\n\nSend the owner this code:\n\`${code}\`\n\nApprove with:\n\`hyperclaw pairing approve telegram ${code}\``);
206
+ console.log(chalk.default.cyan(` 🦅 Telegram pairing from ${userId} — code: ${code}`));
207
+ return false;
208
+ }
209
+ return false;
210
+ }
211
+ async sendMessage(chatId, text, opts = {}) {
212
+ const chunks = chunkText(text, 4096);
213
+ let last = null;
214
+ for (const chunk of chunks) last = await tgRequest(this.token, "sendMessage", {
215
+ chat_id: chatId,
216
+ text: chunk,
217
+ parse_mode: opts.parse_mode || "Markdown",
218
+ disable_web_page_preview: opts.disable_web_page_preview ?? true,
219
+ ...opts.reply_to_message_id ? { reply_to_message_id: opts.reply_to_message_id } : {}
220
+ });
221
+ return last;
222
+ }
223
+ async sendTyping(chatId) {
224
+ await tgRequest(this.token, "sendChatAction", {
225
+ chat_id: chatId,
226
+ action: "typing"
227
+ }).catch(() => {});
228
+ }
229
+ approvePairing(code) {
230
+ const upper = code.toUpperCase();
231
+ if (!this.config.pendingPairings[upper]) return false;
232
+ const userId = this.config.pendingPairings[upper];
233
+ this.config.approvedPairings.push(userId);
234
+ delete this.config.pendingPairings[upper];
235
+ this.saveState();
236
+ return true;
237
+ }
238
+ addToAllowlist(userId) {
239
+ if (!this.config.allowFrom.includes(userId)) {
240
+ this.config.allowFrom.push(userId);
241
+ this.saveState();
242
+ }
243
+ }
244
+ listPendingPairings() {
245
+ return Object.entries(this.config.pendingPairings).map(([code, userId]) => ({
246
+ code,
247
+ userId
248
+ }));
249
+ }
250
+ handleWebhookPayload(body) {
251
+ try {
252
+ this.handleUpdate(JSON.parse(body));
253
+ } catch {}
254
+ }
255
+ async loadState() {
256
+ try {
257
+ const s = await fs_extra.default.readJson(STATE_FILE);
258
+ this.offset = s.offset || 0;
259
+ if (s.pendingPairings) this.config.pendingPairings = s.pendingPairings;
260
+ if (s.approvedPairings) this.config.approvedPairings = s.approvedPairings;
261
+ } catch {}
262
+ }
263
+ async saveState() {
264
+ await fs_extra.default.ensureDir(path.default.dirname(STATE_FILE));
265
+ await fs_extra.default.writeJson(STATE_FILE, {
266
+ offset: this.offset,
267
+ pendingPairings: this.config.pendingPairings,
268
+ approvedPairings: this.config.approvedPairings
269
+ }, { spaces: 2 });
270
+ }
271
+ isRunning() {
272
+ return this.running;
273
+ }
274
+ getBotInfo() {
275
+ return this.botInfo;
276
+ }
277
+ };
278
+
279
+ //#endregion
280
+ exports.TelegramConnector = TelegramConnector;