@53ai/53ai-openclaw 1.0.6 → 1.0.8

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/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
 
7
+ ## [1.0.7] - 2026-03-19
8
+
9
+ ### Changed
10
+ - 规范配置项命名:将 `websocketUrl` 统一更名为 `WSUrl`。
11
+ - 文档更新:优化了 README.md 中的参数说明。
12
+
7
13
  ## [1.0.6] - 2026-03-19
8
14
 
9
15
  ### Changed
package/README.md CHANGED
@@ -83,9 +83,9 @@ openclaw gateway restart
83
83
  sudo systemctl restart openclaw-gateway
84
84
 
85
85
  # 配置必要参数
86
- openclaw config set channels.53aihub.botId "智能体详情的botId"
87
- openclaw config set channels.53aihub.secret "智能体详情的botId对应的secret"
88
- openclaw config set channels.53aihub.websocketUrl "ws://域名/api/v1/openclaw/ws/connect"
86
+ openclaw config set channels.53aihub.botId "智能体的botId"
87
+ openclaw config set channels.53aihub.secret "智能体对应的secret"
88
+ openclaw config set channels.53aihub.WSUrl "ws:/你的域名/api/v1/openclaw/ws/connect"
89
89
 
90
90
  # 启用通道
91
91
  openclaw config set channels.53aihub.enabled true
@@ -95,9 +95,9 @@ openclaw config set channels.53aihub.enabled true
95
95
 
96
96
  | 参数 | 必填 | 默认值 | 说明 |
97
97
  |------|------|--------|------|
98
- | `botId` | 是 | - | 53AIHub 智能体 ID |
99
- | `secret` | 是 | - | 53AIHub Secret |
100
- | `websocketUrl` | 是 | - | 53AIHub 长连接连接地址 |
98
+ | `botId` | 是 | - | 智能体的ID |
99
+ | `secret` | 是 | - | 智能体的Secret |
100
+ | `WSUrl` | 是 | - | 53AIHub平台的WS地址 |
101
101
  | `token` | 否 | - | secret 的别名,与 secret 二选一 |
102
102
  | `enabled` | 否 | false | 是否启用通道 |
103
103
  | `accessPolicy` | 否 | `open` | 访问策略: `open`=开放所有用户, `allowlist`=仅白名单用户, `pairing`=首次使用需审批, `disabled`=禁用 |
@@ -142,8 +142,8 @@ openclaw plugins list | grep 53aihub
142
142
  # 检查通道配置
143
143
  openclaw config get channels.53aihub
144
144
 
145
- # 检查 Gateway 日志
146
- openclaw gateway logs | grep -i 53aihub
145
+ # 检查 Gateway 日志 (用户级服务)
146
+ journalctl --user -u openclaw-gateway.service -f | grep -i 53aihub
147
147
  ```
148
148
 
149
149
  ## 多模态消息支持
@@ -397,7 +397,7 @@ npm test
397
397
 
398
398
  1. 确认 `openclaw.plugin.json` 存在于插件根目录
399
399
  2. 确认 `dist/` 目录包含编译后的文件
400
- 3. 检查 Gateway 日志: `openclaw gateway logs`
400
+ 3. 检查 Gateway 日志: `journalctl --user -u openclaw-gateway.service -f`
401
401
 
402
402
  ### 通道未生效
403
403
 
@@ -407,7 +407,7 @@ npm test
407
407
 
408
408
  ### WebSocket 连接失败
409
409
 
410
- 1. 检查 `websocketUrl` 格式是否正确
410
+ 1. 检查 `WSUrl` 格式是否正确
411
411
  2. 确认网络可达性
412
412
  3. 检查 `botId` 和 `secret` 是否正确
413
413
 
package/dist/index.cjs.js CHANGED
@@ -334,7 +334,7 @@ function parseMessageContent(msg) {
334
334
  }
335
335
 
336
336
  async function sendReply(params) {
337
- const { wsClient, text, toChatId, replyToMsgId, runtime, finish, streamId, isError, errorCode, errorDetails } = params;
337
+ const { wsClient, text, toChatId, replyToMsgId, runtime, finish, streamId, isError, errorCode, errorDetails, isThinking } = params;
338
338
  const reqId = replyToMsgId || streamId;
339
339
  runtime.log?.(`[53aihub] sendReply START: reqId=${reqId}, finish=${finish}, isError=${isError}, textLen=${text?.length || 0}, wsReadyState=${wsClient.readyState}`);
340
340
  if (wsClient.readyState !== 1) {
@@ -395,7 +395,7 @@ async function sendReply(params) {
395
395
  const payload = {
396
396
  req_id: reqId,
397
397
  action: "chat",
398
- status: finish ? "done" : "streaming",
398
+ status: finish ? "done" : isThinking ? "thinking" : "streaming",
399
399
  data: chunk,
400
400
  };
401
401
  const jsonStr = JSON.stringify(payload);
@@ -973,7 +973,6 @@ async function processMessage(params) {
973
973
  cfg: config,
974
974
  dispatcherOptions: {
975
975
  deliver: async (payload, info) => {
976
- state.accumulatedText += payload.text;
977
976
  runtime.log?.(`[53aihub] deliver: kind=${info.kind}, textLen=${payload.text?.length || 0}, accumulatedLen=${state.accumulatedText.length}, isError=${payload.isError}`);
978
977
  if (payload.isError) {
979
978
  const errorMsg = payload.text || "Unknown error";
@@ -993,6 +992,23 @@ async function processMessage(params) {
993
992
  });
994
993
  return;
995
994
  }
995
+ const isCompaction = payload.text?.startsWith("🧹 Compacting context") ||
996
+ state.accumulatedText.startsWith("🧹 Compacting context");
997
+ if (isCompaction && info.kind !== "final") {
998
+ runtime.log?.(`[53aihub] deliver COMPACTION: text preview=${payload.text?.substring(0, 50)}...`);
999
+ await sendReply({
1000
+ wsClient,
1001
+ text: payload.text || "",
1002
+ toChatId: chatId,
1003
+ replyToMsgId: body.msgId,
1004
+ runtime,
1005
+ finish: false,
1006
+ streamId: state.streamId,
1007
+ isThinking: true,
1008
+ });
1009
+ return;
1010
+ }
1011
+ state.accumulatedText += payload.text;
996
1012
  if (info.kind !== "final") {
997
1013
  runtime.log?.(`[53aihub] deliver STREAMING: accumulatedText preview=${state.accumulatedText.substring(0, 50)}...`);
998
1014
  await sendReply({
@@ -1108,7 +1124,7 @@ async function monitorProvider(options) {
1108
1124
  if (isAborted)
1109
1125
  return;
1110
1126
  // 安全: 不在 URL 中传递敏感信息,仅通过 headers 传递认证
1111
- const wsUrl = account.websocketUrl;
1127
+ const wsUrl = account.WSUrl;
1112
1128
  const botId = account.botId || account.config.botId;
1113
1129
  const secret = account.secret || account.token || account.config.secret || account.config.token;
1114
1130
  // 日志输出时隐藏敏感信息
@@ -1207,7 +1223,7 @@ function resolveAccount(cfg, accountId = pluginSdk.DEFAULT_ACCOUNT_ID) {
1207
1223
  accountId,
1208
1224
  name: config.name ?? "53AIHub",
1209
1225
  enabled: config.enabled !== false,
1210
- websocketUrl: config.websocketUrl || DEFAULT_WS_URL,
1226
+ WSUrl: config.WSUrl || DEFAULT_WS_URL,
1211
1227
  botId: config.botId ?? config.userId ?? "",
1212
1228
  secret: config.secret ?? config.token ?? "",
1213
1229
  token: config.token ?? config.secret ?? "",
@@ -1228,8 +1244,8 @@ function setAccount(cfg, account) {
1228
1244
  allowFrom: account.allowFrom ?? existing.allowFrom,
1229
1245
  accessPolicy: account.accessPolicy ?? existing.accessPolicy,
1230
1246
  sendThinkingMessage: account.sendThinkingMessage ?? existing.sendThinkingMessage,
1231
- ...(account.websocketUrl || existing.websocketUrl
1232
- ? { websocketUrl: account.websocketUrl ?? existing.websocketUrl }
1247
+ ...(account.WSUrl || existing.WSUrl
1248
+ ? { WSUrl: account.WSUrl ?? existing.WSUrl }
1233
1249
  : {}),
1234
1250
  ...(account.name || existing.name
1235
1251
  ? { name: account.name ?? existing.name }
@@ -1267,10 +1283,10 @@ async function promptSecret(prompter, account) {
1267
1283
  validate: (value) => (value?.trim() ? undefined : "必填"),
1268
1284
  })).trim();
1269
1285
  }
1270
- async function promptWebsocketUrl(prompter, account) {
1286
+ async function promptWSUrl(prompter, account) {
1271
1287
  return String(await prompter.text({
1272
1288
  message: "WebSocket URL (例如: ws://localhost:8080/ws)",
1273
- initialValue: account?.websocketUrl ?? "",
1289
+ initialValue: account?.WSUrl ?? "",
1274
1290
  validate: (value) => {
1275
1291
  const trimmed = value?.trim();
1276
1292
  if (!trimmed)
@@ -1340,11 +1356,11 @@ const aiHubOnboardingAdapter = {
1340
1356
  }
1341
1357
  const botId = await promptBotId(prompter, account);
1342
1358
  const secret = await promptSecret(prompter, account);
1343
- const websocketUrl = await promptWebsocketUrl(prompter, account);
1359
+ const WSUrl = await promptWSUrl(prompter, account);
1344
1360
  const cfgWithAccount = setAccount(cfg, {
1345
1361
  botId,
1346
1362
  secret,
1347
- websocketUrl: websocketUrl || undefined,
1363
+ WSUrl: WSUrl || undefined,
1348
1364
  enabled: true,
1349
1365
  accessPolicy: account.config.accessPolicy ?? "open",
1350
1366
  allowFrom: account.config.allowFrom ?? [],
@@ -1423,7 +1439,7 @@ const aiHubPlugin = {
1423
1439
  enabled: account.enabled,
1424
1440
  configured: Boolean(account.botId?.trim() || account.token?.trim()),
1425
1441
  botId: account.botId,
1426
- websocketUrl: account.websocketUrl,
1442
+ WSUrl: account.WSUrl,
1427
1443
  accessPolicy: account.config.accessPolicy ?? "open",
1428
1444
  }),
1429
1445
  resolveAllowFrom: ({ cfg }) => {
@@ -1592,6 +1608,67 @@ const aiHubPlugin = {
1592
1608
  },
1593
1609
  };
1594
1610
 
1611
+ const compactionSessions = new Map();
1612
+ /**
1613
+ * 从 sessionKey 中解析 chatId
1614
+ * sessionKey 格式: agent:{agentId}:{channel}:direct:{chatId}
1615
+ * 例如: agent:main:53aihub:direct:user123 -> user123
1616
+ */
1617
+ function parseChatIdFromSessionKey(sessionKey) {
1618
+ if (!sessionKey)
1619
+ return null;
1620
+ const parts = sessionKey.split(":");
1621
+ // 格式: agent:{agentId}:{channel}:direct:{chatId}
1622
+ // parts: [0]agent, [1]agentId, [2]channel, [3]direct, [4]chatId
1623
+ if (parts.length >= 5 && parts[2] === CHANNEL_ID && parts[3] === "direct") {
1624
+ return parts[4];
1625
+ }
1626
+ return null;
1627
+ }
1628
+ async function handleBeforeCompaction(event, ctx) {
1629
+ const runtime = getRuntime();
1630
+ const logger = runtime.logging.getChildLogger({ component: "53aihub-compaction" });
1631
+ const log = (msg) => logger.info(msg);
1632
+ log(`before_compaction: sessionKey=${ctx.sessionKey}, channelId=${ctx.channelId}, messageCount=${event.messageCount}`);
1633
+ const sessionKey = ctx.sessionKey || "";
1634
+ if (!sessionKey.startsWith(`${CHANNEL_ID}:`) && !sessionKey.includes(`:${CHANNEL_ID}:`)) {
1635
+ log(`Skipping compaction hook for non-53aihub session: ${sessionKey}`);
1636
+ return;
1637
+ }
1638
+ const chatId = parseChatIdFromSessionKey(sessionKey);
1639
+ if (!chatId) {
1640
+ logger.error(`Cannot parse chatId from sessionKey: ${sessionKey}`);
1641
+ return;
1642
+ }
1643
+ compactionSessions.set(sessionKey, {
1644
+ startTime: Date.now(),
1645
+ messageCount: event.messageCount,
1646
+ });
1647
+ log(`Compaction started for chatId=${chatId}`);
1648
+ }
1649
+ async function handleAfterCompaction(event, ctx) {
1650
+ const runtime = getRuntime();
1651
+ const logger = runtime.logging.getChildLogger({ component: "53aihub-compaction" });
1652
+ const log = (msg) => logger.info(msg);
1653
+ log(`after_compaction: sessionKey=${ctx.sessionKey}, channelId=${ctx.channelId}, messageCount=${event.messageCount}, compactedCount=${event.compactedCount}`);
1654
+ const sessionKey = ctx.sessionKey || "";
1655
+ if (!sessionKey.startsWith(`${CHANNEL_ID}:`) && !sessionKey.includes(`:${CHANNEL_ID}:`)) {
1656
+ log(`Skipping compaction hook for non-53aihub session: ${sessionKey}`);
1657
+ return;
1658
+ }
1659
+ const chatId = parseChatIdFromSessionKey(sessionKey);
1660
+ if (!chatId) {
1661
+ logger.error(`Cannot parse chatId from sessionKey: ${sessionKey}`);
1662
+ return;
1663
+ }
1664
+ const sessionInfo = compactionSessions.get(sessionKey);
1665
+ compactionSessions.delete(sessionKey);
1666
+ if (sessionInfo) {
1667
+ const duration = Date.now() - sessionInfo.startTime;
1668
+ log(`Compaction completed: chatId=${chatId}, duration=${duration}ms, messagesBefore=${sessionInfo.messageCount}, messagesAfter=${event.messageCount}, compacted=${event.compactedCount}`);
1669
+ }
1670
+ }
1671
+
1595
1672
  const plugin = {
1596
1673
  id: "53ai-openclaw",
1597
1674
  name: "53AI OpenClaw",
@@ -1600,6 +1677,8 @@ const plugin = {
1600
1677
  register(api) {
1601
1678
  setRuntime(api.runtime);
1602
1679
  api.registerChannel({ plugin: aiHubPlugin });
1680
+ api.on("before_compaction", handleBeforeCompaction);
1681
+ api.on("after_compaction", handleAfterCompaction);
1603
1682
  },
1604
1683
  };
1605
1684