palz-connector 1.0.2 → 1.0.3

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.0.2",
4
+ "version": "1.0.3",
5
5
  "description": "Palz IM 接入 OpenClaw",
6
6
  "channels": ["palz-connector"],
7
7
  "configSchema": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palz-connector",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Palz IM 接入 OpenClaw",
5
5
  "main": "plugin.ts",
6
6
  "type": "module",
package/plugin.ts CHANGED
@@ -7,9 +7,9 @@
7
7
 
8
8
  import WebSocket from 'ws';
9
9
  import { readFileSync } from 'fs';
10
- import { resolve, dirname, join } from 'path';
10
+ import { resolve, dirname } from 'path';
11
11
  import { fileURLToPath } from 'url';
12
- import { homedir } from 'os';
12
+
13
13
 
14
14
  // ============ 类型定义 ============
15
15
 
@@ -49,23 +49,12 @@ function getPluginDir(): string {
49
49
  }
50
50
 
51
51
  function readGatewayToken(log?: any): string {
52
- try {
53
- const openclawPath = join(homedir(), '.openclaw', 'openclaw.json');
54
- const openclawConfig = JSON.parse(readFileSync(openclawPath, 'utf-8'));
55
- const token = openclawConfig?.gateway?.auth?.token;
56
- if (token) {
57
- log?.info?.('[Palz] gatewayToken 从 ~/.openclaw/openclaw.json 读取');
58
- return token;
59
- }
60
- } catch {
61
- // 文件不存在或解析失败,继续尝试环境变量
52
+ if (process.env.OPENCLAW_GATEWAY_TOKEN) {
53
+ log?.info?.('[Palz] gatewayToken 从环境变量 OPENCLAW_GATEWAY_TOKEN 读取');
54
+ return process.env.OPENCLAW_GATEWAY_TOKEN;
62
55
  }
63
56
 
64
- if (process.env.GATEWAY_TOKEN) {
65
- return process.env.GATEWAY_TOKEN;
66
- }
67
-
68
- log?.warn?.('[Palz] gatewayToken 未配置(~/.openclaw/openclaw.json 和环境变量 GATEWAY_TOKEN 均未设置)');
57
+ log?.warn?.('[Palz] gatewayToken 未配置(环境变量 OPENCLAW_GATEWAY_TOKEN 未设置)');
69
58
  return '';
70
59
  }
71
60
 
@@ -153,36 +142,28 @@ function isNewSessionCommand(text: string): boolean {
153
142
 
154
143
  function getSessionKey(
155
144
  senderId: string,
145
+ conversationId: string,
156
146
  forceNew: boolean,
157
- sessionTimeout: number,
158
147
  log?: any,
159
148
  ): { sessionKey: string; isNew: boolean } {
149
+ const sessionId = `palz:${senderId}:${conversationId}`;
160
150
  const now = Date.now();
161
151
 
162
152
  if (forceNew) {
163
- const sessionId = `palz:${senderId}:${now}`;
164
153
  userSessions.set(senderId, { lastActivity: now, sessionId });
165
- log?.info?.(`[Palz][Session] 用户主动开启新会话: ${senderId}`);
154
+ log?.info?.(`[Palz][Session] 用户主动开启新会话: ${senderId}, conversation=${conversationId}`);
166
155
  return { sessionKey: sessionId, isNew: true };
167
156
  }
168
157
 
169
158
  const existing = userSessions.get(senderId);
170
- if (existing) {
171
- const elapsed = now - existing.lastActivity;
172
- if (elapsed > sessionTimeout) {
173
- const sessionId = `palz:${senderId}:${now}`;
174
- userSessions.set(senderId, { lastActivity: now, sessionId });
175
- log?.info?.(`[Palz][Session] 会话超时(${Math.round(elapsed / 60000)}分钟),自动开启新会话: ${senderId}`);
176
- return { sessionKey: sessionId, isNew: true };
177
- }
159
+ if (existing && existing.sessionId === sessionId) {
178
160
  existing.lastActivity = now;
179
- return { sessionKey: existing.sessionId, isNew: false };
161
+ return { sessionKey: sessionId, isNew: false };
180
162
  }
181
163
 
182
- const sessionId = `palz:${senderId}:${now}`;
183
164
  userSessions.set(senderId, { lastActivity: now, sessionId });
184
- log?.info?.(`[Palz][Session] 新用户首次会话: ${senderId}`);
185
- return { sessionKey: sessionId, isNew: false };
165
+ log?.info?.(`[Palz][Session] 新会话: ${senderId}, conversation=${conversationId}`);
166
+ return { sessionKey: sessionId, isNew: !existing };
186
167
  }
187
168
 
188
169
  // ============ Gateway SSE 客户端 ============
@@ -198,7 +179,18 @@ async function* streamFromGateway(
198
179
  const rt = getRuntime();
199
180
  const gatewayUrl = `http://127.0.0.1:${rt.gateway?.port || 18789}/v1/chat/completions`;
200
181
 
201
- log?.info?.(`[Palz][Gateway] POST ${gatewayUrl}, session=${sessionKey}`);
182
+ const requestBody = {
183
+ model: 'main',
184
+ messages: [{ role: 'user', content: userContent }],
185
+ stream: true,
186
+ user: sessionKey,
187
+ };
188
+
189
+ const contentPreview = typeof userContent === 'string'
190
+ ? userContent.slice(0, 200)
191
+ : JSON.stringify(userContent).slice(0, 200);
192
+ log?.info?.(`[Palz][Gateway] POST ${gatewayUrl}, session=${sessionKey}, content=${contentPreview}`);
193
+ log?.info?.(`[Palz][Gateway] Request body: ${JSON.stringify(requestBody, null, 2)}`);
202
194
 
203
195
  const controller = new AbortController();
204
196
  const timer = setTimeout(() => controller.abort(), GATEWAY_TIMEOUT);
@@ -211,27 +203,28 @@ async function* streamFromGateway(
211
203
  'Content-Type': 'application/json',
212
204
  ...(gatewayAuth ? { Authorization: `Bearer ${gatewayAuth}` } : {}),
213
205
  },
214
- body: JSON.stringify({
215
- model: 'main',
216
- messages: [{ role: 'user', content: userContent }],
217
- stream: true,
218
- user: sessionKey,
219
- }),
206
+ body: JSON.stringify(requestBody),
220
207
  signal: controller.signal,
221
208
  });
222
209
  } catch (err: any) {
223
210
  clearTimeout(timer);
211
+ log?.error?.(`[Palz][Gateway] 请求异常: ${err.message}`);
224
212
  throw err;
225
213
  }
226
214
 
215
+ log?.info?.(`[Palz][Gateway] Response status=${response.status}, headers=${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
216
+
227
217
  if (!response.ok || !response.body) {
228
218
  clearTimeout(timer);
229
- throw new Error(`Gateway error: ${response.status}`);
219
+ const errorBody = await response.text().catch(() => '');
220
+ log?.error?.(`[Palz][Gateway] 请求失败: status=${response.status}, body=${errorBody}`);
221
+ throw new Error(`Gateway error: ${response.status} ${errorBody}`);
230
222
  }
231
223
 
232
224
  const reader = (response.body as any).getReader();
233
225
  const decoder = new TextDecoder();
234
226
  let buffer = '';
227
+ let fullResponse = '';
235
228
 
236
229
  try {
237
230
  while (true) {
@@ -243,16 +236,23 @@ async function* streamFromGateway(
243
236
  for (const line of lines) {
244
237
  if (!line.startsWith('data: ')) continue;
245
238
  const data = line.slice(6).trim();
246
- if (data === '[DONE]') return;
239
+ if (data === '[DONE]') {
240
+ log?.info?.(`[Palz][Gateway] 流式响应完成, 总长度=${fullResponse.length}, 内容=${fullResponse.slice(0, 500)}`);
241
+ return;
242
+ }
247
243
  try {
248
244
  const chunk = JSON.parse(data);
249
245
  const content = chunk.choices?.[0]?.delta?.content;
250
- if (content) yield content;
246
+ if (content) {
247
+ fullResponse += content;
248
+ yield content;
249
+ }
251
250
  } catch {
252
- // skip malformed JSON chunks
251
+ log?.warn?.(`[Palz][Gateway] 解析SSE数据失败: ${data.slice(0, 200)}`);
253
252
  }
254
253
  }
255
254
  }
255
+ log?.info?.(`[Palz][Gateway] 流结束, 总长度=${fullResponse.length}, 内容=${fullResponse.slice(0, 500)}`);
256
256
  } finally {
257
257
  clearTimeout(timer);
258
258
  }
@@ -306,18 +306,22 @@ function handleIMMessage(params: {
306
306
 
307
307
  const senderId = msg.sender_id;
308
308
  const conversationId = msg.conversation_id;
309
- log?.info?.(`[Palz] 收到消息: sender=${senderId}, bot_id=${config.botId}, conv=${conversationId}`);
309
+ const msgSnapshot = Object.fromEntries(
310
+ Object.entries(msg).map(([k, v]) => {
311
+ const s = typeof v === 'string' ? v : JSON.stringify(v);
312
+ return [k, s.length > 300 ? s.slice(0, 300) + '...' : v];
313
+ }),
314
+ );
315
+ log?.info?.(`[Palz] 收到消息: ${JSON.stringify(msgSnapshot)}`);
310
316
 
311
317
  withUserLock(senderId, async () => {
312
- const sessionTimeout = config.sessionTimeout ?? 1800000;
313
-
314
318
  if (isNewSessionCommand(plainText)) {
315
- getSessionKey(senderId, true, sessionTimeout, log);
319
+ getSessionKey(senderId, conversationId, true, log);
316
320
  await sendToIM(config, conversationId, '✨ 已开启新会话', log);
317
321
  return;
318
322
  }
319
323
 
320
- const { sessionKey } = getSessionKey(senderId, false, sessionTimeout, log);
324
+ const { sessionKey } = getSessionKey(senderId, conversationId, false, log);
321
325
  const gatewayAuth = config.gatewayToken || '';
322
326
 
323
327
  let fullResponse = '';