palz-connector 1.5.4 → 1.5.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.
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/activity.js +1 -1
- package/src/bot.js +30 -19
- package/src/channel.js +11 -44
- package/src/config.js +14 -19
- package/src/monitor.js +5 -8
- package/src/outbound.js +1 -13
- package/src/reply-dispatcher.js +0 -2
- package/src/send.js +5 -7
- package/src/agent-logger.js +0 -84
- package/src/connection-manager.js +0 -135
- package/src/workspace-scanner.js +0 -97
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/activity.js
CHANGED
|
@@ -30,7 +30,7 @@ export async function reportPalzActivity(params) {
|
|
|
30
30
|
if (config.activityReportEnabled !== true)
|
|
31
31
|
return;
|
|
32
32
|
const baseUrl = config.clawGatewayUrl?.trim();
|
|
33
|
-
const releaseName = (config.botId || "").trim();
|
|
33
|
+
const releaseName = (process.env.botID || config.botId || "").trim();
|
|
34
34
|
if (!baseUrl || !releaseName) {
|
|
35
35
|
const warnKey = `${accountId ?? ""}:${baseUrl ? "hasBaseUrl" : "missingBaseUrl"}:${releaseName ? "hasRelease" : "missingRelease"}`;
|
|
36
36
|
if (!warnedMissingConfig.has(warnKey)) {
|
package/src/bot.js
CHANGED
|
@@ -14,6 +14,7 @@ import { resolvePalzAccount } from "./config.js";
|
|
|
14
14
|
import { tryClaimMessage } from "./dedup.js";
|
|
15
15
|
import { createPalzReplyDispatcher } from "./reply-dispatcher.js";
|
|
16
16
|
import { resolvePalzMediaList, resolveMediaLocalRoots } from "./media.js";
|
|
17
|
+
import { sendToPalzIM } from "./send.js";
|
|
17
18
|
import { tracer, trace, context, SpanStatusCode } from "./tracing.js";
|
|
18
19
|
// ============ 原始消息透传字段 ============
|
|
19
20
|
const PASSTHROUGH_EXCLUDE = new Set(["event", "content", "timestamp"]);
|
|
@@ -209,7 +210,7 @@ export async function handlePalzMessage(params) {
|
|
|
209
210
|
span.setAttribute("msg_id", msg.msg_id);
|
|
210
211
|
span.setAttribute("sender_id", msg.sender_id);
|
|
211
212
|
span.setAttribute("conversation_type", msg.conversation_type);
|
|
212
|
-
span.setAttribute("agent_id",
|
|
213
|
+
span.setAttribute("agent_id", msg.agent_id || "main");
|
|
213
214
|
span.setAttribute("is_group", msg.conversation_type === "group");
|
|
214
215
|
try {
|
|
215
216
|
await _handlePalzMessageInner(params);
|
|
@@ -224,12 +225,12 @@ export async function handlePalzMessage(params) {
|
|
|
224
225
|
});
|
|
225
226
|
}
|
|
226
227
|
async function _handlePalzMessageInner(params) {
|
|
227
|
-
const { cfg, msg, runtime, accountId
|
|
228
|
+
const { cfg, msg, runtime, accountId } = params;
|
|
228
229
|
const log = typeof runtime?.log === "function" ? runtime.log : console.log;
|
|
229
230
|
const error = typeof runtime?.error === "function" ? runtime.error : console.error;
|
|
230
231
|
const tag = `palz[${accountId}]`;
|
|
231
232
|
const isGroup = msg.conversation_type === "group";
|
|
232
|
-
const effectiveAgentId =
|
|
233
|
+
const effectiveAgentId = msg.agent_id || "main";
|
|
233
234
|
const step1Filter = `[STEP 1/6 入站过滤] msg_id=${msg.msg_id} sender=${msg.sender_id} conv=${msg.conversation_id} type=${msg.conversation_type} agent=${effectiveAgentId}`;
|
|
234
235
|
log(`${tag}: ${step1Filter}`);
|
|
235
236
|
const span = trace.getActiveSpan();
|
|
@@ -325,7 +326,7 @@ async function _handlePalzMessageInner(params) {
|
|
|
325
326
|
span?.addEvent(waitLog);
|
|
326
327
|
}
|
|
327
328
|
try {
|
|
328
|
-
await context.with(capturedCtx, () => dispatchPalzMessage({ cfg, msg, runtime, accountId
|
|
329
|
+
await context.with(capturedCtx, () => dispatchPalzMessage({ cfg, msg, runtime, accountId }));
|
|
329
330
|
const doneLog = `[完成] msg_id=${msg.msg_id} 总耗时=${Date.now() - startMs}ms queueWait=${queueWaitMs}ms`;
|
|
330
331
|
log(`${tag}: ${doneLog}`);
|
|
331
332
|
span?.addEvent(doneLog);
|
|
@@ -357,7 +358,7 @@ async function dispatchPalzMessage(params) {
|
|
|
357
358
|
});
|
|
358
359
|
}
|
|
359
360
|
async function _dispatchPalzMessageInner(params) {
|
|
360
|
-
const { cfg, msg, runtime, accountId
|
|
361
|
+
const { cfg, msg, runtime, accountId } = params;
|
|
361
362
|
const log = typeof runtime?.log === "function" ? runtime.log : console.log;
|
|
362
363
|
const tag = `palz[${accountId}]`;
|
|
363
364
|
const core = getPalzRuntime();
|
|
@@ -371,6 +372,27 @@ async function _dispatchPalzMessageInner(params) {
|
|
|
371
372
|
if (isGroup) {
|
|
372
373
|
log(`${tag}: [group_id] resolved=${groupId ?? "(none)"} conv=${msg.conversation_id}`);
|
|
373
374
|
}
|
|
375
|
+
// 立即发送确认消息,让用户知道 agent 已收到并开始处理
|
|
376
|
+
try {
|
|
377
|
+
log(`${tag}: [ACK] 发送确认消息 conv=${msg.conversation_id} sender=${msg.sender_id}`);
|
|
378
|
+
await sendToPalzIM({
|
|
379
|
+
config,
|
|
380
|
+
conversationId: msg.conversation_id,
|
|
381
|
+
content: "收到,正在思考中…",
|
|
382
|
+
conversationType: msg.conversation_type || "direct",
|
|
383
|
+
msgId: msg.msg_id,
|
|
384
|
+
senderId: msg.sender_id,
|
|
385
|
+
msgType: msg.msg_type,
|
|
386
|
+
groupId,
|
|
387
|
+
lobsterId: msg.lobster_id,
|
|
388
|
+
palzMsgType: "thinking",
|
|
389
|
+
passthrough: buildPassthroughFromMsg(msg),
|
|
390
|
+
});
|
|
391
|
+
log(`${tag}: [ACK] 确认消息发送成功`);
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
log(`${tag}: [ACK] 确认消息发送失败(不影响主流程): ${String(err)}`);
|
|
395
|
+
}
|
|
374
396
|
// peerId 使用 4 段格式,确保 cron delivery 推断 to 时包含完整路由信息(含 lobster_id)
|
|
375
397
|
// 格式: {conversationType}:{senderId}:{lobsterId}:{conversationId}
|
|
376
398
|
// 群聊中不同用户共享 session:senderId 固定为 "_"
|
|
@@ -403,12 +425,13 @@ async function _dispatchPalzMessageInner(params) {
|
|
|
403
425
|
accountId,
|
|
404
426
|
peer: { kind: peerKind, id: peerId },
|
|
405
427
|
});
|
|
406
|
-
|
|
428
|
+
// IM 指定 agent_id 时走指定 agent,否则强制走 main
|
|
429
|
+
const effectiveAgentId = msg.agent_id || "main";
|
|
407
430
|
if (route.agentId !== effectiveAgentId) {
|
|
408
431
|
const oldAgentId = route.agentId;
|
|
409
432
|
route.agentId = effectiveAgentId;
|
|
410
433
|
route.sessionKey = route.sessionKey.replace(`agent:${oldAgentId}:`, `agent:${effectiveAgentId}:`);
|
|
411
|
-
const step5Override = `[STEP 5 覆盖] agentId: ${oldAgentId} -> ${effectiveAgentId}, sessionKey=${route.sessionKey}`;
|
|
434
|
+
const step5Override = `[STEP 5 覆盖] agentId: ${oldAgentId} -> ${effectiveAgentId}, sessionKey=${route.sessionKey} (${msg.agent_id ? "IM指定" : "默认main"})`;
|
|
412
435
|
log(`${tag}: ${step5Override}`);
|
|
413
436
|
span?.addEvent(step5Override);
|
|
414
437
|
}
|
|
@@ -735,15 +758,3 @@ async function _dispatchPalzMessageInner(params) {
|
|
|
735
758
|
log(`${tag}: [TRACE] 解析 sessionFile 失败: ${String(err)}`);
|
|
736
759
|
}
|
|
737
760
|
}
|
|
738
|
-
export function cleanupAgentCaches(agentIdToClean) {
|
|
739
|
-
const prefix = `${agentIdToClean}:`;
|
|
740
|
-
for (const key of chatHistories.keys()) {
|
|
741
|
-
if (key.startsWith(prefix))
|
|
742
|
-
chatHistories.delete(key);
|
|
743
|
-
}
|
|
744
|
-
for (const key of reasoningActivated.keys()) {
|
|
745
|
-
if (key.includes(`:agent:${agentIdToClean}:`))
|
|
746
|
-
reasoningActivated.delete(key);
|
|
747
|
-
}
|
|
748
|
-
reasoningScannedAgents.delete(agentIdToClean);
|
|
749
|
-
}
|
package/src/channel.js
CHANGED
|
@@ -4,11 +4,9 @@
|
|
|
4
4
|
* 实现 OpenClaw ChannelPlugin 接口,与飞书插件采用相同的架构模式。
|
|
5
5
|
* 包含 config、messaging、outbound、gateway 等全套适配器。
|
|
6
6
|
*/
|
|
7
|
-
import { listPalzAccountIds, resolvePalzAccount, resolveDefaultPalzAccountId,
|
|
7
|
+
import { listPalzAccountIds, resolvePalzAccount, resolveDefaultPalzAccountId, } from "./config.js";
|
|
8
8
|
import { palzOutbound } from "./outbound.js";
|
|
9
9
|
import { normalizePalzTarget, looksLikePalzId } from "./targets.js";
|
|
10
|
-
import { ConnectionManager } from "./connection-manager.js";
|
|
11
|
-
import { startWorkspaceScanner } from "./workspace-scanner.js";
|
|
12
10
|
export const palzPlugin = {
|
|
13
11
|
id: "palz-connector",
|
|
14
12
|
meta: {
|
|
@@ -71,53 +69,22 @@ export const palzPlugin = {
|
|
|
71
69
|
startAccount: async (ctx) => {
|
|
72
70
|
const log = typeof ctx.runtime?.log === "function" ? ctx.runtime.log : console.log;
|
|
73
71
|
const logInfo = typeof ctx.log?.info === "function" ? ctx.log.info.bind(ctx.log) : log;
|
|
74
|
-
const error = typeof ctx.runtime?.error === "function" ? ctx.runtime.error : console.error;
|
|
75
72
|
logInfo("palz-gateway: [startAccount] 输入: accountId=" + JSON.stringify(ctx.accountId));
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
ctx.abortSignal?.addEventListener("abort", () => resolve(), { once: true });
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
const baseConfig = resolvePalzConfig(ctx.cfg);
|
|
87
|
-
if (!baseConfig.streamUrl || !baseConfig.apiBaseUrl) {
|
|
88
|
-
const errMsg = "Palz manager not configured (missing streamUrl/apiBaseUrl)";
|
|
73
|
+
const { monitorPalzProvider } = await import("./monitor.js");
|
|
74
|
+
const account = resolvePalzAccount({ cfg: ctx.cfg, accountId: ctx.accountId });
|
|
75
|
+
logInfo("palz-gateway: [startAccount] account解析: " + JSON.stringify({ accountId: account.accountId, enabled: account.enabled, configured: account.configured, name: account.name, botId: account.config.botId, streamUrl: account.config.streamUrl }));
|
|
76
|
+
if (!account.configured) {
|
|
77
|
+
const errMsg = "Palz account " + JSON.stringify(ctx.accountId) + " not configured (missing botId/streamUrl/apiBaseUrl)";
|
|
89
78
|
logInfo("palz-gateway: [startAccount] 失败: " + errMsg);
|
|
90
79
|
throw new Error(errMsg);
|
|
91
80
|
}
|
|
92
|
-
logInfo("palz-gateway: [startAccount] 启动
|
|
93
|
-
|
|
81
|
+
logInfo("palz-gateway: [startAccount] 启动WebSocket监听 botId=" + account.config.botId + " streamUrl=" + account.config.streamUrl);
|
|
82
|
+
return monitorPalzProvider({
|
|
94
83
|
cfg: ctx.cfg,
|
|
95
|
-
|
|
84
|
+
config: account.config,
|
|
96
85
|
runtime: ctx.runtime,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
100
|
-
const scanner = startWorkspaceScanner({
|
|
101
|
-
onSnapshot: (releaseNames) => {
|
|
102
|
-
manager.reconcile(releaseNames).catch((err) => {
|
|
103
|
-
error("palz-gateway: reconcile error: " + String(err));
|
|
104
|
-
});
|
|
105
|
-
},
|
|
106
|
-
log: logInfo,
|
|
107
|
-
error,
|
|
108
|
-
});
|
|
109
|
-
return new Promise((resolve) => {
|
|
110
|
-
const onAbort = () => {
|
|
111
|
-
logInfo("palz-gateway: [startAccount] abort received, shutting down manager");
|
|
112
|
-
scanner.stop();
|
|
113
|
-
manager.shutdown();
|
|
114
|
-
resolve();
|
|
115
|
-
};
|
|
116
|
-
if (ctx.abortSignal?.aborted) {
|
|
117
|
-
onAbort();
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
ctx.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
86
|
+
abortSignal: ctx.abortSignal,
|
|
87
|
+
accountId: ctx.accountId,
|
|
121
88
|
});
|
|
122
89
|
},
|
|
123
90
|
},
|
package/src/config.js
CHANGED
|
@@ -12,7 +12,7 @@ import path from "path";
|
|
|
12
12
|
import { fileURLToPath } from "url";
|
|
13
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
14
|
const __dirname = path.dirname(__filename);
|
|
15
|
-
|
|
15
|
+
const DEFAULT_ACCOUNT_ID = "__default__";
|
|
16
16
|
const DEFAULT_SESSION_TIMEOUT = 30 * 60 * 1000;
|
|
17
17
|
const DEFAULT_CONFIG_FILENAME = "palz-connector.config.json";
|
|
18
18
|
let _cachedFileConfig = null;
|
|
@@ -55,11 +55,11 @@ function loadConfigFile() {
|
|
|
55
55
|
_cachedFileConfig = {};
|
|
56
56
|
return _cachedFileConfig;
|
|
57
57
|
}
|
|
58
|
-
export function resolvePalzConfig(_cfg
|
|
58
|
+
export function resolvePalzConfig(_cfg) {
|
|
59
59
|
const file = loadConfigFile();
|
|
60
60
|
const config = {
|
|
61
61
|
enabled: file.enabled !== false,
|
|
62
|
-
botId:
|
|
62
|
+
botId: process.env.botID || "",
|
|
63
63
|
streamUrl: file.streamUrl || "",
|
|
64
64
|
apiBaseUrl: file.apiBaseUrl || "",
|
|
65
65
|
clawGatewayUrl: file.clawGatewayUrl || "",
|
|
@@ -70,46 +70,41 @@ export function resolvePalzConfig(_cfg, botId) {
|
|
|
70
70
|
};
|
|
71
71
|
if (!_configLoggedOnce) {
|
|
72
72
|
_configLoggedOnce = true;
|
|
73
|
-
console.log("palz-config: [resolve] input:
|
|
73
|
+
console.log("palz-config: [resolve] input: env.botID=" + JSON.stringify(process.env.botID) + " configFile=" + JSON.stringify(file));
|
|
74
74
|
console.log("palz-config: [resolve] output: " + JSON.stringify(config));
|
|
75
75
|
}
|
|
76
76
|
return config;
|
|
77
77
|
}
|
|
78
78
|
export function listPalzAccountIds(_cfg) {
|
|
79
79
|
const config = resolvePalzConfig();
|
|
80
|
-
const ids = config.enabled ? [
|
|
80
|
+
const ids = !config.enabled || !config.botId ? [] : [config.botId];
|
|
81
81
|
if (!_configAdapterLoggedOnce) {
|
|
82
82
|
console.log("palz-config: [listAccountIds] output: " + JSON.stringify(ids));
|
|
83
83
|
}
|
|
84
84
|
return ids;
|
|
85
85
|
}
|
|
86
86
|
export function resolveDefaultPalzAccountId(_cfg) {
|
|
87
|
+
const config = resolvePalzConfig();
|
|
88
|
+
const id = config.botId || DEFAULT_ACCOUNT_ID;
|
|
87
89
|
if (!_configAdapterLoggedOnce) {
|
|
88
|
-
console.log("palz-config: [defaultAccountId] output: " + JSON.stringify(
|
|
90
|
+
console.log("palz-config: [defaultAccountId] output: " + JSON.stringify(id));
|
|
89
91
|
}
|
|
90
|
-
return
|
|
92
|
+
return id;
|
|
91
93
|
}
|
|
92
94
|
export function resolvePalzAccount(params) {
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const accountId = isManager ? PALZ_MANAGER_ACCOUNT_ID : rawAccountId;
|
|
96
|
-
const botId = isManager ? "" : rawAccountId;
|
|
97
|
-
const config = resolvePalzConfig(params.cfg, botId);
|
|
95
|
+
const config = resolvePalzConfig();
|
|
96
|
+
const accountId = params.accountId?.trim() || config.botId || DEFAULT_ACCOUNT_ID;
|
|
98
97
|
const result = {
|
|
99
98
|
accountId,
|
|
100
99
|
enabled: config.enabled !== false,
|
|
101
|
-
configured:
|
|
102
|
-
|
|
103
|
-
: Boolean(botId && config.streamUrl && config.apiBaseUrl),
|
|
104
|
-
name: isManager
|
|
105
|
-
? "Palz Connector (manager)"
|
|
106
|
-
: "Palz Connector (" + botId + ")",
|
|
100
|
+
configured: Boolean(config.botId && config.streamUrl && config.apiBaseUrl),
|
|
101
|
+
name: "Palz Connector (" + (config.botId || "unconfigured") + ")",
|
|
107
102
|
config,
|
|
108
103
|
};
|
|
109
104
|
if (!_configAdapterLoggedOnce) {
|
|
110
105
|
_configAdapterLoggedOnce = true;
|
|
111
106
|
console.log("palz-config: [resolveAccount] input: accountId=" + JSON.stringify(params.accountId));
|
|
112
|
-
console.log("palz-config: [resolveAccount] output: " + JSON.stringify({ accountId: result.accountId, enabled: result.enabled, configured: result.configured, name: result.name
|
|
107
|
+
console.log("palz-config: [resolveAccount] output: " + JSON.stringify({ accountId: result.accountId, enabled: result.enabled, configured: result.configured, name: result.name }));
|
|
113
108
|
}
|
|
114
109
|
return result;
|
|
115
110
|
}
|
package/src/monitor.js
CHANGED
|
@@ -9,11 +9,10 @@ import { handlePalzMessage } from "./bot.js";
|
|
|
9
9
|
import { reportPalzActivity } from "./activity.js";
|
|
10
10
|
import { tracer, extractTraceparentContext, SpanStatusCode } from "./tracing.js";
|
|
11
11
|
const WS_CONNECT_TIMEOUT_MS = 15_000;
|
|
12
|
-
const WS_PING_INTERVAL_MS =
|
|
12
|
+
const WS_PING_INTERVAL_MS = 30_000;
|
|
13
13
|
const WS_PONG_TIMEOUT_MS = 70_000;
|
|
14
14
|
export async function monitorPalzProvider(params) {
|
|
15
|
-
const { cfg, config, runtime, abortSignal, accountId
|
|
16
|
-
const effectiveAgentId = agentId || accountId;
|
|
15
|
+
const { cfg, config, runtime, abortSignal, accountId } = params;
|
|
17
16
|
const log = typeof runtime?.log === "function" ? runtime.log : console.log;
|
|
18
17
|
const error = typeof runtime?.error === "function" ? runtime.error : console.error;
|
|
19
18
|
if (!config.botId || !config.streamUrl) {
|
|
@@ -184,6 +183,7 @@ export async function monitorPalzProvider(params) {
|
|
|
184
183
|
try {
|
|
185
184
|
lastPingAt = Date.now();
|
|
186
185
|
ws.ping();
|
|
186
|
+
log(`palz[${accountId}]: [WS_HEARTBEAT] ping sent, last_pong_ago=${timeSinceLastPong}ms`);
|
|
187
187
|
}
|
|
188
188
|
catch (err) {
|
|
189
189
|
error(`palz[${accountId}]: [WS_HEARTBEAT] ping failed: ${err.message ?? err}`);
|
|
@@ -193,11 +193,9 @@ export async function monitorPalzProvider(params) {
|
|
|
193
193
|
});
|
|
194
194
|
ws.on("pong", () => {
|
|
195
195
|
const now = Date.now();
|
|
196
|
-
const rttMs = lastPingAt > 0 ? now - lastPingAt :
|
|
196
|
+
const rttMs = lastPingAt > 0 ? now - lastPingAt : null;
|
|
197
197
|
lastPongAt = now;
|
|
198
|
-
|
|
199
|
-
log(`palz[${accountId}]: [WS_HEARTBEAT] slow pong, rtt=${rttMs}ms`);
|
|
200
|
-
}
|
|
198
|
+
log(`palz[${accountId}]: [WS_HEARTBEAT] pong received${rttMs === null ? "" : `, rtt=${rttMs}ms`}`);
|
|
201
199
|
});
|
|
202
200
|
ws.on("message", (data) => {
|
|
203
201
|
const receivedAt = new Date();
|
|
@@ -229,7 +227,6 @@ export async function monitorPalzProvider(params) {
|
|
|
229
227
|
msg,
|
|
230
228
|
runtime,
|
|
231
229
|
accountId,
|
|
232
|
-
agentId: effectiveAgentId,
|
|
233
230
|
}).catch((err) => {
|
|
234
231
|
error(`palz[${accountId}]: [WS_RECV] handlePalzMessage unhandled error: ${err.message ?? err}`);
|
|
235
232
|
});
|
package/src/outbound.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 实现 ChannelOutboundAdapter 接口,供 OpenClaw Runtime 发送主动消息
|
|
5
5
|
* (如定时任务 cron delivery、跨渠道路由等)。
|
|
6
6
|
*/
|
|
7
|
-
import { resolvePalzAccount
|
|
7
|
+
import { resolvePalzAccount } from "./config.js";
|
|
8
8
|
import { sendToPalzIM } from "./send.js";
|
|
9
9
|
import { loadMediaAsOssUrl } from "./media.js";
|
|
10
10
|
import { parsePalzTarget } from "./targets.js";
|
|
@@ -25,11 +25,6 @@ export const palzOutbound = {
|
|
|
25
25
|
const { cfg, to, text, accountId } = ctx;
|
|
26
26
|
const log = typeof ctx.log === "function" ? ctx.log : console.log;
|
|
27
27
|
log(`palz-outbound: [sendText] 输入: to="${to}" accountId="${accountId}" textLen=${text?.length || 0} text="${(text || "").slice(0, 120)}"`);
|
|
28
|
-
if (accountId === PALZ_MANAGER_ACCOUNT_ID) {
|
|
29
|
-
const err = new Error("Cannot send messages with manager account " + PALZ_MANAGER_ACCOUNT_ID);
|
|
30
|
-
log(`palz-outbound: [sendText] 拒绝: ${err.message}`);
|
|
31
|
-
throw err;
|
|
32
|
-
}
|
|
33
28
|
const account = resolvePalzAccount({ cfg, accountId });
|
|
34
29
|
const { senderId, lobsterId, conversationId, conversationType } = parsePalzTarget(to);
|
|
35
30
|
log(`palz-outbound: [sendText] 解析: senderId="${senderId}" lobsterId="${lobsterId}" conversationId="${conversationId}" conversationType="${conversationType}" botId=${account.config.botId}`);
|
|
@@ -42,7 +37,6 @@ export const palzOutbound = {
|
|
|
42
37
|
senderId,
|
|
43
38
|
conversationType,
|
|
44
39
|
lobsterId,
|
|
45
|
-
log,
|
|
46
40
|
});
|
|
47
41
|
const output = { channel: "palz-connector", messageId: Date.now().toString() };
|
|
48
42
|
log(`palz-outbound: [sendText] 输出: ${JSON.stringify(output)} sendResult=${JSON.stringify(result)}`);
|
|
@@ -51,11 +45,6 @@ export const palzOutbound = {
|
|
|
51
45
|
sendMedia: async (ctx) => {
|
|
52
46
|
const { cfg, to, text, mediaUrl, accountId, mediaLocalRoots } = ctx;
|
|
53
47
|
const log = typeof ctx.log === "function" ? ctx.log : console.log;
|
|
54
|
-
if (accountId === PALZ_MANAGER_ACCOUNT_ID) {
|
|
55
|
-
const err = new Error("Cannot send media with manager account " + PALZ_MANAGER_ACCOUNT_ID);
|
|
56
|
-
log(`palz-outbound: [sendMedia] 拒绝: ${err.message}`);
|
|
57
|
-
throw err;
|
|
58
|
-
}
|
|
59
48
|
const account = resolvePalzAccount({ cfg, accountId });
|
|
60
49
|
const { senderId, lobsterId, conversationId, conversationType } = parsePalzTarget(to);
|
|
61
50
|
log(`palz-outbound: [sendMedia] 输入: to="${to}" accountId="${accountId}" textLen=${text?.length || 0} mediaUrl="${(mediaUrl || "").slice(0, 200)}"`);
|
|
@@ -83,7 +72,6 @@ export const palzOutbound = {
|
|
|
83
72
|
senderId,
|
|
84
73
|
conversationType,
|
|
85
74
|
lobsterId,
|
|
86
|
-
log,
|
|
87
75
|
});
|
|
88
76
|
const output = { channel: "palz-connector", messageId: Date.now().toString() };
|
|
89
77
|
log(`palz-outbound: [sendMedia] 输出: ${JSON.stringify(output)} sendResult=${JSON.stringify(result)}`);
|
package/src/reply-dispatcher.js
CHANGED
package/src/send.js
CHANGED
|
@@ -36,9 +36,7 @@ function normalizeContent(content) {
|
|
|
36
36
|
return content;
|
|
37
37
|
}
|
|
38
38
|
async function _sendToPalzIMInner(params) {
|
|
39
|
-
const { config, conversationId, content: rawContent, conversationType, msgId, senderId, stream, msgType, groupId, lobsterId, palzMsgType, toolContent, passthrough
|
|
40
|
-
const log = paramLog ?? console.log;
|
|
41
|
-
const error = paramError ?? console.error;
|
|
39
|
+
const { config, conversationId, content: rawContent, conversationType, msgId, senderId, stream, msgType, groupId, lobsterId, palzMsgType, toolContent, passthrough } = params;
|
|
42
40
|
const content = normalizeContent(rawContent);
|
|
43
41
|
const url = `${config.apiBaseUrl}/bot/send`;
|
|
44
42
|
const resolvedMsgId = msgId || nextMsgId();
|
|
@@ -77,7 +75,7 @@ async function _sendToPalzIMInner(params) {
|
|
|
77
75
|
}
|
|
78
76
|
const reqBodyStr = JSON.stringify(reqBody);
|
|
79
77
|
const reqLog = `[HTTP_REQ] POST ${url} body_length=${reqBodyStr.length}\n request_body=${reqBodyStr}`;
|
|
80
|
-
log(`palz-send: ${reqLog}`);
|
|
78
|
+
console.log(`palz-send: ${reqLog}`);
|
|
81
79
|
span?.addEvent(reqLog);
|
|
82
80
|
// 构建请求 headers,注入 Traceparent
|
|
83
81
|
const headers = { "Content-Type": "application/json" };
|
|
@@ -101,7 +99,7 @@ async function _sendToPalzIMInner(params) {
|
|
|
101
99
|
const elapsedMs = Date.now() - startMs;
|
|
102
100
|
const reason = err?.name === "AbortError" ? `timeout ${SEND_TIMEOUT_MS}ms` : (err?.message ?? String(err));
|
|
103
101
|
const failLog = `[HTTP_RES] ERROR elapsed=${elapsedMs}ms reason=${reason}`;
|
|
104
|
-
error(`palz-send: ${failLog}`);
|
|
102
|
+
console.error(`palz-send: ${failLog}`);
|
|
105
103
|
span?.addEvent(failLog);
|
|
106
104
|
throw err?.name === "AbortError"
|
|
107
105
|
? new Error(`Palz send timeout after ${SEND_TIMEOUT_MS}ms`)
|
|
@@ -119,12 +117,12 @@ async function _sendToPalzIMInner(params) {
|
|
|
119
117
|
catch { }
|
|
120
118
|
if (!response.ok) {
|
|
121
119
|
const failLog = `[HTTP_RES] FAILED status=${response.status} elapsed=${elapsedMs}ms\n response_headers=${JSON.stringify(Object.fromEntries(response.headers.entries()))}\n response_body=${rawText.slice(0, 500)}`;
|
|
122
|
-
error(`palz-send: ${failLog}`);
|
|
120
|
+
console.error(`palz-send: ${failLog}`);
|
|
123
121
|
span?.addEvent(failLog);
|
|
124
122
|
throw new Error(`Palz send failed: ${response.status} ${rawText}`);
|
|
125
123
|
}
|
|
126
124
|
const okLog = `[HTTP_RES] OK status=${response.status} elapsed=${elapsedMs}ms msg_id=${resolvedMsgId}${traceparent ? ` Traceparent=${traceparent}` : ""}\n response_body=${rawText.slice(0, 500)}`;
|
|
127
|
-
log(`palz-send: ${okLog}`);
|
|
125
|
+
console.log(`palz-send: ${okLog}`);
|
|
128
126
|
span?.addEvent(okLog);
|
|
129
127
|
return body;
|
|
130
128
|
}
|
package/src/agent-logger.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import os from "os";
|
|
4
|
-
const DEFAULT_OPENCLAW_DIR = path.join(os.homedir(), ".openclaw");
|
|
5
|
-
const LOG_DIR_NAME = "palz-log";
|
|
6
|
-
function todayStr() {
|
|
7
|
-
const d = new Date();
|
|
8
|
-
const y = d.getFullYear();
|
|
9
|
-
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
10
|
-
const day = String(d.getDate()).padStart(2, "0");
|
|
11
|
-
return `${y}-${m}-${day}`;
|
|
12
|
-
}
|
|
13
|
-
function formatTimestamp() {
|
|
14
|
-
const d = new Date();
|
|
15
|
-
const offset = -d.getTimezoneOffset();
|
|
16
|
-
const sign = offset >= 0 ? "+" : "-";
|
|
17
|
-
const absOff = Math.abs(offset);
|
|
18
|
-
const hh = String(Math.floor(absOff / 60)).padStart(2, "0");
|
|
19
|
-
const mm = String(absOff % 60).padStart(2, "0");
|
|
20
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
21
|
-
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}${sign}${hh}:${mm}`;
|
|
22
|
-
}
|
|
23
|
-
function argsToString(args) {
|
|
24
|
-
return args.map((a) => (typeof a === "string" ? a : String(a))).join(" ");
|
|
25
|
-
}
|
|
26
|
-
export function createAgentLogger(agentId, openclawDir) {
|
|
27
|
-
const baseDir = openclawDir || DEFAULT_OPENCLAW_DIR;
|
|
28
|
-
const logDir = path.join(baseDir, `workspace-${agentId}`, LOG_DIR_NAME);
|
|
29
|
-
let currentDate = "";
|
|
30
|
-
let fd = null;
|
|
31
|
-
function ensureFd() {
|
|
32
|
-
const date = todayStr();
|
|
33
|
-
if (fd !== null && date === currentDate)
|
|
34
|
-
return fd;
|
|
35
|
-
if (fd !== null) {
|
|
36
|
-
try {
|
|
37
|
-
fs.closeSync(fd);
|
|
38
|
-
}
|
|
39
|
-
catch { }
|
|
40
|
-
fd = null;
|
|
41
|
-
}
|
|
42
|
-
try {
|
|
43
|
-
fs.mkdirSync(logDir, { recursive: true });
|
|
44
|
-
const filePath = path.join(logDir, `palz-${date}.log`);
|
|
45
|
-
fd = fs.openSync(filePath, "a");
|
|
46
|
-
currentDate = date;
|
|
47
|
-
return fd;
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
function writeLine(level, args) {
|
|
54
|
-
const fileFd = ensureFd();
|
|
55
|
-
if (fileFd !== null) {
|
|
56
|
-
const line = `${formatTimestamp()} [${level}] ${argsToString(args)}\n`;
|
|
57
|
-
try {
|
|
58
|
-
fs.writeSync(fileFd, line);
|
|
59
|
-
}
|
|
60
|
-
catch { }
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const logger = {
|
|
64
|
-
log: (...args) => {
|
|
65
|
-
console.log(...args);
|
|
66
|
-
writeLine("INFO", args);
|
|
67
|
-
},
|
|
68
|
-
error: (...args) => {
|
|
69
|
-
console.error(...args);
|
|
70
|
-
writeLine("ERROR", args);
|
|
71
|
-
},
|
|
72
|
-
close: () => {
|
|
73
|
-
if (fd !== null) {
|
|
74
|
-
try {
|
|
75
|
-
fs.closeSync(fd);
|
|
76
|
-
}
|
|
77
|
-
catch { }
|
|
78
|
-
fd = null;
|
|
79
|
-
currentDate = "";
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
return logger;
|
|
84
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { monitorPalzProvider } from "./monitor.js";
|
|
2
|
-
import { cleanupAgentCaches } from "./bot.js";
|
|
3
|
-
import { createAgentLogger } from "./agent-logger.js";
|
|
4
|
-
const BATCH_SIZE = 10;
|
|
5
|
-
const BATCH_DELAY_MS = 500;
|
|
6
|
-
function sleep(ms) {
|
|
7
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
|
-
}
|
|
9
|
-
export class ConnectionManager {
|
|
10
|
-
connections = new Map();
|
|
11
|
-
cfg;
|
|
12
|
-
baseConfig;
|
|
13
|
-
runtime;
|
|
14
|
-
log;
|
|
15
|
-
error;
|
|
16
|
-
reconciling = false;
|
|
17
|
-
shutdownCalled = false;
|
|
18
|
-
constructor(params) {
|
|
19
|
-
this.cfg = params.cfg;
|
|
20
|
-
this.baseConfig = params.baseConfig;
|
|
21
|
-
this.runtime = params.runtime;
|
|
22
|
-
this.log = params.log ?? console.log;
|
|
23
|
-
this.error = params.error ?? console.error;
|
|
24
|
-
}
|
|
25
|
-
async reconcile(desiredReleaseNames) {
|
|
26
|
-
if (this.shutdownCalled)
|
|
27
|
-
return;
|
|
28
|
-
if (this.reconciling) {
|
|
29
|
-
this.log("connection-manager: reconcile skipped (already in progress)");
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
this.reconciling = true;
|
|
33
|
-
try {
|
|
34
|
-
const desired = new Set(desiredReleaseNames);
|
|
35
|
-
const current = new Set(this.connections.keys());
|
|
36
|
-
const toRemove = [...current].filter((name) => !desired.has(name));
|
|
37
|
-
const toAdd = [...desired].filter((name) => !current.has(name));
|
|
38
|
-
for (const name of toRemove) {
|
|
39
|
-
this.removeAgent(name);
|
|
40
|
-
}
|
|
41
|
-
for (let i = 0; i < toAdd.length; i++) {
|
|
42
|
-
if (this.shutdownCalled)
|
|
43
|
-
break;
|
|
44
|
-
this.addAgent(toAdd[i]);
|
|
45
|
-
if (i > 0 && i % BATCH_SIZE === 0 && i < toAdd.length - 1) {
|
|
46
|
-
this.log(`connection-manager: batch pause after ${i} connections`);
|
|
47
|
-
await sleep(BATCH_DELAY_MS);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (toAdd.length > 0 || toRemove.length > 0) {
|
|
51
|
-
this.log(`connection-manager: reconcile done, added=${toAdd.length} removed=${toRemove.length} active=${this.connections.size}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
catch (err) {
|
|
55
|
-
this.error(`connection-manager: reconcile error: ${err}`);
|
|
56
|
-
}
|
|
57
|
-
finally {
|
|
58
|
-
this.reconciling = false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
addAgent(releasename) {
|
|
62
|
-
if (this.connections.has(releasename))
|
|
63
|
-
return;
|
|
64
|
-
if (this.shutdownCalled)
|
|
65
|
-
return;
|
|
66
|
-
const abortController = new AbortController();
|
|
67
|
-
const config = { ...this.baseConfig, botId: releasename };
|
|
68
|
-
const logger = createAgentLogger(releasename);
|
|
69
|
-
const agentRuntime = { ...this.runtime, log: logger.log, error: logger.error };
|
|
70
|
-
this.log(`connection-manager: adding agent "${releasename}"`);
|
|
71
|
-
const promise = monitorPalzProvider({
|
|
72
|
-
cfg: this.cfg,
|
|
73
|
-
config,
|
|
74
|
-
runtime: agentRuntime,
|
|
75
|
-
abortSignal: abortController.signal,
|
|
76
|
-
accountId: releasename,
|
|
77
|
-
agentId: releasename,
|
|
78
|
-
});
|
|
79
|
-
const entry = {
|
|
80
|
-
releasename,
|
|
81
|
-
abortController,
|
|
82
|
-
promise,
|
|
83
|
-
startedAt: Date.now(),
|
|
84
|
-
logger,
|
|
85
|
-
};
|
|
86
|
-
this.connections.set(releasename, entry);
|
|
87
|
-
promise
|
|
88
|
-
.catch((err) => {
|
|
89
|
-
this.error(`connection-manager: agent "${releasename}" exited with error: ${err}`);
|
|
90
|
-
})
|
|
91
|
-
.finally(() => {
|
|
92
|
-
if (this.connections.get(releasename) === entry) {
|
|
93
|
-
this.connections.delete(releasename);
|
|
94
|
-
this.log(`connection-manager: agent "${releasename}" cleaned up from map`);
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
removeAgent(releasename) {
|
|
99
|
-
const entry = this.connections.get(releasename);
|
|
100
|
-
if (!entry)
|
|
101
|
-
return;
|
|
102
|
-
this.log(`connection-manager: removing agent "${releasename}"`);
|
|
103
|
-
this.connections.delete(releasename);
|
|
104
|
-
entry.abortController.abort();
|
|
105
|
-
entry.logger.close();
|
|
106
|
-
try {
|
|
107
|
-
cleanupAgentCaches(releasename);
|
|
108
|
-
}
|
|
109
|
-
catch (err) {
|
|
110
|
-
this.error(`connection-manager: cache cleanup error for "${releasename}": ${err}`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
getActiveAgents() {
|
|
114
|
-
return [...this.connections.keys()].sort();
|
|
115
|
-
}
|
|
116
|
-
shutdown() {
|
|
117
|
-
if (this.shutdownCalled)
|
|
118
|
-
return;
|
|
119
|
-
this.shutdownCalled = true;
|
|
120
|
-
this.log(`connection-manager: shutting down ${this.connections.size} connection(s)`);
|
|
121
|
-
for (const [name, entry] of this.connections) {
|
|
122
|
-
try {
|
|
123
|
-
entry.abortController.abort();
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
this.error(`connection-manager: abort error for "${name}": ${err}`);
|
|
127
|
-
}
|
|
128
|
-
try {
|
|
129
|
-
entry.logger.close();
|
|
130
|
-
}
|
|
131
|
-
catch { }
|
|
132
|
-
}
|
|
133
|
-
this.connections.clear();
|
|
134
|
-
}
|
|
135
|
-
}
|
package/src/workspace-scanner.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import os from "os";
|
|
4
|
-
const DEFAULT_OPENCLAW_DIR = path.join(os.homedir(), ".openclaw");
|
|
5
|
-
const WORKSPACE_PREFIX = "workspace-";
|
|
6
|
-
const DEFAULT_SCAN_INTERVAL_MS = 5000;
|
|
7
|
-
const VALID_RELEASENAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
8
|
-
export function scanWorkspaceReleaseNames(openclawDir) {
|
|
9
|
-
const dir = openclawDir || DEFAULT_OPENCLAW_DIR;
|
|
10
|
-
let entries;
|
|
11
|
-
try {
|
|
12
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return [];
|
|
16
|
-
}
|
|
17
|
-
const releaseNames = [];
|
|
18
|
-
for (const entry of entries) {
|
|
19
|
-
if (!entry.isDirectory()) {
|
|
20
|
-
if (entry.isSymbolicLink()) {
|
|
21
|
-
try {
|
|
22
|
-
const fullPath = path.join(dir, entry.name);
|
|
23
|
-
const stat = fs.statSync(fullPath);
|
|
24
|
-
if (!stat.isDirectory())
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (!entry.name.startsWith(WORKSPACE_PREFIX))
|
|
36
|
-
continue;
|
|
37
|
-
const releasename = entry.name.slice(WORKSPACE_PREFIX.length);
|
|
38
|
-
if (!releasename)
|
|
39
|
-
continue;
|
|
40
|
-
if (!VALID_RELEASENAME_RE.test(releasename))
|
|
41
|
-
continue;
|
|
42
|
-
releaseNames.push(releasename);
|
|
43
|
-
}
|
|
44
|
-
return releaseNames.sort();
|
|
45
|
-
}
|
|
46
|
-
export function startWorkspaceScanner(opts) {
|
|
47
|
-
const dir = opts.openclawDir || DEFAULT_OPENCLAW_DIR;
|
|
48
|
-
const intervalMs = opts.intervalMs ?? DEFAULT_SCAN_INTERVAL_MS;
|
|
49
|
-
const log = opts.log ?? console.log;
|
|
50
|
-
const error = opts.error ?? console.error;
|
|
51
|
-
let stopped = false;
|
|
52
|
-
let scanning = false;
|
|
53
|
-
let timer = null;
|
|
54
|
-
function doScan() {
|
|
55
|
-
if (stopped)
|
|
56
|
-
return [];
|
|
57
|
-
if (scanning)
|
|
58
|
-
return [];
|
|
59
|
-
scanning = true;
|
|
60
|
-
try {
|
|
61
|
-
const names = scanWorkspaceReleaseNames(dir);
|
|
62
|
-
try {
|
|
63
|
-
opts.onSnapshot(names);
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
error(`workspace-scanner: onSnapshot error: ${err}`);
|
|
67
|
-
}
|
|
68
|
-
return names;
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
error(`workspace-scanner: scan error: ${err}`);
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
finally {
|
|
75
|
-
scanning = false;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
log(`workspace-scanner: starting, dir=${dir} interval=${intervalMs}ms`);
|
|
79
|
-
const initialNames = doScan();
|
|
80
|
-
log(`workspace-scanner: initial scan found ${initialNames.length} workspace(s): [${initialNames.join(", ")}]`);
|
|
81
|
-
timer = setInterval(doScan, intervalMs);
|
|
82
|
-
return {
|
|
83
|
-
stop() {
|
|
84
|
-
if (stopped)
|
|
85
|
-
return;
|
|
86
|
-
stopped = true;
|
|
87
|
-
if (timer) {
|
|
88
|
-
clearInterval(timer);
|
|
89
|
-
timer = null;
|
|
90
|
-
}
|
|
91
|
-
log("workspace-scanner: stopped");
|
|
92
|
-
},
|
|
93
|
-
scanNow() {
|
|
94
|
-
return doScan();
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
}
|