palz-connector 1.3.5 → 1.3.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "palz-connector",
3
3
  "name": "Palz Connector Channel",
4
- "version": "1.3.5",
4
+ "version": "1.3.7",
5
5
  "description": "Palz IM 接入 OpenClaw",
6
6
  "channels": [
7
7
  "palz-connector"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palz-connector",
3
- "version": "1.3.5",
3
+ "version": "1.3.7",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "description": "Palz IM 接入 OpenClaw — 模块化架构,基于 OpenClaw Runtime 消息管道",
@@ -3,6 +3,6 @@
3
3
  "streamUrl": "ws://14.103.148.99:8090/ws/bot",
4
4
  "apiBaseUrl": "http://14.103.148.99:8090/api",
5
5
  "sessionTimeout": 1800000,
6
- "groupContextCache": true,
6
+ "groupContextCache": false,
7
7
  "showProcess": true
8
8
  }
@@ -3,6 +3,6 @@
3
3
  "streamUrl": "wss://claw-server.csaiagent.com/ws/bot",
4
4
  "apiBaseUrl": "https://claw-server.csaiagent.com/api",
5
5
  "sessionTimeout": 1800000,
6
- "groupContextCache": true,
6
+ "groupContextCache": false,
7
7
  "showProcess": true
8
8
  }
@@ -3,6 +3,6 @@
3
3
  "streamUrl": "wss://claw-server.csagentai.com/ws/bot",
4
4
  "apiBaseUrl": "https://claw-server.csagentai.com/api",
5
5
  "sessionTimeout": 1800000,
6
- "groupContextCache": true,
6
+ "groupContextCache": false,
7
7
  "showProcess": true
8
8
  }
@@ -3,6 +3,6 @@
3
3
  "streamUrl": "wss://claw-server.csjkagent.com/ws/bot",
4
4
  "apiBaseUrl": "https://claw-server.csjkagent.com/api",
5
5
  "sessionTimeout": 1800000,
6
- "groupContextCache": true,
6
+ "groupContextCache": false,
7
7
  "showProcess": true
8
8
  }
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
- // ============ group_id 解析 ============
21
+ // ============ 原始消息透传字段 ============
22
22
 
23
- /** conversation_id 中解析 group_id,格式: user_{userID}_lobster_{lobsterID}_group_{groupID}_release_{releaseName} */
24
- const GROUP_ID_RE = /_group_([^_]+)_release_/;
23
+ const PASSTHROUGH_EXCLUDE = new Set(["event", "content", "timestamp"]);
25
24
 
26
- function resolveGroupId(msg: PalzMessageEvent): string | undefined {
27
- if (msg.group_id) return msg.group_id;
28
- if (msg.conversation_type !== "group") return undefined;
29
- const m = GROUP_ID_RE.exec(msg.conversation_id);
30
- return m ? m[1] : undefined;
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,14 +389,18 @@ 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 = resolveGroupId(msg);
392
+ const groupId = isGroup ? msg.conversation_id : undefined;
391
393
  if (isGroup) {
392
- log(`${tag}: [group_id] resolved=${groupId ?? "(none)"} from_msg=${msg.group_id ?? "(none)"} conv=${msg.conversation_id}`);
394
+ log(`${tag}: [group_id] resolved=${groupId ?? "(none)"} conv=${msg.conversation_id}`);
393
395
  }
394
396
 
395
- // 群聊:peerId = chat:conversation_id(整群共享 session,与 palzTo 格式一致)
396
- // DM:peerId = sender_id:conversation_id(每用户每会话独立 session)
397
- const peerId = isGroup ? `chat:${msg.conversation_id}` : `${msg.sender_id}:${msg.conversation_id}`;
397
+ // peerId 使用 4 段格式,确保 cron delivery 推断 to 时包含完整路由信息(含 lobster_id)
398
+ // 格式: {conversationType}:{senderId}:{lobsterId}:{conversationId}
399
+ // 群聊中不同用户共享 session:senderId 固定为 "_"
400
+ const lobsterId = msg.lobster_id || "";
401
+ const peerId = isGroup
402
+ ? `group:_:${lobsterId}:${msg.conversation_id}`
403
+ : `direct:${msg.sender_id}:${lobsterId}:${msg.conversation_id}`;
398
404
 
399
405
  // STEP 4: 解析媒体
400
406
  const mediaCount = Array.isArray(msg.content)
@@ -458,9 +464,9 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
458
464
  span?.addEvent(step6aOutput);
459
465
 
460
466
  // STEP 6b: 构建 inbound context
461
- const palzFrom = `palz:${msg.sender_id}`;
462
- // 群聊:To 指向群,DM:To 指向用户会话
463
- const palzTo = isGroup ? `chat:${msg.conversation_id}` : `${msg.sender_id}:${msg.conversation_id}`;
467
+ const palzFrom = `palz:${msg.sender_id}:${msg.conversation_id}`;
468
+ // 新格式: {conversationType}:{senderId}:{lobsterId}:{conversationId}
469
+ const palzTo = `${msg.conversation_type || "direct"}:${msg.sender_id}:${lobsterId}:${msg.conversation_id}`;
464
470
 
465
471
  // 命令授权:DM 默认允许,群聊也默认允许(可后续扩展 allowlist)
466
472
  const wasMentioned = isGroup ? (msg.mentioned_bot === true) : true;
@@ -530,6 +536,7 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
530
536
  `conversation_type: ${msg.conversation_type || "direct"}`,
531
537
  `to: ${palzTo}`,
532
538
  `mentioned_bot: ${wasMentioned}`,
539
+ `lobster_id: ${msg.lobster_id || ""}`,
533
540
  ];
534
541
  if (groupId) {
535
542
  untrustedContext.push(`group_id: ${groupId}`);
@@ -701,6 +708,7 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
701
708
  mediaLocalRoots: resolveMediaLocalRoots(effectiveAgentId),
702
709
  showProcess,
703
710
  sessionKey: route.sessionKey,
711
+ passthrough: buildPassthroughFromMsg(msg),
704
712
  });
705
713
 
706
714
  // 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
@@ -14,14 +14,14 @@ import type { ContentPart, TextContentPart, OpenAIContent } from "./types.js";
14
14
  export const palzOutbound = {
15
15
  deliveryMode: "direct" as const,
16
16
 
17
- resolveTarget: (params: { to?: string; mode?: string }) => {
17
+ resolveTarget: (params: any) => {
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> or chat:<conversationId>") };
20
+ return { ok: false as const, error: new Error("Palz target is required. Format: <conversationType>:<senderId>:<lobsterId>:<conversationId>") };
21
21
  }
22
- // Must contain ":" — bare sender_id or bare conversation_id is invalid
23
- if (!to.includes(":")) {
24
- return { ok: false as const, error: new Error(`Invalid Palz target "${to}": must be <senderId>:<conversationId> or chat:<conversationId>. A bare ID without ":" is not a valid target.`) };
22
+ const parts = to.split(":");
23
+ if (parts.length < 4 || (parts[0] !== "group" && parts[0] !== "direct")) {
24
+ return { ok: false as const, error: new Error(`Invalid Palz target "${to}": must be <conversationType>:<senderId>:<lobsterId>:<conversationId> where conversationType is "group" or "direct".`) };
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() };
@@ -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
- * 目标格式: "{senderId}:{conversationId}"
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
- // 群聊目标:chat:xxx → senderId 留空
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] && parts[1] !== "_") ? 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
- const parts = to.split(":");
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
  }