palz-connector 1.3.3 → 1.3.5

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.3",
4
+ "version": "1.3.5",
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.3",
3
+ "version": "1.3.5",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "description": "Palz IM 接入 OpenClaw — 模块化架构,基于 OpenClaw Runtime 消息管道",
@@ -4,5 +4,5 @@
4
4
  "apiBaseUrl": "http://14.103.148.99:8090/api",
5
5
  "sessionTimeout": 1800000,
6
6
  "groupContextCache": true,
7
- "showProcess":true
7
+ "showProcess": true
8
8
  }
@@ -4,5 +4,5 @@
4
4
  "apiBaseUrl": "https://claw-server.csaiagent.com/api",
5
5
  "sessionTimeout": 1800000,
6
6
  "groupContextCache": true,
7
- "showProcess":true
7
+ "showProcess": true
8
8
  }
@@ -4,5 +4,5 @@
4
4
  "apiBaseUrl": "https://claw-server.csagentai.com/api",
5
5
  "sessionTimeout": 1800000,
6
6
  "groupContextCache": true,
7
- "showProcess":true
7
+ "showProcess": true
8
8
  }
@@ -4,5 +4,5 @@
4
4
  "apiBaseUrl": "https://claw-server.csjkagent.com/api",
5
5
  "sessionTimeout": 1800000,
6
6
  "groupContextCache": true,
7
- "showProcess":true
7
+ "showProcess": true
8
8
  }
package/src/bot.ts CHANGED
@@ -45,8 +45,10 @@ function extractPlainText(content: OpenAIContent): string {
45
45
 
46
46
  // ============ reasoning 激活状态缓存(内存) ============
47
47
 
48
- /** 已激活 reasoning stream session key 集合,避免每次请求重复发送 /reasoning stream */
49
- const reasoningActivated = new Set<string>();
48
+ const REASONING_ACTIVATE_TTL_MS = 15 * 60 * 1000; // 15 分钟
49
+
50
+ /** 已激活 reasoning stream 的 session key → 激活时间戳(ms),超过 TTL 后重新发送激活指令 */
51
+ const reasoningActivated = new Map<string, number>();
50
52
 
51
53
  /** 已扫描过 session store 的 agent 集合,每个 agent 只扫描一次 */
52
54
  const reasoningScannedAgents = new Set<string>();
@@ -64,7 +66,7 @@ function prefillReasoningActivated(core: any, agentId: string, log: (...args: an
64
66
  let count = 0;
65
67
  for (const [key, entry] of Object.entries(store)) {
66
68
  if ((entry as any)?.reasoningLevel === "stream") {
67
- reasoningActivated.add(key);
69
+ reasoningActivated.set(key, Date.now());
68
70
  count++;
69
71
  }
70
72
  }
@@ -600,7 +602,9 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
600
602
  prefillReasoningActivated(core, route.agentId, log);
601
603
  }
602
604
 
603
- if (showProcess && !reasoningActivated.has(route.sessionKey)) {
605
+ const reasoningExpired = !reasoningActivated.has(route.sessionKey)
606
+ || (Date.now() - reasoningActivated.get(route.sessionKey)! > REASONING_ACTIVATE_TTL_MS);
607
+ if (showProcess && reasoningExpired) {
604
608
  try {
605
609
  log(`${tag}: [REASONING ACTIVATE] 开始激活 reasoning stream, sessionKey=${route.sessionKey}`);
606
610
  // 构造 directive-only 的 context,Body 仅包含 /reasoning stream
@@ -659,7 +663,7 @@ async function _dispatchPalzMessageInner(params: HandlePalzMessageParams): Promi
659
663
  });
660
664
 
661
665
  if (activateSuccess) {
662
- reasoningActivated.add(route.sessionKey);
666
+ reasoningActivated.set(route.sessionKey, Date.now());
663
667
  log(`${tag}: [REASONING ACTIVATE] 激活成功, sessionKey=${route.sessionKey}`);
664
668
  } else {
665
669
  log(`${tag}: [REASONING ACTIVATE] 未收到确认 ack, 下次请求将重试, sessionKey=${route.sessionKey}`);
@@ -72,6 +72,22 @@ export function createPalzReplyDispatcher(params: CreatePalzReplyDispatcherParam
72
72
  const streamState = enableStreaming ? createStreamingState() : null;
73
73
  const tag = `palz[${accountId}]`;
74
74
 
75
+ // 会话级串行队列:保证同一会话内 IM 消息按入队顺序到达
76
+ // (不同 dispatcher 实例对应不同会话,天然隔离)
77
+ let imSendQueue: Promise<unknown> = Promise.resolve();
78
+ const enqueueIMSend = (
79
+ content: OpenAIContent,
80
+ streamOpts: StreamChunkOpts | undefined,
81
+ palzMsgType: string | undefined,
82
+ label: string,
83
+ ): Promise<unknown> => {
84
+ const next = imSendQueue.then(() => sendToIM(content, streamOpts, palzMsgType)).catch((err: any) => {
85
+ error(`${tag}: [IM_QUEUE] ${label} 发送失败: ${err?.message ?? String(err)}`);
86
+ });
87
+ imSendQueue = next;
88
+ return next;
89
+ };
90
+
75
91
  log(
76
92
  `${tag}: [DISPATCHER 创建] conv=${conversationId} sender=${senderId} streaming=${enableStreaming}${streamState ? ` streamId=${streamState.streamId}` : ""}`,
77
93
  );
@@ -131,9 +147,7 @@ export function createPalzReplyDispatcher(params: CreatePalzReplyDispatcherParam
131
147
  const display = resolveToolDisplay(toolName, data.args);
132
148
  const text = formatToolStartText(toolName, data.args);
133
149
  log(`${tag}: [TOOL_EVENTS] tool_start name=${toolName} detail=${display.detail ?? "(none)"}`);
134
- sendToIM(text, undefined, "tool_start").catch((err: any) => {
135
- error(`${tag}: [TOOL_EVENTS] tool_start 发送失败: ${err.message}`);
136
- });
150
+ enqueueIMSend(text, undefined, "tool_start", "tool_start");
137
151
  }
138
152
 
139
153
  if (phase === "result") {
@@ -142,9 +156,7 @@ export function createPalzReplyDispatcher(params: CreatePalzReplyDispatcherParam
142
156
  const text = formatToolResultText(toolName, data.result, isError, meta);
143
157
  const resultSummary = isError ? "Error" : formatResultSummary(data.result);
144
158
  log(`${tag}: [TOOL_EVENTS] tool_result name=${toolName} is_error=${isError} summary=${resultSummary}`);
145
- sendToIM(text, undefined, "tool_result").catch((err: any) => {
146
- error(`${tag}: [TOOL_EVENTS] tool_result 发送失败: ${err.message}`);
147
- });
159
+ enqueueIMSend(text, undefined, "tool_result", "tool_result");
148
160
  }
149
161
  }
150
162
  });
@@ -201,18 +213,24 @@ export function createPalzReplyDispatcher(params: CreatePalzReplyDispatcherParam
201
213
 
202
214
  if (streamState && kind === "final" && typeof content === "string") {
203
215
  const delta = content.slice(streamState.lastSentLength);
216
+ const seq = streamState.seq++;
204
217
  log(
205
- `${tag}: [DELIVER 流式最终] seq=${streamState.seq} totalLen=${content.length} deltaLen=${delta.length} delta="${delta.slice(0, 120)}"`,
218
+ `${tag}: [DELIVER 流式最终] seq=${seq} totalLen=${content.length} deltaLen=${delta.length} delta="${delta.slice(0, 120)}"`,
219
+ );
220
+ await enqueueIMSend(
221
+ content,
222
+ {
223
+ streamId: streamState.streamId,
224
+ seq,
225
+ isFinal: true,
226
+ delta,
227
+ },
228
+ undefined,
229
+ `final seq=${seq}`,
206
230
  );
207
- await sendToIM(content, {
208
- streamId: streamState.streamId,
209
- seq: streamState.seq++,
210
- isFinal: true,
211
- delta,
212
- });
213
231
  } else {
214
232
  log(`${tag}: [DELIVER 非流式] contentType=${typeof content === "string" ? "string" : "array"}`);
215
- await sendToIM(content);
233
+ await enqueueIMSend(content, undefined, undefined, "deliver");
216
234
  }
217
235
 
218
236
  log(`${tag}: [DELIVER 完成] kind=${kind}`);
@@ -260,14 +278,17 @@ export function createPalzReplyDispatcher(params: CreatePalzReplyDispatcherParam
260
278
  `${tag}: [PARTIAL 输出] seq=${seq} totalLen=${text.length} deltaLen=${delta.length} delta="${delta.slice(0, 120)}" elapsed=${elapsed}ms`,
261
279
  );
262
280
 
263
- sendToIM(text, {
264
- streamId: streamState.streamId,
265
- seq,
266
- isFinal: false,
267
- delta,
268
- }).catch((err: any) => {
269
- error(`${tag}: [PARTIAL 发送失败] seq=${seq} error=${err.message}`);
270
- });
281
+ enqueueIMSend(
282
+ text,
283
+ {
284
+ streamId: streamState.streamId,
285
+ seq,
286
+ isFinal: false,
287
+ delta,
288
+ },
289
+ undefined,
290
+ `partial seq=${seq}`,
291
+ );
271
292
  }
272
293
  : undefined,
273
294
  onReasoningStream: showProcess
@@ -287,9 +308,7 @@ export function createPalzReplyDispatcher(params: CreatePalzReplyDispatcherParam
287
308
  text = text.replace(/^Reasoning:\n/, "");
288
309
  text = text.replace(/^_|_$/gm, "");
289
310
  log(`${tag}: [THINKING] 思考完成, len=${text.length}`);
290
- sendToIM(text, undefined, "thinking").catch((err: any) => {
291
- error(`${tag}: [THINKING] 发送失败: ${err.message}`);
292
- });
311
+ enqueueIMSend(text, undefined, "thinking", "thinking");
293
312
  }
294
313
  : undefined,
295
314
  };