palz-connector 1.3.4 → 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 +26 -18
- 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
|
// ============ 文本提取工具 ============
|
|
@@ -45,8 +47,10 @@ function extractPlainText(content: OpenAIContent): string {
|
|
|
45
47
|
|
|
46
48
|
// ============ reasoning 激活状态缓存(内存) ============
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
const REASONING_ACTIVATE_TTL_MS = 15 * 60 * 1000; // 15 分钟
|
|
51
|
+
|
|
52
|
+
/** 已激活 reasoning stream 的 session key → 激活时间戳(ms),超过 TTL 后重新发送激活指令 */
|
|
53
|
+
const reasoningActivated = new Map<string, number>();
|
|
50
54
|
|
|
51
55
|
/** 已扫描过 session store 的 agent 集合,每个 agent 只扫描一次 */
|
|
52
56
|
const reasoningScannedAgents = new Set<string>();
|
|
@@ -64,7 +68,7 @@ function prefillReasoningActivated(core: any, agentId: string, log: (...args: an
|
|
|
64
68
|
let count = 0;
|
|
65
69
|
for (const [key, entry] of Object.entries(store)) {
|
|
66
70
|
if ((entry as any)?.reasoningLevel === "stream") {
|
|
67
|
-
reasoningActivated.
|
|
71
|
+
reasoningActivated.set(key, Date.now());
|
|
68
72
|
count++;
|
|
69
73
|
}
|
|
70
74
|
}
|
|
@@ -385,9 +389,9 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
|
|
|
385
389
|
const plainText = extractPlainText(msg.content).trim();
|
|
386
390
|
const useStream = msg.stream === true;
|
|
387
391
|
const senderName = msg.sender_name || msg.sender_id;
|
|
388
|
-
const groupId =
|
|
392
|
+
const groupId = isGroup ? msg.conversation_id : undefined;
|
|
389
393
|
if (isGroup) {
|
|
390
|
-
log(`${tag}: [group_id] resolved=${groupId ?? "(none)"}
|
|
394
|
+
log(`${tag}: [group_id] resolved=${groupId ?? "(none)"} conv=${msg.conversation_id}`);
|
|
391
395
|
}
|
|
392
396
|
|
|
393
397
|
// 群聊:peerId = chat:conversation_id(整群共享 session,与 palzTo 格式一致)
|
|
@@ -456,9 +460,9 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
|
|
|
456
460
|
span?.addEvent(step6aOutput);
|
|
457
461
|
|
|
458
462
|
// STEP 6b: 构建 inbound context
|
|
459
|
-
const palzFrom = `palz:${msg.sender_id}`;
|
|
460
|
-
//
|
|
461
|
-
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}`;
|
|
462
466
|
|
|
463
467
|
// 命令授权:DM 默认允许,群聊也默认允许(可后续扩展 allowlist)
|
|
464
468
|
const wasMentioned = isGroup ? (msg.mentioned_bot === true) : true;
|
|
@@ -528,6 +532,7 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
|
|
|
528
532
|
`conversation_type: ${msg.conversation_type || "direct"}`,
|
|
529
533
|
`to: ${palzTo}`,
|
|
530
534
|
`mentioned_bot: ${wasMentioned}`,
|
|
535
|
+
`lobster_id: ${msg.lobster_id || ""}`,
|
|
531
536
|
];
|
|
532
537
|
if (groupId) {
|
|
533
538
|
untrustedContext.push(`group_id: ${groupId}`);
|
|
@@ -600,7 +605,9 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
|
|
|
600
605
|
prefillReasoningActivated(core, route.agentId, log);
|
|
601
606
|
}
|
|
602
607
|
|
|
603
|
-
|
|
608
|
+
const reasoningExpired = !reasoningActivated.has(route.sessionKey)
|
|
609
|
+
|| (Date.now() - reasoningActivated.get(route.sessionKey)! > REASONING_ACTIVATE_TTL_MS);
|
|
610
|
+
if (showProcess && reasoningExpired) {
|
|
604
611
|
try {
|
|
605
612
|
log(`${tag}: [REASONING ACTIVATE] 开始激活 reasoning stream, sessionKey=${route.sessionKey}`);
|
|
606
613
|
// 构造 directive-only 的 context,Body 仅包含 /reasoning stream
|
|
@@ -659,7 +666,7 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
|
|
|
659
666
|
});
|
|
660
667
|
|
|
661
668
|
if (activateSuccess) {
|
|
662
|
-
reasoningActivated.
|
|
669
|
+
reasoningActivated.set(route.sessionKey, Date.now());
|
|
663
670
|
log(`${tag}: [REASONING ACTIVATE] 激活成功, sessionKey=${route.sessionKey}`);
|
|
664
671
|
} else {
|
|
665
672
|
log(`${tag}: [REASONING ACTIVATE] 未收到确认 ack, 下次请求将重试, sessionKey=${route.sessionKey}`);
|
|
@@ -697,6 +704,7 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
|
|
|
697
704
|
mediaLocalRoots: resolveMediaLocalRoots(effectiveAgentId),
|
|
698
705
|
showProcess,
|
|
699
706
|
sessionKey: route.sessionKey,
|
|
707
|
+
passthrough: buildPassthroughFromMsg(msg),
|
|
700
708
|
});
|
|
701
709
|
|
|
702
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
|
}
|