openclaw-extension-typex 1.0.19 → 1.0.21
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 +8 -0
- package/dist/agent-tools-send.d.ts +50 -0
- package/dist/agent-tools-send.js +353 -0
- package/dist/client/accounts.d.ts +1 -1
- package/dist/client/accounts.js +10 -10
- package/dist/client/client.d.ts +48 -8
- package/dist/client/client.js +303 -115
- package/dist/client/config.d.ts +3 -2
- package/dist/client/domain.d.ts +7 -0
- package/dist/client/domain.js +9 -0
- package/dist/client/message.d.ts +2 -1
- package/dist/client/message.js +203 -61
- package/dist/client/monitor.d.ts +2 -1
- package/dist/client/monitor.js +36 -7
- package/dist/client/outbound.d.ts +1 -1
- package/dist/client/outbound.js +49 -4
- package/dist/client/runtime.d.ts +1 -1
- package/dist/client/send.d.ts +8 -0
- package/dist/client/send.js +11 -1
- package/dist/client/types.d.ts +17 -1
- package/dist/config-schema.d.ts +4 -2
- package/dist/config-schema.js +4 -4
- package/dist/directory.d.ts +24 -0
- package/dist/directory.js +94 -0
- package/dist/index.d.ts +178 -130
- package/dist/index.js +10 -9
- package/dist/normalize.js +7 -2
- package/dist/onboarding.d.ts +2 -2
- package/dist/onboarding.js +2 -2
- package/dist/plugin.d.ts +65 -22
- package/dist/plugin.js +81 -18
- package/dist/setup-entry.d.ts +181 -0
- package/dist/setup-entry.js +5 -0
- package/dist/types.d.ts +1 -1
- package/openclaw.plugin.json +4 -3
- package/package.json +26 -6
package/dist/client/message.js
CHANGED
|
@@ -11,13 +11,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.stripBotMention = exports.normalizeMessageToText = exports.checkBotMentioned = exports.buildAgentBody = void 0;
|
|
13
13
|
exports.processTypeXMessage = processTypeXMessage;
|
|
14
|
-
const
|
|
14
|
+
const reply_history_1 = require("openclaw/plugin-sdk/reply-history");
|
|
15
15
|
const message_helpers_js_1 = require("./message-helpers.js");
|
|
16
16
|
const runtime_js_1 = require("./runtime.js");
|
|
17
17
|
const send_js_1 = require("./send.js");
|
|
18
|
+
const agent_tools_send_js_1 = require("../agent-tools-send.js");
|
|
18
19
|
const types_js_1 = require("./types.js");
|
|
19
20
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
20
21
|
const node_path_1 = __importDefault(require("node:path"));
|
|
22
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
21
23
|
// Re-export for convenience
|
|
22
24
|
var message_helpers_js_2 = require("./message-helpers.js");
|
|
23
25
|
Object.defineProperty(exports, "buildAgentBody", { enumerable: true, get: function () { return message_helpers_js_2.buildAgentBody; } });
|
|
@@ -25,6 +27,102 @@ Object.defineProperty(exports, "checkBotMentioned", { enumerable: true, get: fun
|
|
|
25
27
|
Object.defineProperty(exports, "normalizeMessageToText", { enumerable: true, get: function () { return message_helpers_js_2.normalizeMessageToText; } });
|
|
26
28
|
Object.defineProperty(exports, "stripBotMention", { enumerable: true, get: function () { return message_helpers_js_2.stripBotMention; } });
|
|
27
29
|
const CHANNEL_ID = "openclaw-extension-typex";
|
|
30
|
+
function cleanupRecipient(value) {
|
|
31
|
+
return value
|
|
32
|
+
.replace(/<[^>]*>/g, " ")
|
|
33
|
+
.replace(/ /gi, " ")
|
|
34
|
+
.replace(/\s+/g, " ")
|
|
35
|
+
.trim()
|
|
36
|
+
.replace(/[,。!?!?,;;::]+$/g, "")
|
|
37
|
+
.replace(/<\/?[a-z][^>]*$/i, "")
|
|
38
|
+
.replace(/(?:<\/?(?:p|li|ul|ol|span|div|br)[^>]*)+$/gi, "")
|
|
39
|
+
.replace(/^(给|到)/, "")
|
|
40
|
+
.replace(/(吗|吧|呀|啊)$/g, "")
|
|
41
|
+
.trim();
|
|
42
|
+
}
|
|
43
|
+
function normalizeIntentSource(value) {
|
|
44
|
+
return value
|
|
45
|
+
.replace(/<[^>]*>/g, " ")
|
|
46
|
+
.replace(/ /gi, " ")
|
|
47
|
+
.replace(/\s+/g, " ")
|
|
48
|
+
.trim();
|
|
49
|
+
}
|
|
50
|
+
function shouldIncludeGeneratedTextForExplicitSend(text) {
|
|
51
|
+
return /(生成|写|整理|总结|编|拟|回复|文案|消息|内容|代码|祝福|介绍|说明)/.test(text);
|
|
52
|
+
}
|
|
53
|
+
function shouldIncludeAttachmentsForExplicitSend(text, attachmentPaths) {
|
|
54
|
+
if (attachmentPaths.length === 0) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return /(图|图片|照片|附件|文件|发这张|把这张|这个文件|这个附件)/.test(text);
|
|
58
|
+
}
|
|
59
|
+
function resolveMentionTargetName(mentions, appId, fallbackToken) {
|
|
60
|
+
const visibleMentions = (mentions ?? []).filter((mention) => {
|
|
61
|
+
const userId = mention.id?.user_id ?? mention.id?.open_id ?? "";
|
|
62
|
+
return userId && userId !== appId;
|
|
63
|
+
});
|
|
64
|
+
if (visibleMentions.length === 1) {
|
|
65
|
+
return visibleMentions[0].name?.trim() || fallbackToken;
|
|
66
|
+
}
|
|
67
|
+
return fallbackToken;
|
|
68
|
+
}
|
|
69
|
+
function detectExplicitSendIntent(params) {
|
|
70
|
+
const source = normalizeIntentSource(params.text);
|
|
71
|
+
if (!source) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const includeGeneratedText = params.attachmentPaths.length === 0 ||
|
|
75
|
+
shouldIncludeGeneratedTextForExplicitSend(source);
|
|
76
|
+
const includeAttachments = shouldIncludeAttachmentsForExplicitSend(source, params.attachmentPaths);
|
|
77
|
+
const sendPatterns = [
|
|
78
|
+
/(?:帮我|麻烦你|请你|你帮我|你帮忙)?(?:把)?(?:.*?)(?:发给|发送给|转给|转发给|代发给)\s*([^\n,。!?!?,;;]+)/,
|
|
79
|
+
/(?:帮我|麻烦你|请你|你帮我|你帮忙)?发(?:一条消息|个消息|消息|一句话|一段话|一下)?给\s*([^\n,。!?!?,;;]+)/,
|
|
80
|
+
/(?:帮我|麻烦你|请你|你帮我|你帮忙)?发.+?给\s*([^\n,。!?!?,;;]+)/,
|
|
81
|
+
/(?:帮我|麻烦你|请你|你帮我|你帮忙)?把(?:.+?)(?:发|发送|转发)给\s*([^\n,。!?!?,;;]+)/,
|
|
82
|
+
];
|
|
83
|
+
const sendMatch = sendPatterns
|
|
84
|
+
.map((pattern) => source.match(pattern))
|
|
85
|
+
.find((match) => Boolean(match?.[1]));
|
|
86
|
+
if (sendMatch?.[1]) {
|
|
87
|
+
const rawRecipient = cleanupRecipient(sendMatch[1]);
|
|
88
|
+
if (!rawRecipient) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if (params.isGroup) {
|
|
92
|
+
const pronounTarget = /^(他|她|它|TA|ta|对方)$/.test(rawRecipient)
|
|
93
|
+
? resolveMentionTargetName(params.mentions, params.appId, rawRecipient)
|
|
94
|
+
: rawRecipient;
|
|
95
|
+
return {
|
|
96
|
+
kind: "group-send-in-group",
|
|
97
|
+
memberName: pronounTarget,
|
|
98
|
+
includeGeneratedText,
|
|
99
|
+
includeAttachments,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
kind: "dm-send-by-name",
|
|
104
|
+
recipient: rawRecipient,
|
|
105
|
+
includeGeneratedText,
|
|
106
|
+
includeAttachments,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (params.isGroup) {
|
|
110
|
+
const hasTellIntent = source.includes("告诉") ||
|
|
111
|
+
/跟.+?(?:说|讲)/.test(source) ||
|
|
112
|
+
/对.+?(?:说|讲)/.test(source) ||
|
|
113
|
+
/让.+?知道/.test(source);
|
|
114
|
+
if (!hasTellIntent) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
kind: "group-send-in-group",
|
|
119
|
+
memberName: source,
|
|
120
|
+
includeGeneratedText: true,
|
|
121
|
+
includeAttachments,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
28
126
|
async function processTypeXMessage(client, payload, appId, options = {}) {
|
|
29
127
|
const cfg = options.cfg;
|
|
30
128
|
const accountId = options.accountId ?? appId;
|
|
@@ -48,9 +146,9 @@ async function processTypeXMessage(client, payload, appId, options = {}) {
|
|
|
48
146
|
const chatId = payload.chat_id;
|
|
49
147
|
const senderId = payload.sender_id;
|
|
50
148
|
const senderName = payload.sender_name ?? senderId;
|
|
51
|
-
const isGroup = payload.chat_type === "group";
|
|
149
|
+
const isGroup = payload.chat_type === "group" || client.mode === "bot";
|
|
52
150
|
const rawText = (0, message_helpers_js_1.normalizeMessageToText)(payload);
|
|
53
|
-
logger?.info(`[typex:${accountId}] ${isGroup ? "group" : "DM"} message from ${senderId} in ${chatId}`);
|
|
151
|
+
logger?.info(`[typex:${accountId}] ${isGroup ? "group" : "DM"} message from ${senderId} in ${chatId} (chat_type=${payload.chat_type ?? "unknown"} mode=${client.mode})`);
|
|
54
152
|
if (!rawText.trim()) {
|
|
55
153
|
logger?.info(`[typex:${accountId}] skipping empty/system message`);
|
|
56
154
|
return;
|
|
@@ -82,10 +180,10 @@ async function processTypeXMessage(client, payload, appId, options = {}) {
|
|
|
82
180
|
if (requireMention && !mentionedBot) {
|
|
83
181
|
logger?.info(`[typex:${accountId}] no @mention — buffering to history`);
|
|
84
182
|
if (chatHistories) {
|
|
85
|
-
(0,
|
|
183
|
+
(0, reply_history_1.recordPendingHistoryEntryIfEnabled)({
|
|
86
184
|
historyMap: chatHistories,
|
|
87
185
|
historyKey: chatId,
|
|
88
|
-
limit: typexCfg?.historyLimit ?? cfg.messages?.groupChat?.historyLimit ??
|
|
186
|
+
limit: typexCfg?.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? reply_history_1.DEFAULT_GROUP_HISTORY_LIMIT,
|
|
89
187
|
entry: {
|
|
90
188
|
sender: senderId,
|
|
91
189
|
body: `${senderName}: ${rawText}`,
|
|
@@ -183,55 +281,39 @@ async function processTypeXMessage(client, payload, appId, options = {}) {
|
|
|
183
281
|
});
|
|
184
282
|
}
|
|
185
283
|
if (objectKeys.size > 0) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
catch (e) {
|
|
207
|
-
const msg = e?.message ?? String(e);
|
|
208
|
-
logger?.warn?.(`[typex:${accountId}] failed to persist attachment: ${msg}`);
|
|
209
|
-
console.log('failed to persist attachment', msg);
|
|
210
|
-
try {
|
|
211
|
-
console.log('persist debug', {
|
|
212
|
-
home: process.env.HOME,
|
|
213
|
-
cwd: process.cwd(),
|
|
214
|
-
messageId: payload.message_id,
|
|
215
|
-
objectKey,
|
|
216
|
-
mimeType: fileData.mimeType,
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
catch { }
|
|
220
|
-
}
|
|
221
|
-
attachments.push({
|
|
222
|
-
contentType: fileData.mimeType,
|
|
223
|
-
mimeType: fileData.mimeType, // Alias
|
|
224
|
-
size: fileData.buffer.length,
|
|
225
|
-
payload: fileData.buffer.toString("base64"),
|
|
226
|
-
data: fileData.buffer.toString("base64"), // Alias
|
|
227
|
-
type: isImage ? "image" : "file", // Alias
|
|
228
|
-
});
|
|
284
|
+
for (const objectKey of objectKeys) {
|
|
285
|
+
logger?.info(`[typex:${accountId}] fetching attachment objectKey=${objectKey}`);
|
|
286
|
+
const fileData = await client.fetchFileBuffer(objectKey);
|
|
287
|
+
if (fileData) {
|
|
288
|
+
const isImage = fileData.mimeType.startsWith("image/");
|
|
289
|
+
// Persist to local disk so the agent can open the image via the read tool
|
|
290
|
+
// even if this surface can't transport binary attachments to the model.
|
|
291
|
+
try {
|
|
292
|
+
const extRaw = String(fileData.mimeType ?? "application/octet-stream").split("/")[1] ?? "bin";
|
|
293
|
+
const ext = extRaw === "jpeg" ? "jpg" : extRaw;
|
|
294
|
+
const homeDir = node_os_1.default.homedir?.() ?? ".";
|
|
295
|
+
// Store TypeX attachments under the OpenClaw workspace so newer
|
|
296
|
+
// local-media allowlists can safely expose them to the model.
|
|
297
|
+
const outDir = node_path_1.default.join(homeDir, ".openclaw", "workspace", "typex-attachments", String(payload.message_id));
|
|
298
|
+
node_fs_1.default.mkdirSync(outDir, { recursive: true });
|
|
299
|
+
const baseName = String(objectKey).replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
300
|
+
const outPath = node_path_1.default.join(outDir, `${baseName}.${ext}`);
|
|
301
|
+
node_fs_1.default.writeFileSync(outPath, fileData.buffer);
|
|
302
|
+
attachmentPaths.push(outPath);
|
|
229
303
|
}
|
|
304
|
+
catch (e) {
|
|
305
|
+
const msg = e?.message ?? String(e);
|
|
306
|
+
logger?.warn?.(`[typex:${accountId}] failed to persist attachment: ${msg}`);
|
|
307
|
+
}
|
|
308
|
+
attachments.push({
|
|
309
|
+
contentType: fileData.mimeType,
|
|
310
|
+
mimeType: fileData.mimeType, // Alias
|
|
311
|
+
size: fileData.buffer.length,
|
|
312
|
+
payload: fileData.buffer.toString("base64"),
|
|
313
|
+
data: fileData.buffer.toString("base64"), // Alias
|
|
314
|
+
type: isImage ? "image" : "file", // Alias
|
|
315
|
+
});
|
|
230
316
|
}
|
|
231
|
-
console.log('processed attachments:', attachments.map(a => ({ type: a.contentType, size: a.size })));
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
logger?.info(`[typex:${accountId}] skipping attachment fetch because mode is not bot`);
|
|
235
317
|
}
|
|
236
318
|
}
|
|
237
319
|
}
|
|
@@ -242,6 +324,20 @@ async function processTypeXMessage(client, payload, appId, options = {}) {
|
|
|
242
324
|
const cleanTextWithAttachments = attachmentPaths.length > 0
|
|
243
325
|
? `${cleanText}\n\n[TypeX attachments saved locally]\n${attachmentPaths.map(p => `- ${p}`).join("\n")}`
|
|
244
326
|
: cleanText;
|
|
327
|
+
const contentForAgent = cleanTextWithAttachments;
|
|
328
|
+
const explicitSendIntent = detectExplicitSendIntent({
|
|
329
|
+
text: cleanText,
|
|
330
|
+
isGroup,
|
|
331
|
+
attachmentPaths,
|
|
332
|
+
mentions: payload.mentions,
|
|
333
|
+
appId,
|
|
334
|
+
});
|
|
335
|
+
if (explicitSendIntent) {
|
|
336
|
+
const targetLabel = explicitSendIntent.kind === "dm-send-by-name"
|
|
337
|
+
? explicitSendIntent.recipient
|
|
338
|
+
: explicitSendIntent.memberName;
|
|
339
|
+
logger?.info(`[typex:${accountId}] detected explicit send intent kind=${explicitSendIntent.kind} target=${targetLabel}`);
|
|
340
|
+
}
|
|
245
341
|
// ── Session routing ───────────────────────────────────────────────────────
|
|
246
342
|
const peerId = isGroup ? chatId : senderId;
|
|
247
343
|
let route = {};
|
|
@@ -257,12 +353,12 @@ async function processTypeXMessage(client, payload, appId, options = {}) {
|
|
|
257
353
|
logger?.error(`[typex:${accountId}] resolveAgentRoute failed: ${e?.message ?? String(e)}`);
|
|
258
354
|
}
|
|
259
355
|
// ── Build message body ────────────────────────────────────────────────────
|
|
260
|
-
const historyLimit = typexCfg?.historyLimit ?? cfg?.messages?.groupChat?.historyLimit ??
|
|
356
|
+
const historyLimit = typexCfg?.historyLimit ?? cfg?.messages?.groupChat?.historyLimit ?? reply_history_1.DEFAULT_GROUP_HISTORY_LIMIT;
|
|
261
357
|
const envelopeOptions = channel.reply?.resolveEnvelopeFormatOptions?.(cfg) ?? {};
|
|
262
358
|
const agentBody = (0, message_helpers_js_1.buildAgentBody)({
|
|
263
359
|
messageId: payload.message_id,
|
|
264
360
|
senderLabel: senderName,
|
|
265
|
-
content:
|
|
361
|
+
content: contentForAgent,
|
|
266
362
|
quotedContent,
|
|
267
363
|
});
|
|
268
364
|
const envelopeFrom = isGroup ? `${chatId}:${senderId}` : senderId;
|
|
@@ -275,7 +371,7 @@ async function processTypeXMessage(client, payload, appId, options = {}) {
|
|
|
275
371
|
}) ?? agentBody;
|
|
276
372
|
let combinedBody = formattedBody;
|
|
277
373
|
if (isGroup && chatHistories) {
|
|
278
|
-
combinedBody = (0,
|
|
374
|
+
combinedBody = (0, reply_history_1.buildPendingHistoryContextFromMap)({
|
|
279
375
|
historyMap: chatHistories,
|
|
280
376
|
historyKey: chatId,
|
|
281
377
|
limit: historyLimit,
|
|
@@ -311,6 +407,11 @@ async function processTypeXMessage(client, payload, appId, options = {}) {
|
|
|
311
407
|
SenderId: senderId,
|
|
312
408
|
Provider: CHANNEL_ID,
|
|
313
409
|
Surface: CHANNEL_ID,
|
|
410
|
+
DeliveryContext: {
|
|
411
|
+
channel: CHANNEL_ID,
|
|
412
|
+
to: typexTo,
|
|
413
|
+
accountId: route.accountId ?? accountId,
|
|
414
|
+
},
|
|
314
415
|
MessageSid: payload.message_id,
|
|
315
416
|
ReplyToBody: quotedContent,
|
|
316
417
|
Timestamp: typeof payload.create_time === "number" ? payload.create_time : Date.now(),
|
|
@@ -348,9 +449,52 @@ async function processTypeXMessage(client, payload, appId, options = {}) {
|
|
|
348
449
|
extractedFromText.push(v);
|
|
349
450
|
return "";
|
|
350
451
|
}).trim();
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
452
|
+
}
|
|
453
|
+
const generatedText = text?.trim() ?? "";
|
|
454
|
+
if (explicitSendIntent) {
|
|
455
|
+
const targetLabel = explicitSendIntent.kind === "dm-send-by-name"
|
|
456
|
+
? explicitSendIntent.recipient
|
|
457
|
+
: explicitSendIntent.memberName;
|
|
458
|
+
logger?.info(`[typex:${accountId}] executing explicit send fallback kind=${explicitSendIntent.kind} target=${targetLabel}`);
|
|
459
|
+
const hasSendableContent = (explicitSendIntent.includeGeneratedText && generatedText.length > 0) ||
|
|
460
|
+
(explicitSendIntent.includeAttachments && attachmentPaths.length > 0);
|
|
461
|
+
if (!hasSendableContent) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
if (explicitSendIntent.kind === "dm-send-by-name") {
|
|
466
|
+
const messageToSend = explicitSendIntent.includeGeneratedText ? generatedText : "";
|
|
467
|
+
const mediaPathsToSend = explicitSendIntent.includeAttachments ? attachmentPaths : [];
|
|
468
|
+
await (0, agent_tools_send_js_1.executeTypeXSendByName)({
|
|
469
|
+
cfg,
|
|
470
|
+
accountId,
|
|
471
|
+
recipient: explicitSendIntent.recipient,
|
|
472
|
+
message: messageToSend,
|
|
473
|
+
mediaPaths: mediaPathsToSend,
|
|
474
|
+
});
|
|
475
|
+
await (0, send_js_1.sendMessageTypeX)(client, chatId, `已代发给 ${explicitSendIntent.recipient}。`, { msgType: types_js_1.TypeXMessageEnum.richText, replyMsgId: payload.message_id });
|
|
476
|
+
logger?.info(`[typex:${accountId}] explicit send completed kind=${explicitSendIntent.kind} target=${explicitSendIntent.recipient}`);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
const messageToSend = explicitSendIntent.includeGeneratedText ? generatedText : "";
|
|
480
|
+
const mediaPathsToSend = explicitSendIntent.includeAttachments ? attachmentPaths : [];
|
|
481
|
+
await (0, agent_tools_send_js_1.executeTypeXSendInGroup)({
|
|
482
|
+
cfg,
|
|
483
|
+
accountId,
|
|
484
|
+
chatId,
|
|
485
|
+
memberName: explicitSendIntent.memberName,
|
|
486
|
+
message: messageToSend,
|
|
487
|
+
mediaPaths: mediaPathsToSend,
|
|
488
|
+
});
|
|
489
|
+
await (0, send_js_1.sendMessageTypeX)(client, chatId, `已在群里发送给 ${explicitSendIntent.memberName}。`, { msgType: types_js_1.TypeXMessageEnum.richText, replyMsgId: payload.message_id });
|
|
490
|
+
logger?.info(`[typex:${accountId}] explicit send completed kind=${explicitSendIntent.kind} target=${explicitSendIntent.memberName}`);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
catch (error) {
|
|
494
|
+
const message = error?.message ?? String(error);
|
|
495
|
+
logger?.warn?.(`[typex:${accountId}] explicit send fallback failed: ${message}`);
|
|
496
|
+
await (0, send_js_1.sendMessageTypeX)(client, chatId, `发送失败:${message}`, { msgType: types_js_1.TypeXMessageEnum.richText, replyMsgId: payload.message_id });
|
|
497
|
+
return;
|
|
354
498
|
}
|
|
355
499
|
}
|
|
356
500
|
if (text) {
|
|
@@ -362,8 +506,6 @@ async function processTypeXMessage(client, payload, appId, options = {}) {
|
|
|
362
506
|
? [rp.mediaUrl]
|
|
363
507
|
: [];
|
|
364
508
|
for (const url of [...urls, ...extractedFromText]) {
|
|
365
|
-
logger?.info?.(`[typex:${accountId}] sending outbound mediaUrl=${url}`);
|
|
366
|
-
console.log('sending outbound mediaUrl', url);
|
|
367
509
|
await (0, send_js_1.sendMessageTypeX)(client, chatId, {}, { mediaUrl: url, msgType: types_js_1.TypeXMessageEnum.richText, replyMsgId: payload.message_id });
|
|
368
510
|
}
|
|
369
511
|
},
|
|
@@ -375,6 +517,6 @@ async function processTypeXMessage(client, payload, appId, options = {}) {
|
|
|
375
517
|
});
|
|
376
518
|
// ── Clear group history after dispatch ────────────────────────────────────
|
|
377
519
|
if (isGroup && chatHistories) {
|
|
378
|
-
(0,
|
|
520
|
+
(0, reply_history_1.clearHistoryEntriesIfEnabled)({ historyMap: chatHistories, historyKey: chatId, limit: historyLimit });
|
|
379
521
|
}
|
|
380
522
|
}
|
package/dist/client/monitor.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { OpenClawConfig
|
|
1
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core";
|
|
2
|
+
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
|
2
3
|
export type MonitorTypeXOpts = {
|
|
3
4
|
account: unknown;
|
|
4
5
|
runtime: RuntimeEnv;
|
package/dist/client/monitor.js
CHANGED
|
@@ -41,12 +41,13 @@ const client_js_1 = require("./client.js");
|
|
|
41
41
|
const message_js_1 = require("./message.js");
|
|
42
42
|
const runtime_js_1 = require("./runtime.js");
|
|
43
43
|
const POS_STORE_VERSION = 1;
|
|
44
|
-
const
|
|
44
|
+
const OPENCLAW_HOME_DIR = path.join(os.homedir(), ".openclaw");
|
|
45
|
+
const OPENCLAW_CONFIG_PATH = path.join(OPENCLAW_HOME_DIR, "openclaw.json");
|
|
45
46
|
function normalizeAccountIdForFile(accountId) {
|
|
46
47
|
return (accountId || "default").replace(/[^a-z0-9._-]+/gi, "_");
|
|
47
48
|
}
|
|
48
49
|
function resolveTypeXPosStatePath(accountId) {
|
|
49
|
-
const stateDir = (0, runtime_js_1.getTypeXRuntime)().state.resolveStateDir(
|
|
50
|
+
const stateDir = (0, runtime_js_1.getTypeXRuntime)().state.resolveStateDir({}, os.homedir);
|
|
50
51
|
const normalized = normalizeAccountIdForFile(accountId);
|
|
51
52
|
return path.join(stateDir, "typex", `update-pos-${normalized}.json`);
|
|
52
53
|
}
|
|
@@ -206,18 +207,46 @@ async function monitorTypeXProvider(opts) {
|
|
|
206
207
|
// Group history buffer: lives for the entire monitor lifetime.
|
|
207
208
|
const chatHistories = new Map();
|
|
208
209
|
let fatalError = null;
|
|
210
|
+
let consecutiveFetchFailures = 0;
|
|
211
|
+
const resolveBackoffMs = (failures) => {
|
|
212
|
+
if (failures <= 0)
|
|
213
|
+
return 3000;
|
|
214
|
+
if (failures === 1)
|
|
215
|
+
return 10000;
|
|
216
|
+
if (failures === 2)
|
|
217
|
+
return 30000;
|
|
218
|
+
if (failures === 3)
|
|
219
|
+
return 60000;
|
|
220
|
+
return 300000;
|
|
221
|
+
};
|
|
222
|
+
const isFatalFetchError = (err) => {
|
|
223
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
224
|
+
const normalized = message.toLowerCase();
|
|
225
|
+
return (normalized.includes("non-json response") ||
|
|
226
|
+
normalized.includes("session auth error") ||
|
|
227
|
+
normalized.includes("http 401") ||
|
|
228
|
+
normalized.includes("http 403") ||
|
|
229
|
+
normalized.includes("http 404"));
|
|
230
|
+
};
|
|
209
231
|
// --- Polling Loop ---
|
|
210
232
|
while (!abortSignal.aborted) {
|
|
211
233
|
let messages;
|
|
212
234
|
try {
|
|
213
235
|
messages = await client.fetchMessages(currentPos);
|
|
236
|
+
consecutiveFetchFailures = 0;
|
|
214
237
|
}
|
|
215
238
|
catch (err) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
239
|
+
consecutiveFetchFailures += 1;
|
|
240
|
+
const backoffMs = resolveBackoffMs(consecutiveFetchFailures);
|
|
241
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
242
|
+
logger?.error(`[${accountObj.accountId}] Failed to fetch TypeX messages (attempt ${consecutiveFetchFailures}); next retry in ${Math.round(backoffMs / 1000)}s: ${error.message}`);
|
|
243
|
+
if (isFatalFetchError(error) || consecutiveFetchFailures >= 5) {
|
|
244
|
+
logger?.error(`[${accountObj.accountId}] Fatal error fetching TypeX messages; stopping monitor to avoid request spam.`);
|
|
245
|
+
fatalError = error;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
249
|
+
continue;
|
|
221
250
|
}
|
|
222
251
|
if (messages && messages.length > 0) {
|
|
223
252
|
for (const msg of messages) {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk/core";
|
|
2
2
|
export declare const typexOutbound: ChannelOutboundAdapter;
|
package/dist/client/outbound.js
CHANGED
|
@@ -3,29 +3,74 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.typexOutbound = void 0;
|
|
4
4
|
const client_js_1 = require("./client.js");
|
|
5
5
|
const send_js_1 = require("./send.js");
|
|
6
|
+
const types_js_1 = require("./types.js");
|
|
7
|
+
function resolveTypeXTarget(rawTarget) {
|
|
8
|
+
const trimmed = (rawTarget ?? "").trim();
|
|
9
|
+
if (/^user:\d+$/i.test(trimmed)) {
|
|
10
|
+
return { chatId: trimmed.replace(/^user:/i, ""), receiverId: trimmed.replace(/^user:/i, "") };
|
|
11
|
+
}
|
|
12
|
+
if (/^(?:chat|group):\d+$/i.test(trimmed)) {
|
|
13
|
+
return { chatId: trimmed.replace(/^(?:chat|group):/i, "") };
|
|
14
|
+
}
|
|
15
|
+
return { chatId: trimmed };
|
|
16
|
+
}
|
|
6
17
|
exports.typexOutbound = {
|
|
7
18
|
deliveryMode: "direct",
|
|
8
19
|
// chunker: ... (optional, default might be used or I can skip for MVP)
|
|
9
20
|
chunkerMode: "markdown",
|
|
10
21
|
textChunkLimit: 2000,
|
|
22
|
+
resolveTarget: ({ to }) => {
|
|
23
|
+
const trimmed = (to ?? "").trim();
|
|
24
|
+
if (!trimmed) {
|
|
25
|
+
return { ok: false, error: new Error("TypeX target is required.") };
|
|
26
|
+
}
|
|
27
|
+
if (/^(?:user|chat|group):\d+$/i.test(trimmed) || /^\d+$/.test(trimmed)) {
|
|
28
|
+
return { ok: true, to: trimmed };
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
error: new Error("TypeX target must be a numeric id or a user:/chat:/group: target."),
|
|
33
|
+
};
|
|
34
|
+
},
|
|
11
35
|
sendText: async ({ to, text, accountId, cfg }) => {
|
|
12
36
|
const typexCfg = (cfg?.channels?.["openclaw-extension-typex"] ?? {});
|
|
13
37
|
const client = (0, client_js_1.getTypeXClient)(accountId ?? undefined, { typexCfg });
|
|
14
|
-
const
|
|
38
|
+
const target = resolveTypeXTarget(to);
|
|
39
|
+
let hasMention = false;
|
|
40
|
+
const finalText = (text || "").replace(/<at\s+user_id="([^"]+)"(?:>([^<]*)<\/at>|\s*\/>)/g, (match, userId, name) => {
|
|
41
|
+
hasMention = true;
|
|
42
|
+
const label = name || userId;
|
|
43
|
+
return `@${label} `;
|
|
44
|
+
});
|
|
45
|
+
const result = await (0, send_js_1.sendMessageTypeX)(client, target.chatId, finalText, {
|
|
46
|
+
receiverId: target.receiverId,
|
|
47
|
+
msgType: hasMention ? types_js_1.TypeXMessageEnum.text : undefined,
|
|
48
|
+
});
|
|
15
49
|
return {
|
|
16
50
|
channel: "openclaw-extension-typex",
|
|
17
51
|
messageId: result?.message_id || "unknown",
|
|
18
|
-
chatId:
|
|
52
|
+
chatId: target.chatId,
|
|
19
53
|
};
|
|
20
54
|
},
|
|
21
55
|
sendMedia: async ({ to, text, mediaUrl, accountId, cfg }) => {
|
|
22
56
|
const typexCfg = (cfg?.channels?.["openclaw-extension-typex"] ?? {});
|
|
23
57
|
const client = (0, client_js_1.getTypeXClient)(accountId ?? undefined, { typexCfg });
|
|
24
|
-
const
|
|
58
|
+
const target = resolveTypeXTarget(to);
|
|
59
|
+
let hasMention = false;
|
|
60
|
+
const finalText = (text || "").replace(/<at\s+user_id="([^"]+)"(?:>([^<]*)<\/at>|\s*\/>)/g, (match, userId, name) => {
|
|
61
|
+
hasMention = true;
|
|
62
|
+
const label = name || userId;
|
|
63
|
+
return `@${label} `;
|
|
64
|
+
});
|
|
65
|
+
const result = await (0, send_js_1.sendMessageTypeX)(client, target.chatId, finalText, {
|
|
66
|
+
mediaUrl,
|
|
67
|
+
receiverId: target.receiverId,
|
|
68
|
+
msgType: hasMention ? types_js_1.TypeXMessageEnum.text : undefined,
|
|
69
|
+
});
|
|
25
70
|
return {
|
|
26
71
|
channel: "openclaw-extension-typex",
|
|
27
72
|
messageId: result?.message_id || "unknown",
|
|
28
|
-
chatId:
|
|
73
|
+
chatId: target.chatId,
|
|
29
74
|
};
|
|
30
75
|
},
|
|
31
76
|
};
|
package/dist/client/runtime.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Global runtime reference for the TypeX plugin.
|
|
3
3
|
*/
|
|
4
|
-
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
4
|
+
import type { PluginRuntime } from "openclaw/plugin-sdk/channel-core";
|
|
5
5
|
export declare function setTypeXRuntime(next: PluginRuntime): void;
|
|
6
6
|
export declare function getTypeXRuntime(): PluginRuntime;
|
package/dist/client/send.d.ts
CHANGED
|
@@ -5,6 +5,13 @@ export type TypeXSendOpts = {
|
|
|
5
5
|
mediaUrl?: string;
|
|
6
6
|
maxBytes?: number;
|
|
7
7
|
replyMsgId?: string;
|
|
8
|
+
receiverId?: string;
|
|
9
|
+
isDelegate?: boolean;
|
|
10
|
+
atUserIds?: string[];
|
|
11
|
+
atMentions?: Array<{
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
}>;
|
|
8
15
|
};
|
|
9
16
|
/**
|
|
10
17
|
* Send a message via the TypeX client to a specific chat.
|
|
@@ -16,6 +23,7 @@ export type TypeXSendOpts = {
|
|
|
16
23
|
export declare function sendMessageTypeX(client: TypeXClient, chatId: string, content: string | {
|
|
17
24
|
text?: string;
|
|
18
25
|
object_url?: string;
|
|
26
|
+
thumb_url?: string;
|
|
19
27
|
file_name?: string;
|
|
20
28
|
file_size?: number;
|
|
21
29
|
file_type?: string;
|
package/dist/client/send.js
CHANGED
|
@@ -77,6 +77,16 @@ async function sendMessageTypeX(client, chatId, content, opts = {}) {
|
|
|
77
77
|
finalContent = opts.mediaUrl;
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
-
const res =
|
|
80
|
+
const res = client.mode === "bot"
|
|
81
|
+
? await client.sendBotGroupMessage(chatId, finalContent, msgType, {
|
|
82
|
+
replyMsgId: opts.replyMsgId,
|
|
83
|
+
atUserIds: opts.atUserIds,
|
|
84
|
+
atMentions: opts.atMentions,
|
|
85
|
+
})
|
|
86
|
+
: opts.isDelegate && opts.receiverId
|
|
87
|
+
? await client.sendDelegatedContactMessage(opts.receiverId, finalContent, msgType)
|
|
88
|
+
: opts.isDelegate
|
|
89
|
+
? await client.sendDelegatedChatMessage(chatId, finalContent, msgType)
|
|
90
|
+
: await client.sendUserChatMessage(chatId, finalContent, msgType);
|
|
81
91
|
return res;
|
|
82
92
|
}
|
package/dist/client/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WizardPrompter } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { WizardPrompter } from "openclaw/plugin-sdk/setup";
|
|
2
2
|
export declare enum TypeXMessageEnum {
|
|
3
3
|
text = 0,
|
|
4
4
|
image = 1,
|
|
@@ -73,3 +73,19 @@ export interface TypeXMessageEntry {
|
|
|
73
73
|
/** Monotonic position cursor used by the polling loop. */
|
|
74
74
|
position: number;
|
|
75
75
|
}
|
|
76
|
+
export interface TypeXFeedSearchEntry {
|
|
77
|
+
id: string;
|
|
78
|
+
chat_id: string;
|
|
79
|
+
name?: string;
|
|
80
|
+
}
|
|
81
|
+
export interface TypeXContactSearchEntry {
|
|
82
|
+
friend_id: string;
|
|
83
|
+
name?: string;
|
|
84
|
+
}
|
|
85
|
+
export interface TypeXGroupMemberEntry {
|
|
86
|
+
user_id: string;
|
|
87
|
+
name?: string;
|
|
88
|
+
avatar?: string;
|
|
89
|
+
member_role?: number;
|
|
90
|
+
joined_at?: number;
|
|
91
|
+
}
|
package/dist/config-schema.d.ts
CHANGED
|
@@ -7,9 +7,10 @@ export declare const TypeXConfigSchema: z.ZodObject<{
|
|
|
7
7
|
botName: z.ZodOptional<z.ZodString>;
|
|
8
8
|
markdown: z.ZodOptional<z.ZodObject<{
|
|
9
9
|
tables: z.ZodOptional<z.ZodEnum<{
|
|
10
|
+
code: "code";
|
|
10
11
|
off: "off";
|
|
11
12
|
bullets: "bullets";
|
|
12
|
-
|
|
13
|
+
block: "block";
|
|
13
14
|
}>>;
|
|
14
15
|
}, z.core.$strict>>;
|
|
15
16
|
allowFrom: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
@@ -36,9 +37,10 @@ export declare const TypeXConfigSchema: z.ZodObject<{
|
|
|
36
37
|
botName: z.ZodOptional<z.ZodString>;
|
|
37
38
|
markdown: z.ZodOptional<z.ZodObject<{
|
|
38
39
|
tables: z.ZodOptional<z.ZodEnum<{
|
|
40
|
+
code: "code";
|
|
39
41
|
off: "off";
|
|
40
42
|
bullets: "bullets";
|
|
41
|
-
|
|
43
|
+
block: "block";
|
|
42
44
|
}>>;
|
|
43
45
|
}, z.core.$strict>>;
|
|
44
46
|
allowFrom: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
package/dist/config-schema.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TypeXConfigSchema = void 0;
|
|
4
|
-
const
|
|
4
|
+
const channel_config_schema_1 = require("openclaw/plugin-sdk/channel-config-schema");
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
6
|
const allowFromEntry = zod_1.z.union([zod_1.z.string(), zod_1.z.number()]);
|
|
7
|
-
const toolsBySenderSchema = zod_1.z.record(zod_1.z.string(),
|
|
7
|
+
const toolsBySenderSchema = zod_1.z.record(zod_1.z.string(), channel_config_schema_1.ToolPolicySchema).optional();
|
|
8
8
|
// Simplify Group Schema for TypeX MVP
|
|
9
9
|
const TypeXGroupSchema = zod_1.z
|
|
10
10
|
.object({
|
|
11
11
|
enabled: zod_1.z.boolean().optional(),
|
|
12
|
-
tools:
|
|
12
|
+
tools: channel_config_schema_1.ToolPolicySchema,
|
|
13
13
|
toolsBySender: toolsBySenderSchema,
|
|
14
14
|
systemPrompt: zod_1.z.string().optional(),
|
|
15
15
|
})
|
|
@@ -21,7 +21,7 @@ const TypeXAccountSchema = zod_1.z
|
|
|
21
21
|
appId: zod_1.z.string().optional(),
|
|
22
22
|
appSecret: zod_1.z.string().optional(),
|
|
23
23
|
botName: zod_1.z.string().optional(),
|
|
24
|
-
markdown:
|
|
24
|
+
markdown: channel_config_schema_1.MarkdownConfigSchema,
|
|
25
25
|
// Policy settings
|
|
26
26
|
allowFrom: zod_1.z.array(allowFromEntry).optional(),
|
|
27
27
|
responsePrefix: zod_1.z.string().optional(),
|