@jackle.dev/zalox-plugin 1.0.10 → 1.0.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackle.dev/zalox-plugin",
3
- "version": "1.0.10",
3
+ "version": "1.0.13",
4
4
  "description": "OpenClaw channel plugin for Zalo via zca-js (in-process, single login)",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
package/src/listener.ts CHANGED
@@ -373,10 +373,10 @@ export async function startInProcessListener(
373
373
  ? mentions.some((m: any) => String(m.uid || m.id || m) === ownId)
374
374
  : false;
375
375
  const name = account.name || 'Bot';
376
- const textMention = content.includes(`@${name}`);
376
+ const textMention = content.includes(`@${name}`) || content.includes('@Tiệp Lê');
377
377
 
378
378
  // DEBUG Group logic
379
- // console.log(`[ZaloX] Group msg: mentioned=${isMentioned} textMention=${textMention} (name=${name})`);
379
+ console.log(`[ZaloX] Group msg: mentioned=${isMentioned} textMention=${textMention} name=${name} content="${content}"`);
380
380
 
381
381
  if (!isMentioned && !textMention && !content.startsWith('/')) {
382
382
  return;
@@ -411,20 +411,8 @@ export async function startInProcessListener(
411
411
 
412
412
  if (!normalized.content?.trim() && !normalized.mediaUrl) return;
413
413
 
414
- // React with ❤️
415
- try {
416
- const reactThreadType = isGroup ? ThreadType.Group : ThreadType.User;
417
- api.addReaction(Reactions.HEART, {
418
- threadId: normalized.threadId,
419
- type: reactThreadType,
420
- data: {
421
- msgId: String(data.msgId || ''),
422
- cliMsgId: String(data.cliMsgId || ''),
423
- },
424
- }).catch((err: any) => {
425
- runtime.error?.(`[${account.accountId}] ZaloX reaction failed: ${String(err)}`);
426
- });
427
- } catch {}
414
+ // React logic removed.
415
+ // Agent should decide when to react.
428
416
 
429
417
  processMessage(normalized, account, config, runtime, profile, statusSink).catch((err) => {
430
418
  runtime.error?.(`[${account.accountId}] ZaloX process error: ${String(err)}`);
package/src/send.ts CHANGED
@@ -16,6 +16,12 @@ export type SendResult = {
16
16
  error?: string;
17
17
  };
18
18
 
19
+ function cleanText(input: string): string {
20
+ if (!input) return '';
21
+ // Remove <ctrl...> artifacts
22
+ return input.replace(/<ctrl\d+>/gi, '').trim();
23
+ }
24
+
19
25
  export async function sendText(
20
26
  threadId: string,
21
27
  text: string,
@@ -24,8 +30,10 @@ export async function sendText(
24
30
  if (!threadId?.trim()) {
25
31
  return { ok: false, error: 'No threadId provided' };
26
32
  }
27
- if (!text?.trim()) {
28
- return { ok: false, error: 'No text provided' };
33
+
34
+ const cleaned = cleanText(text);
35
+ if (!cleaned) {
36
+ return { ok: false, error: 'No text provided (or empty after cleanup)' };
29
37
  }
30
38
 
31
39
  const cached = getCachedApi(options.profile);
@@ -35,7 +43,13 @@ export async function sendText(
35
43
 
36
44
  try {
37
45
  const type = options.isGroup ? ThreadType.Group : ThreadType.User;
38
- const result = await cached.api.sendMessage(text.slice(0, 2000), threadId.trim(), type);
46
+
47
+ // Group support handled by zca-js automatically via threadId format usually,
48
+ // but ThreadType is explicit here.
49
+ console.log(`[ZaloX] Sending text to ${threadId} (type=${type}): "${cleaned.slice(0, 50)}..."`);
50
+ const result = await cached.api.sendMessage(cleaned.slice(0, 2000), threadId.trim(), type);
51
+ console.log(`[ZaloX] Send result:`, JSON.stringify(result));
52
+
39
53
  const msgId = result?.message?.msgId ? String(result.message.msgId) : undefined;
40
54
  return { ok: true, messageId: msgId };
41
55
  } catch (err: any) {
@@ -57,6 +71,8 @@ export async function sendMedia(
57
71
  if (!cached) {
58
72
  return { ok: false, error: `No active API session for profile "${options.profile}". Listener may not be running.` };
59
73
  }
74
+
75
+ const cleaned = cleanText(text);
60
76
 
61
77
  try {
62
78
  const type = options.isGroup ? ThreadType.Group : ThreadType.User;
@@ -73,7 +89,7 @@ export async function sendMedia(
73
89
  if (filePath) {
74
90
  // Send with local file attachment
75
91
  const result = await cached.api.sendMessage(
76
- { msg: text || '', attachments: [filePath] },
92
+ { msg: cleaned || '', attachments: [filePath] },
77
93
  threadId.trim(),
78
94
  type,
79
95
  );
@@ -97,7 +113,7 @@ export async function sendMedia(
97
113
 
98
114
  try {
99
115
  const result = await cached.api.sendMessage(
100
- { msg: text || '', attachments: [tmpPath] },
116
+ { msg: cleaned || '', attachments: [tmpPath] },
101
117
  threadId.trim(),
102
118
  type,
103
119
  );
@@ -107,12 +123,12 @@ export async function sendMedia(
107
123
  } catch (sendErr) {
108
124
  console.error(`[ZaloX] Send attachment failed: ${sendErr}`);
109
125
  // Fallback: send text with URL as link
110
- const fullText = text ? `${text}\n${mediaUrl}` : mediaUrl;
126
+ const fullText = cleaned ? `${cleaned}\n${mediaUrl}` : mediaUrl;
111
127
  return sendText(threadId, fullText, options);
112
128
  }
113
129
  } catch (dlErr: any) {
114
130
  // Fallback: send text with URL as link
115
- const fullText = text ? `${text}\n${mediaUrl}` : mediaUrl;
131
+ const fullText = cleaned ? `${cleaned}\n${mediaUrl}` : mediaUrl;
116
132
  return sendText(threadId, fullText, options);
117
133
  } finally {
118
134
  if (tmpPath) try { unlinkSync(tmpPath); } catch {}
@@ -120,7 +136,7 @@ export async function sendMedia(
120
136
  }
121
137
 
122
138
  // Fallback: send text only
123
- const fullText = text ? `${text}\n${mediaUrl}` : mediaUrl;
139
+ const fullText = cleaned ? `${cleaned}\n${mediaUrl}` : mediaUrl;
124
140
  return sendText(threadId, fullText, options);
125
141
  } catch (err: any) {
126
142
  return { ok: false, error: err.message || String(err) };
@@ -46,10 +46,11 @@ export const zaloxScenarioTool = {
46
46
  // Analyze Requirements (Playbook Logic)
47
47
  const reqs = data.requirements || {};
48
48
  const flow = data.flow || [];
49
+ const setup = data.setup || {};
49
50
  const warnings = [];
50
51
 
51
52
  if (reqs.packages && reqs.packages.length > 0) {
52
- warnings.push(`📦 Packages required: ${reqs.packages.join(', ')} (Please install via npm)`);
53
+ warnings.push(`📦 Packages required: ${reqs.packages.join(', ')}`);
53
54
  }
54
55
  if (reqs.env && reqs.env.length > 0) {
55
56
  warnings.push(`🔑 Environment variables needed: ${reqs.env.join(', ')}`);
@@ -60,8 +61,14 @@ export const zaloxScenarioTool = {
60
61
 
61
62
  if (warnings.length > 0 || flow.length > 0) {
62
63
  report += `\n\n🚨 PLAYBOOK REQUIREMENTS:\n${warnings.join('\n')}`;
64
+
65
+ if (setup.commands && setup.commands.length > 0) {
66
+ report += `\n\n🛠️ SETUP GUIDE (Ask user to run/confirm):\n`;
67
+ if (setup.guide) report += `ℹ️ ${setup.guide}\n`;
68
+ report += `Commands:\n${setup.commands.map((c: string) => `> ${c}`).join('\n')}`;
69
+ }
70
+
63
71
  report += `\n\n📋 EXECUTION FLOW (SOP):\n${flow.join('\n')}`;
64
- report += `\n\n👉 Agent: Please read the flow above and ask user to fulfill requirements if missing.`;
65
72
  }
66
73
 
67
74
  return report;