palz-connector 1.3.5 → 1.3.6
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/src/bot.ts +17 -13
- package/src/channel.ts +2 -2
- package/src/outbound.ts +8 -6
- package/src/reply-dispatcher.ts +4 -0
- package/src/send.ts +6 -1
- package/src/targets.ts +37 -11
- package/src/types.ts +6 -0
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/bot.ts
CHANGED
|
@@ -18,16 +18,18 @@ import { resolvePalzMediaList, resolveMediaLocalRoots } from "./media.js";
|
|
|
18
18
|
import { tracer, trace, context, SpanStatusCode } from "./tracing.js";
|
|
19
19
|
import type { PalzMessageEvent, OpenAIContent, ContentPart, TextContentPart, PalzMediaInfo } from "./types.js";
|
|
20
20
|
|
|
21
|
-
// ============
|
|
21
|
+
// ============ 原始消息透传字段 ============
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
const GROUP_ID_RE = /_group_([^_]+)_release_/;
|
|
23
|
+
const PASSTHROUGH_EXCLUDE = new Set(["event", "content", "timestamp"]);
|
|
25
24
|
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
function buildPassthroughFromMsg(msg: PalzMessageEvent): Record<string, unknown> {
|
|
26
|
+
const out: Record<string, unknown> = {};
|
|
27
|
+
for (const [k, v] of Object.entries(msg)) {
|
|
28
|
+
if (PASSTHROUGH_EXCLUDE.has(k)) continue;
|
|
29
|
+
if (v === undefined) continue;
|
|
30
|
+
out[k] = v;
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
// ============ 文本提取工具 ============
|
|
@@ -387,9 +389,9 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
|
|
|
387
389
|
const plainText = extractPlainText(msg.content).trim();
|
|
388
390
|
const useStream = msg.stream === true;
|
|
389
391
|
const senderName = msg.sender_name || msg.sender_id;
|
|
390
|
-
const groupId =
|
|
392
|
+
const groupId = isGroup ? msg.conversation_id : undefined;
|
|
391
393
|
if (isGroup) {
|
|
392
|
-
log(`${tag}: [group_id] resolved=${groupId ?? "(none)"}
|
|
394
|
+
log(`${tag}: [group_id] resolved=${groupId ?? "(none)"} conv=${msg.conversation_id}`);
|
|
393
395
|
}
|
|
394
396
|
|
|
395
397
|
// 群聊:peerId = chat:conversation_id(整群共享 session,与 palzTo 格式一致)
|
|
@@ -458,9 +460,9 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
|
|
|
458
460
|
span?.addEvent(step6aOutput);
|
|
459
461
|
|
|
460
462
|
// STEP 6b: 构建 inbound context
|
|
461
|
-
const palzFrom = `palz:${msg.sender_id}`;
|
|
462
|
-
//
|
|
463
|
-
const palzTo =
|
|
463
|
+
const palzFrom = `palz:${msg.sender_id}:${msg.conversation_id}`;
|
|
464
|
+
// 新格式: {conversationType}:{senderId}:{lobsterId}:{conversationId}
|
|
465
|
+
const palzTo = `${msg.conversation_type || "direct"}:${msg.sender_id}:${msg.lobster_id || ""}:${msg.conversation_id}`;
|
|
464
466
|
|
|
465
467
|
// 命令授权:DM 默认允许,群聊也默认允许(可后续扩展 allowlist)
|
|
466
468
|
const wasMentioned = isGroup ? (msg.mentioned_bot === true) : true;
|
|
@@ -530,6 +532,7 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
|
|
|
530
532
|
`conversation_type: ${msg.conversation_type || "direct"}`,
|
|
531
533
|
`to: ${palzTo}`,
|
|
532
534
|
`mentioned_bot: ${wasMentioned}`,
|
|
535
|
+
`lobster_id: ${msg.lobster_id || ""}`,
|
|
533
536
|
];
|
|
534
537
|
if (groupId) {
|
|
535
538
|
untrustedContext.push(`group_id: ${groupId}`);
|
|
@@ -701,6 +704,7 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
|
|
|
701
704
|
mediaLocalRoots: resolveMediaLocalRoots(effectiveAgentId),
|
|
702
705
|
showProcess,
|
|
703
706
|
sessionKey: route.sessionKey,
|
|
707
|
+
passthrough: buildPassthroughFromMsg(msg),
|
|
704
708
|
});
|
|
705
709
|
|
|
706
710
|
// STEP 6d: 分发消息给 AI
|
package/src/channel.ts
CHANGED
|
@@ -66,13 +66,13 @@ export const palzPlugin = {
|
|
|
66
66
|
normalizeTarget: (raw: string) => normalizePalzTarget(raw),
|
|
67
67
|
targetResolver: {
|
|
68
68
|
looksLikeId: looksLikePalzId,
|
|
69
|
-
hint: "<senderId>:<conversationId>",
|
|
69
|
+
hint: "<conversationType>:<senderId>:<lobsterId>:<conversationId>",
|
|
70
70
|
},
|
|
71
71
|
},
|
|
72
72
|
|
|
73
73
|
agentPrompt: {
|
|
74
74
|
messageToolHints: () => [
|
|
75
|
-
"- Palz targeting: DO NOT set `target` — always omit it so the system auto-infers the correct conversation. Never use sender_id or conversation_id alone as target. If you must specify an explicit target, the only valid format is `<senderId>:<conversationId>`.",
|
|
75
|
+
"- Palz targeting: DO NOT set `target` — always omit it so the system auto-infers the correct conversation. Never use sender_id or conversation_id alone as target. If you must specify an explicit target, the only valid format is `<conversationType>:<senderId>:<lobsterId>:<conversationId>`.",
|
|
76
76
|
],
|
|
77
77
|
},
|
|
78
78
|
|
package/src/outbound.ts
CHANGED
|
@@ -17,11 +17,11 @@ export const palzOutbound = {
|
|
|
17
17
|
resolveTarget: (params: { to?: string; mode?: string }) => {
|
|
18
18
|
const to = params.to?.trim();
|
|
19
19
|
if (!to) {
|
|
20
|
-
return { ok: false as const, error: new Error("Palz target is required. Format: <senderId>:<conversationId>
|
|
20
|
+
return { ok: false as const, error: new Error("Palz target is required. Format: <conversationType>:<senderId>:<lobsterId>:<conversationId>") };
|
|
21
21
|
}
|
|
22
22
|
// Must contain ":" — bare sender_id or bare conversation_id is invalid
|
|
23
23
|
if (!to.includes(":")) {
|
|
24
|
-
return { ok: false as const, error: new Error(`Invalid Palz target "${to}": must be <senderId>:<conversationId
|
|
24
|
+
return { ok: false as const, error: new Error(`Invalid Palz target "${to}": must be <conversationType>:<senderId>:<lobsterId>:<conversationId>. A bare ID without ":" is not a valid target.`) };
|
|
25
25
|
}
|
|
26
26
|
return { ok: true as const, to };
|
|
27
27
|
},
|
|
@@ -32,8 +32,8 @@ export const palzOutbound = {
|
|
|
32
32
|
log(`palz-outbound: [sendText] 输入: to="${to}" accountId="${accountId}" textLen=${text?.length || 0} text="${(text || "").slice(0, 120)}"`);
|
|
33
33
|
|
|
34
34
|
const account = resolvePalzAccount({ cfg, accountId });
|
|
35
|
-
const { senderId, conversationId, conversationType } = parsePalzTarget(to);
|
|
36
|
-
log(`palz-outbound: [sendText] 解析: senderId="${senderId}" conversationId="${conversationId}" conversationType="${conversationType}" botId=${account.config.botId}`);
|
|
35
|
+
const { senderId, lobsterId, conversationId, conversationType } = parsePalzTarget(to);
|
|
36
|
+
log(`palz-outbound: [sendText] 解析: senderId="${senderId}" lobsterId="${lobsterId}" conversationId="${conversationId}" conversationType="${conversationType}" botId=${account.config.botId}`);
|
|
37
37
|
|
|
38
38
|
const result = await sendToPalzIM({
|
|
39
39
|
config: account.config,
|
|
@@ -41,6 +41,7 @@ export const palzOutbound = {
|
|
|
41
41
|
content: text,
|
|
42
42
|
senderId,
|
|
43
43
|
conversationType,
|
|
44
|
+
lobsterId,
|
|
44
45
|
});
|
|
45
46
|
|
|
46
47
|
const output = { channel: "palz-connector", messageId: Date.now().toString() };
|
|
@@ -53,9 +54,9 @@ export const palzOutbound = {
|
|
|
53
54
|
const log = typeof ctx.log === "function" ? ctx.log : console.log;
|
|
54
55
|
|
|
55
56
|
const account = resolvePalzAccount({ cfg, accountId });
|
|
56
|
-
const { senderId, conversationId, conversationType } = parsePalzTarget(to);
|
|
57
|
+
const { senderId, lobsterId, conversationId, conversationType } = parsePalzTarget(to);
|
|
57
58
|
log(`palz-outbound: [sendMedia] 输入: to="${to}" accountId="${accountId}" textLen=${text?.length || 0} mediaUrl="${(mediaUrl || "").slice(0, 200)}"`);
|
|
58
|
-
log(`palz-outbound: [sendMedia] 解析: senderId="${senderId}" conversationId="${conversationId}" conversationType="${conversationType}" botId=${account.config.botId}`);
|
|
59
|
+
log(`palz-outbound: [sendMedia] 解析: senderId="${senderId}" lobsterId="${lobsterId}" conversationId="${conversationId}" conversationType="${conversationType}" botId=${account.config.botId}`);
|
|
59
60
|
|
|
60
61
|
const contentParts: ContentPart[] = [];
|
|
61
62
|
|
|
@@ -87,6 +88,7 @@ export const palzOutbound = {
|
|
|
87
88
|
content,
|
|
88
89
|
senderId,
|
|
89
90
|
conversationType,
|
|
91
|
+
lobsterId,
|
|
90
92
|
});
|
|
91
93
|
|
|
92
94
|
const output = { channel: "palz-connector", messageId: Date.now().toString() };
|
package/src/reply-dispatcher.ts
CHANGED
|
@@ -47,6 +47,8 @@ export interface CreatePalzReplyDispatcherParams {
|
|
|
47
47
|
mediaLocalRoots?: readonly string[];
|
|
48
48
|
showProcess?: boolean;
|
|
49
49
|
sessionKey?: string;
|
|
50
|
+
/** IM 原始消息透传字段(除 event/content/timestamp) */
|
|
51
|
+
passthrough?: Record<string, unknown>;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export function createPalzReplyDispatcher(params: CreatePalzReplyDispatcherParams) {
|
|
@@ -65,6 +67,7 @@ export function createPalzReplyDispatcher(params: CreatePalzReplyDispatcherParam
|
|
|
65
67
|
groupId,
|
|
66
68
|
mediaLocalRoots,
|
|
67
69
|
showProcess,
|
|
70
|
+
passthrough,
|
|
68
71
|
} = params;
|
|
69
72
|
|
|
70
73
|
const log = typeof runtime?.log === "function" ? runtime.log : console.log;
|
|
@@ -115,6 +118,7 @@ export function createPalzReplyDispatcher(params: CreatePalzReplyDispatcherParam
|
|
|
115
118
|
groupId,
|
|
116
119
|
palzMsgType,
|
|
117
120
|
toolContent,
|
|
121
|
+
passthrough,
|
|
118
122
|
});
|
|
119
123
|
log(`${tag}: [DISPATCHER←sendToIM] 输出: ${JSON.stringify(result)}`);
|
|
120
124
|
return result;
|
package/src/send.ts
CHANGED
|
@@ -29,12 +29,13 @@ export async function sendToPalzIM(params: SendToIMParams): Promise<any> {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
async function _sendToPalzIMInner(params: SendToIMParams): Promise<any> {
|
|
32
|
-
const { config, conversationId, content, conversationType, msgId, senderId, stream, msgType, groupId, palzMsgType, toolContent } = params;
|
|
32
|
+
const { config, conversationId, content, conversationType, msgId, senderId, stream, msgType, groupId, lobsterId, palzMsgType, toolContent, passthrough } = params;
|
|
33
33
|
const url = `${config.apiBaseUrl}/bot/send`;
|
|
34
34
|
const resolvedMsgId = msgId || nextMsgId();
|
|
35
35
|
const span = trace.getActiveSpan();
|
|
36
36
|
|
|
37
37
|
const reqBody: Record<string, unknown> = {
|
|
38
|
+
...(passthrough ?? {}),
|
|
38
39
|
bot_id: config.botId,
|
|
39
40
|
conversation_id: conversationId,
|
|
40
41
|
conversation_type: conversationType || "direct",
|
|
@@ -54,6 +55,10 @@ async function _sendToPalzIMInner(params: SendToIMParams): Promise<any> {
|
|
|
54
55
|
reqBody.group_id = groupId;
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
if (lobsterId) {
|
|
59
|
+
reqBody.lobster_id = lobsterId;
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
if (stream) {
|
|
58
63
|
reqBody.stream_id = stream.streamId;
|
|
59
64
|
reqBody.seq = stream.seq;
|
package/src/targets.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Palz Connector 目标地址解析
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 新格式: "{conversationType}:{senderId}:{lobsterId}:{conversationId}"
|
|
5
|
+
* 旧格式(兼容):
|
|
6
|
+
* DM: "{senderId}:{conversationId}"
|
|
7
|
+
* 群聊: "chat:{conversationId}"
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
export function normalizePalzTarget(raw: string): string | undefined {
|
|
@@ -14,22 +17,44 @@ export function looksLikePalzId(raw: string): boolean {
|
|
|
14
17
|
return /^[\p{L}\p{N}\w:._-]+$/u.test(trimmed);
|
|
15
18
|
}
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
* 从 "to" 地址中解析出 senderId 和 conversationId。
|
|
19
|
-
* 格式:
|
|
20
|
-
* DM: "{senderId}:{conversationId}"
|
|
21
|
-
* 群聊: "chat:{conversationId}"(senderId 为空,群消息不需要指定 senderId)
|
|
22
|
-
*/
|
|
23
|
-
export function parsePalzTarget(to: string): {
|
|
20
|
+
export interface ParsedPalzTarget {
|
|
24
21
|
senderId?: string;
|
|
22
|
+
lobsterId?: string;
|
|
25
23
|
conversationId: string;
|
|
26
24
|
conversationType: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 从 "to" 地址中解析出 senderId、lobsterId 和 conversationId。
|
|
29
|
+
*
|
|
30
|
+
* 新格式(4段): "{conversationType}:{senderId}:{lobsterId}:{conversationId}"
|
|
31
|
+
* 示例: "group:6a9e1cfd...:lobster123:group_grp_3b14486f"
|
|
32
|
+
* 示例: "direct:6a9e1cfd...:lobster123:conv_xxx"
|
|
33
|
+
*
|
|
34
|
+
* 旧格式(兼容):
|
|
35
|
+
* 群聊: "chat:{conversationId}"(senderId 为空)
|
|
36
|
+
* DM: "{senderId}:{conversationId}"
|
|
37
|
+
*/
|
|
38
|
+
export function parsePalzTarget(to: string): ParsedPalzTarget {
|
|
39
|
+
const parts = to.split(":");
|
|
40
|
+
|
|
41
|
+
// 新4段格式: conversationType:senderId:lobsterId:conversationId
|
|
42
|
+
// conversationType 为 "group" 或 "direct"
|
|
43
|
+
if (parts.length >= 4 && (parts[0] === "group" || parts[0] === "direct")) {
|
|
44
|
+
return {
|
|
45
|
+
conversationType: parts[0],
|
|
46
|
+
senderId: parts[1] || undefined,
|
|
47
|
+
lobsterId: parts[2] || undefined,
|
|
48
|
+
conversationId: parts.slice(3).join(":"),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 旧格式兼容:chat:xxx → 群聊,senderId 留空
|
|
29
53
|
if (to.startsWith("chat:")) {
|
|
30
54
|
return { conversationId: to.slice(5), conversationType: "group" };
|
|
31
55
|
}
|
|
32
|
-
|
|
56
|
+
|
|
57
|
+
// 旧格式兼容:senderId:conversationId
|
|
33
58
|
if (parts.length >= 2) {
|
|
34
59
|
return {
|
|
35
60
|
senderId: parts[0],
|
|
@@ -37,5 +62,6 @@ export function parsePalzTarget(to: string): {
|
|
|
37
62
|
conversationType: "direct",
|
|
38
63
|
};
|
|
39
64
|
}
|
|
65
|
+
|
|
40
66
|
return { conversationId: to, conversationType: "direct" };
|
|
41
67
|
}
|
package/src/types.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface PalzMessageEvent {
|
|
|
37
37
|
owner_name?: string;
|
|
38
38
|
/** 群组 ID,群聊时 IM 可直接下发;若未提供则从 conversation_id 中解析 */
|
|
39
39
|
group_id?: string;
|
|
40
|
+
/** Lobster ID,标识 agent 身份(IM 通过此字段区分不同 agent) */
|
|
41
|
+
lobster_id?: string;
|
|
40
42
|
/** W3C Trace Context traceparent,由 IM 上游传递 */
|
|
41
43
|
traceparent?: string;
|
|
42
44
|
}
|
|
@@ -92,8 +94,12 @@ export interface SendToIMParams {
|
|
|
92
94
|
msgType?: string;
|
|
93
95
|
/** 群组 ID,群聊时透传 */
|
|
94
96
|
groupId?: string;
|
|
97
|
+
/** Lobster ID,标识 agent 身份 */
|
|
98
|
+
lobsterId?: string;
|
|
95
99
|
/** Palz 自定义消息类型(tool_start / tool_result) */
|
|
96
100
|
palzMsgType?: string;
|
|
97
101
|
/** 工具调用结构化内容 */
|
|
98
102
|
toolContent?: Record<string, unknown>;
|
|
103
|
+
/** IM 原始消息中除 event/content/timestamp 外的字段,原样透传到发送请求体 */
|
|
104
|
+
passthrough?: Record<string, unknown>;
|
|
99
105
|
}
|