@hpplay-lebo/cluster-hub 3.3.1 → 3.4.0

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/docs/GUIDE.md CHANGED
@@ -44,17 +44,21 @@ openclaw plugins list
44
44
 
45
45
  ### 更新插件
46
46
 
47
- 已安装过的用户,拉取最新版本即可:
47
+ 插件自带更新脚本,一行命令即可:
48
48
 
49
49
  ```bash
50
- cd ~/.openclaw/extensions/cluster-hub
51
- git pull
50
+ ~/.openclaw/extensions/cluster-hub/update.sh
51
+ ```
52
+
53
+ 脚本会自动检查 npm 最新版本,有更新则下载替换。更新后需重启 Gateway:
52
54
 
53
- # 重启 Gateway 加载新代码
55
+ ```bash
54
56
  kill -9 $(pgrep -f "openclaw.*gateway")
55
57
  openclaw gateway start
56
58
  ```
57
59
 
60
+ 也可以直接告诉 AI:"**更新 Hub 插件**",它会自动执行更新 + 重启。
61
+
58
62
  ---
59
63
 
60
64
  ## 第二步:创建集群(根节点)
@@ -277,7 +281,7 @@ openclaw gateway start
277
281
 
278
282
  ```bash
279
283
  # 更新插件
280
- cd ~/.openclaw/extensions/cluster-hub && git pull
284
+ ~/.openclaw/extensions/cluster-hub/update.sh
281
285
 
282
286
  # 完全重启
283
287
  kill -9 $(pgrep -f "openclaw.*gateway")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hpplay-lebo/cluster-hub",
3
- "version": "3.3.1",
3
+ "version": "3.4.0",
4
4
  "description": "OpenClaw Hub cluster plugin — cross-network node collaboration, chat, and task orchestration",
5
5
  "author": "HPPlay",
6
6
  "license": "MIT",
@@ -98,7 +98,13 @@ async function feishuPost(path: string, body: any): Promise<any> {
98
98
  },
99
99
  body: JSON.stringify(body),
100
100
  });
101
- const data = await res.json() as any;
101
+ const text = await res.text();
102
+ let data;
103
+ try {
104
+ data = JSON.parse(text);
105
+ } catch (e: any) {
106
+ throw new Error(`JSON Error: ${e.message}. Status: ${res.status}. Body: ${text.substring(0, 200)}`);
107
+ }
102
108
  if (data.code !== 0) throw new Error(data.msg || `API error ${data.code}`);
103
109
  return data.data;
104
110
  }
@@ -210,13 +216,55 @@ async function docCreate(title: string, folderToken?: string) {
210
216
  return result;
211
217
  }
212
218
 
219
+ function simpleMarkdownToBlocks(markdown: string): any[] {
220
+ const blocks: any[] = [];
221
+ const lines = markdown.split('\n');
222
+ let inCodeBlock = false;
223
+ let codeContent = '';
224
+
225
+ for (const line of lines) {
226
+ if (line.trim().startsWith('```')) {
227
+ if (inCodeBlock) {
228
+ blocks.push({
229
+ block_type: 14,
230
+ code: { elements: [{ text_run: { content: codeContent.replace(/\n$/, '') } }], language: 1 }
231
+ });
232
+ inCodeBlock = false;
233
+ codeContent = '';
234
+ } else {
235
+ inCodeBlock = true;
236
+ }
237
+ continue;
238
+ }
239
+
240
+ if (inCodeBlock) {
241
+ codeContent += line + '\n';
242
+ continue;
243
+ }
244
+
245
+ const trimmed = line.trim();
246
+ if (!trimmed) continue;
247
+
248
+ if (trimmed.startsWith('# ')) {
249
+ blocks.push({ block_type: 3, heading1: { elements: [{ text_run: { content: trimmed.substring(2) } }] } });
250
+ } else if (trimmed.startsWith('## ')) {
251
+ blocks.push({ block_type: 4, heading2: { elements: [{ text_run: { content: trimmed.substring(3) } }] } });
252
+ } else if (trimmed.startsWith('### ')) {
253
+ blocks.push({ block_type: 5, heading3: { elements: [{ text_run: { content: trimmed.substring(4) } }] } });
254
+ } else if (trimmed.startsWith('- ') || trimmed.startsWith('* ')) {
255
+ blocks.push({ block_type: 12, bullet: { elements: [{ text_run: { content: trimmed.substring(2) } }] } });
256
+ } else if (trimmed.startsWith('> ')) {
257
+ blocks.push({ block_type: 15, quote: { elements: [{ text_run: { content: trimmed.substring(2) } }] } });
258
+ } else {
259
+ blocks.push({ block_type: 2, text: { elements: [{ text_run: { content: line } }] } });
260
+ }
261
+ }
262
+ return blocks;
263
+ }
264
+
213
265
  async function docWrite(docToken: string, markdown: string) {
214
- // 1. 转换 markdown 为 blocks
215
- const converted = await feishuPost('/open-apis/docx/v1/documents/convert', {
216
- content_type: 'markdown',
217
- content: markdown,
218
- });
219
- const blocks = converted?.blocks || [];
266
+ // 1. 转换 markdown 为 blocks (本地转换,不再依赖不稳定的 convert 接口)
267
+ const blocks = simpleMarkdownToBlocks(markdown);
220
268
 
221
269
  // 2. 清除现有内容
222
270
  const existing = await feishuGet(`/open-apis/docx/v1/documents/${docToken}/blocks`);
@@ -249,11 +297,7 @@ async function docWrite(docToken: string, markdown: string) {
249
297
  }
250
298
 
251
299
  async function docAppend(docToken: string, markdown: string) {
252
- const converted = await feishuPost('/open-apis/docx/v1/documents/convert', {
253
- content_type: 'markdown',
254
- content: markdown,
255
- });
256
- const blocks = converted?.blocks || [];
300
+ const blocks = simpleMarkdownToBlocks(markdown);
257
301
  if (blocks.length === 0) throw new Error('Content is empty');
258
302
 
259
303
  const inserted = await feishuPost(