palz-connector 1.2.5 → 1.2.7
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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/palz-connector.config.json +2 -1
- package/palz-connector.dev.config.json +2 -1
- package/palz-connector.prod.config.json +2 -1
- package/palz-connector.staging.config.json +2 -1
- package/src/bot.ts +73 -11
- package/src/config.ts +1 -0
- package/src/reply-dispatcher.ts +3 -0
- package/src/send.ts +8 -3
- package/src/types.ts +10 -0
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/bot.ts
CHANGED
|
@@ -15,7 +15,19 @@ import { resolvePalzAccount } from "./config.js";
|
|
|
15
15
|
import { tryClaimMessage } from "./dedup.js";
|
|
16
16
|
import { createPalzReplyDispatcher } from "./reply-dispatcher.js";
|
|
17
17
|
import { resolvePalzMediaList } from "./media.js";
|
|
18
|
-
import type { PalzMessageEvent, OpenAIContent, ContentPart, TextContentPart } from "./types.js";
|
|
18
|
+
import type { PalzMessageEvent, OpenAIContent, ContentPart, TextContentPart, PalzMediaInfo } from "./types.js";
|
|
19
|
+
|
|
20
|
+
// ============ group_id 解析 ============
|
|
21
|
+
|
|
22
|
+
/** 从 conversation_id 中解析 group_id,格式: user_{userID}_lobster_{lobsterID}_group_{groupID}_release_{releaseName} */
|
|
23
|
+
const GROUP_ID_RE = /_group_([^_]+)_release_/;
|
|
24
|
+
|
|
25
|
+
function resolveGroupId(msg: PalzMessageEvent): string | undefined {
|
|
26
|
+
if (msg.group_id) return msg.group_id;
|
|
27
|
+
if (msg.conversation_type !== "group") return undefined;
|
|
28
|
+
const m = GROUP_ID_RE.exec(msg.conversation_id);
|
|
29
|
+
return m ? m[1] : undefined;
|
|
30
|
+
}
|
|
19
31
|
|
|
20
32
|
// ============ 文本提取工具 ============
|
|
21
33
|
|
|
@@ -51,6 +63,7 @@ const enqueue = createChatQueue();
|
|
|
51
63
|
interface HistoryEntry {
|
|
52
64
|
sender: string;
|
|
53
65
|
body: string;
|
|
66
|
+
mediaList?: PalzMediaInfo[];
|
|
54
67
|
timestamp: number;
|
|
55
68
|
messageId: string;
|
|
56
69
|
}
|
|
@@ -193,25 +206,39 @@ export async function handlePalzMessage(params: HandlePalzMessageParams): Promis
|
|
|
193
206
|
|
|
194
207
|
// 群聊 @提及检测
|
|
195
208
|
const wasMentioned = isGroup ? (msg.mentioned_bot === true) : true;
|
|
196
|
-
|
|
197
|
-
|
|
209
|
+
// 群聊上下文缓存:groupContextCache=true(默认)时,未@消息仅缓存不发送;
|
|
210
|
+
// groupContextCache=false 时,所有群聊消息都发送给AI
|
|
211
|
+
const account = resolvePalzAccount({ cfg, accountId });
|
|
212
|
+
const groupContextCacheEnabled = account.config.groupContextCache !== false;
|
|
213
|
+
if (isGroup && !wasMentioned && groupContextCacheEnabled) {
|
|
214
|
+
// 未@机器人:记录到群聊历史(含媒体),下次被@时作为上下文
|
|
198
215
|
const historyKey = `${effectiveAgentId}:${msg.conversation_id}`;
|
|
199
216
|
const senderName = msg.sender_name || msg.sender_id;
|
|
200
217
|
log(`${tag}: [STEP 1 群聊历史] 未@机器人, 准备记录历史 historyKey=${historyKey} mentioned_bot=${msg.mentioned_bot} conversation_type=${msg.conversation_type}`);
|
|
218
|
+
// 解析媒体文件(图片/文档等),缓存到本地
|
|
219
|
+
const historyMediaList = await resolvePalzMediaList(msg.content, log);
|
|
220
|
+
log(`${tag}: [STEP 1 群聊历史] 媒体解析完成: mediaCount=${historyMediaList.length}`);
|
|
221
|
+
const bodyWithPlaceholder = historyMediaList.length > 0
|
|
222
|
+
? `${senderName}: ${plainText} ${historyMediaList.map(m => m.placeholder).join(' ')}`
|
|
223
|
+
: `${senderName}: ${plainText}`;
|
|
201
224
|
recordGroupHistoryEntry({
|
|
202
225
|
historyKey,
|
|
203
226
|
entry: {
|
|
204
227
|
sender: msg.sender_id,
|
|
205
|
-
body:
|
|
228
|
+
body: bodyWithPlaceholder,
|
|
229
|
+
mediaList: historyMediaList.length > 0 ? historyMediaList : undefined,
|
|
206
230
|
timestamp: Date.now(),
|
|
207
231
|
messageId: msg.msg_id,
|
|
208
232
|
},
|
|
209
233
|
limit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
210
234
|
log,
|
|
211
235
|
});
|
|
212
|
-
log(`${tag}: [STEP 1 跳过] 原因=群聊中未@机器人, 已记录到历史 historyKey=${historyKey}`);
|
|
236
|
+
log(`${tag}: [STEP 1 跳过] 原因=群聊中未@机器人, 已记录到历史 historyKey=${historyKey} mediaCount=${historyMediaList.length}`);
|
|
213
237
|
return;
|
|
214
238
|
}
|
|
239
|
+
if (isGroup && !wasMentioned && !groupContextCacheEnabled) {
|
|
240
|
+
log(`${tag}: [STEP 1 群聊直通] groupContextCache=false, 未@消息也将发送给AI mentioned_bot=${msg.mentioned_bot}`);
|
|
241
|
+
}
|
|
215
242
|
|
|
216
243
|
// 去重(按 agentId + conversationId 隔离,同群多 bot 场景)
|
|
217
244
|
const claimed = tryClaimMessage(msg.msg_id, effectiveAgentId, msg.conversation_id);
|
|
@@ -247,6 +274,10 @@ async function dispatchPalzMessage(params: HandlePalzMessageParams): Promise<voi
|
|
|
247
274
|
const plainText = extractPlainText(msg.content).trim();
|
|
248
275
|
const useStream = msg.stream === true;
|
|
249
276
|
const senderName = msg.sender_name || msg.sender_id;
|
|
277
|
+
const groupId = resolveGroupId(msg);
|
|
278
|
+
if (isGroup) {
|
|
279
|
+
log(`${tag}: [group_id] resolved=${groupId ?? "(none)"} from_msg=${msg.group_id ?? "(none)"} conv=${msg.conversation_id}`);
|
|
280
|
+
}
|
|
250
281
|
|
|
251
282
|
// 群聊:peerId = chat:conversation_id(整群共享 session,与 palzTo 格式一致)
|
|
252
283
|
// DM:peerId = sender_id:conversation_id(每用户每会话独立 session)
|
|
@@ -258,7 +289,7 @@ async function dispatchPalzMessage(params: HandlePalzMessageParams): Promise<voi
|
|
|
258
289
|
: 0;
|
|
259
290
|
log(`${tag}: [STEP 4/6 媒体解析] 输入: contentType=${typeof msg.content === "string" ? "string" : "array"} mediaCount=${mediaCount}`);
|
|
260
291
|
const mediaList = await resolvePalzMediaList(msg.content, log);
|
|
261
|
-
|
|
292
|
+
let mediaPayload = buildMediaPayload(mediaList);
|
|
262
293
|
log(`${tag}: [STEP 4 输出] mediaList=${JSON.stringify(mediaList.map((m) => ({ path: m.path, contentType: m.contentType })))} mediaPayload=${JSON.stringify(mediaPayload)}`);
|
|
263
294
|
|
|
264
295
|
// STEP 5: 解析路由
|
|
@@ -316,10 +347,11 @@ async function dispatchPalzMessage(params: HandlePalzMessageParams): Promise<voi
|
|
|
316
347
|
|
|
317
348
|
const chatType = isGroup ? "group" : "direct";
|
|
318
349
|
|
|
319
|
-
// 群聊历史:将积攒的未@消息拼入 Body
|
|
320
|
-
const
|
|
350
|
+
// 群聊历史:将积攒的未@消息拼入 Body 上下文(仅 groupContextCache=true 时生效)
|
|
351
|
+
const groupContextCacheEnabled = account.config.groupContextCache !== false;
|
|
352
|
+
const historyKey = isGroup && groupContextCacheEnabled ? `${effectiveAgentId}:${msg.conversation_id}` : undefined;
|
|
321
353
|
let combinedBody = body;
|
|
322
|
-
if (isGroup && historyKey) {
|
|
354
|
+
if (isGroup && historyKey && groupContextCacheEnabled) {
|
|
323
355
|
log(`${tag}: [STEP 6b 群聊历史] 开始构建, historyKey=${historyKey} bodyLen=${body.length}`);
|
|
324
356
|
combinedBody = buildGroupHistoryContext({
|
|
325
357
|
historyKey,
|
|
@@ -336,11 +368,20 @@ async function dispatchPalzMessage(params: HandlePalzMessageParams): Promise<voi
|
|
|
336
368
|
});
|
|
337
369
|
log(`${tag}: [STEP 6b 群聊历史] 构建完成, historyKey=${historyKey} bodyLen=${body.length} combinedBodyLen=${combinedBody.length} hasHistory=${combinedBody.length !== body.length}`);
|
|
338
370
|
// log(`${tag}: [STEP 6b 群聊历史] combinedBody=\n${combinedBody}`);
|
|
371
|
+
|
|
372
|
+
// 将历史中缓存的媒体合并到当前 mediaPayload
|
|
373
|
+
const historyEntries = chatHistories.get(historyKey) ?? [];
|
|
374
|
+
const historyMediaList = historyEntries.flatMap(e => e.mediaList ?? []);
|
|
375
|
+
if (historyMediaList.length > 0) {
|
|
376
|
+
const allMedia = [...historyMediaList, ...mediaList];
|
|
377
|
+
mediaPayload = buildMediaPayload(allMedia);
|
|
378
|
+
log(`${tag}: [STEP 6b 历史媒体合并] historyMedia=${historyMediaList.length} currentMedia=${mediaList.length} totalMedia=${allMedia.length}`);
|
|
379
|
+
}
|
|
339
380
|
}
|
|
340
381
|
|
|
341
382
|
// 构建 InboundHistory(结构化历史数据,Runtime 会注入到系统提示中)
|
|
342
383
|
const inboundHistory =
|
|
343
|
-
isGroup && historyKey
|
|
384
|
+
isGroup && historyKey && groupContextCacheEnabled
|
|
344
385
|
? (chatHistories.get(historyKey) ?? []).map((entry) => ({
|
|
345
386
|
sender: entry.sender,
|
|
346
387
|
body: entry.body,
|
|
@@ -349,6 +390,25 @@ async function dispatchPalzMessage(params: HandlePalzMessageParams): Promise<voi
|
|
|
349
390
|
: undefined;
|
|
350
391
|
log(`${tag}: [STEP 6b InboundHistory] count=${inboundHistory?.length ?? 0}`);
|
|
351
392
|
|
|
393
|
+
// 构建 UntrustedContext:将 IM 消息的关键字段注入到 AI agent 的上下文中
|
|
394
|
+
const untrustedContext: string[] = [
|
|
395
|
+
`sender_id: ${msg.sender_id}`,
|
|
396
|
+
`sender_name: ${senderName}`,
|
|
397
|
+
`conversation_id: ${msg.conversation_id}`,
|
|
398
|
+
`conversation_type: ${msg.conversation_type || "direct"}`,
|
|
399
|
+
`mentioned_bot: ${wasMentioned}`,
|
|
400
|
+
];
|
|
401
|
+
if (groupId) {
|
|
402
|
+
untrustedContext.push(`group_id: ${groupId}`);
|
|
403
|
+
}
|
|
404
|
+
if (msg.owner_id) {
|
|
405
|
+
untrustedContext.push(`owner_id: ${msg.owner_id}`);
|
|
406
|
+
}
|
|
407
|
+
if (msg.owner_name) {
|
|
408
|
+
untrustedContext.push(`owner_name: ${msg.owner_name}`);
|
|
409
|
+
}
|
|
410
|
+
log(`${tag}: [STEP 6b UntrustedContext] entries=${untrustedContext.length} values=${JSON.stringify(untrustedContext)}`);
|
|
411
|
+
|
|
352
412
|
const ctx = core.channel.reply.finalizeInboundContext({
|
|
353
413
|
Body: combinedBody,
|
|
354
414
|
BodyForAgent: messageBody,
|
|
@@ -363,6 +423,7 @@ async function dispatchPalzMessage(params: HandlePalzMessageParams): Promise<voi
|
|
|
363
423
|
GroupSubject: isGroup ? msg.conversation_id : undefined,
|
|
364
424
|
SenderId: msg.sender_id,
|
|
365
425
|
SenderName: senderName,
|
|
426
|
+
UntrustedContext: untrustedContext,
|
|
366
427
|
Provider: "palz-connector",
|
|
367
428
|
Surface: "palz-connector",
|
|
368
429
|
MessageSid: msg.msg_id,
|
|
@@ -401,6 +462,7 @@ async function dispatchPalzMessage(params: HandlePalzMessageParams): Promise<voi
|
|
|
401
462
|
enableStreaming: useStream,
|
|
402
463
|
msgId: msg.msg_id,
|
|
403
464
|
msgType: msg.msg_type,
|
|
465
|
+
groupId,
|
|
404
466
|
});
|
|
405
467
|
|
|
406
468
|
// STEP 6d: 分发消息给 AI
|
|
@@ -422,7 +484,7 @@ async function dispatchPalzMessage(params: HandlePalzMessageParams): Promise<voi
|
|
|
422
484
|
});
|
|
423
485
|
|
|
424
486
|
// AI 回复完成后清空群聊历史(已拼入上下文,避免下次重复)
|
|
425
|
-
if (isGroup && historyKey) {
|
|
487
|
+
if (isGroup && historyKey && groupContextCacheEnabled) {
|
|
426
488
|
clearGroupHistory(historyKey, log);
|
|
427
489
|
}
|
|
428
490
|
|
package/src/config.ts
CHANGED
|
@@ -72,6 +72,7 @@ export function resolvePalzConfig(_cfg?: any): PalzConfig {
|
|
|
72
72
|
streamUrl: file.streamUrl || "",
|
|
73
73
|
apiBaseUrl: file.apiBaseUrl || "",
|
|
74
74
|
sessionTimeout: file.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT,
|
|
75
|
+
groupContextCache: file.groupContextCache !== false,
|
|
75
76
|
};
|
|
76
77
|
if (!_configLoggedOnce) {
|
|
77
78
|
_configLoggedOnce = true;
|
package/src/reply-dispatcher.ts
CHANGED
|
@@ -42,6 +42,7 @@ export interface CreatePalzReplyDispatcherParams {
|
|
|
42
42
|
enableStreaming: boolean;
|
|
43
43
|
msgId: string;
|
|
44
44
|
msgType?: string;
|
|
45
|
+
groupId?: string;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
export function createPalzReplyDispatcher(params: CreatePalzReplyDispatcherParams) {
|
|
@@ -57,6 +58,7 @@ export function createPalzReplyDispatcher(params: CreatePalzReplyDispatcherParam
|
|
|
57
58
|
enableStreaming,
|
|
58
59
|
msgId,
|
|
59
60
|
msgType,
|
|
61
|
+
groupId,
|
|
60
62
|
} = params;
|
|
61
63
|
|
|
62
64
|
const log = typeof runtime?.log === "function" ? runtime.log : console.log;
|
|
@@ -87,6 +89,7 @@ export function createPalzReplyDispatcher(params: CreatePalzReplyDispatcherParam
|
|
|
87
89
|
senderId,
|
|
88
90
|
stream: streamOpts,
|
|
89
91
|
msgType,
|
|
92
|
+
groupId,
|
|
90
93
|
});
|
|
91
94
|
log(`${tag}: [DISPATCHER←sendToIM] 输出: ${JSON.stringify(result)}`);
|
|
92
95
|
return result;
|
package/src/send.ts
CHANGED
|
@@ -14,7 +14,7 @@ function nextMsgId(): string {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export async function sendToPalzIM(params: SendToIMParams): Promise<any> {
|
|
17
|
-
const { config, conversationId, content, conversationType, msgId, senderId, stream, msgType } = params;
|
|
17
|
+
const { config, conversationId, content, conversationType, msgId, senderId, stream, msgType, groupId } = params;
|
|
18
18
|
const url = `${config.apiBaseUrl}/bot/send`;
|
|
19
19
|
const resolvedMsgId = msgId || nextMsgId();
|
|
20
20
|
|
|
@@ -34,6 +34,10 @@ export async function sendToPalzIM(params: SendToIMParams): Promise<any> {
|
|
|
34
34
|
reqBody.msg_type = msgType;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
if (groupId) {
|
|
38
|
+
reqBody.group_id = groupId;
|
|
39
|
+
}
|
|
40
|
+
|
|
37
41
|
if (stream) {
|
|
38
42
|
reqBody.stream_id = stream.streamId;
|
|
39
43
|
reqBody.seq = stream.seq;
|
|
@@ -41,15 +45,16 @@ export async function sendToPalzIM(params: SendToIMParams): Promise<any> {
|
|
|
41
45
|
reqBody.delta = stream.delta;
|
|
42
46
|
}
|
|
43
47
|
|
|
48
|
+
const reqBodyStr = JSON.stringify(reqBody);
|
|
44
49
|
console.log(
|
|
45
|
-
`palz-send: [HTTP_REQ] POST ${url}\n request_body=${
|
|
50
|
+
`palz-send: [HTTP_REQ] POST ${url} body_length=${reqBodyStr.length}\n request_body=${reqBodyStr}`,
|
|
46
51
|
);
|
|
47
52
|
|
|
48
53
|
const startMs = Date.now();
|
|
49
54
|
const response = await fetch(url, {
|
|
50
55
|
method: "POST",
|
|
51
56
|
headers: { "Content-Type": "application/json" },
|
|
52
|
-
body:
|
|
57
|
+
body: reqBodyStr,
|
|
53
58
|
});
|
|
54
59
|
const elapsedMs = Date.now() - startMs;
|
|
55
60
|
|
package/src/types.ts
CHANGED
|
@@ -31,6 +31,12 @@ export interface PalzMessageEvent {
|
|
|
31
31
|
mentioned_bot?: boolean;
|
|
32
32
|
/** 可选,IM 下发的消息类型,回复时原样透传 */
|
|
33
33
|
msg_type?: string;
|
|
34
|
+
/** Bot 所属用户的 ID */
|
|
35
|
+
owner_id?: string;
|
|
36
|
+
/** Bot 所属用户的用户名 */
|
|
37
|
+
owner_name?: string;
|
|
38
|
+
/** 群组 ID,群聊时 IM 可直接下发;若未提供则从 conversation_id 中解析 */
|
|
39
|
+
group_id?: string;
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
// ============ 配置 ============
|
|
@@ -41,6 +47,8 @@ export interface PalzConfig {
|
|
|
41
47
|
streamUrl?: string;
|
|
42
48
|
apiBaseUrl?: string;
|
|
43
49
|
sessionTimeout?: number;
|
|
50
|
+
/** 群聊上下文缓存开关:true=未@消息缓存为上下文(默认),false=所有群聊消息直接发送给AI */
|
|
51
|
+
groupContextCache?: boolean;
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
export interface ResolvedPalzAccount {
|
|
@@ -78,4 +86,6 @@ export interface SendToIMParams {
|
|
|
78
86
|
stream?: StreamChunkOpts;
|
|
79
87
|
/** IM 下发的消息类型,回复时原样透传 */
|
|
80
88
|
msgType?: string;
|
|
89
|
+
/** 群组 ID,群聊时透传 */
|
|
90
|
+
groupId?: string;
|
|
81
91
|
}
|