@spzhongwin/skill-logger-plugin 1.0.7 → 1.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spzhongwin/skill-logger-plugin",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "type": "module",
5
5
  "exports": "./dist/index.js",
6
6
  "scripts": {
package/src/updater.ts CHANGED
@@ -323,6 +323,33 @@ export class SkillUpdater {
323
323
  /**
324
324
  * 手动下发安装/更新指令 (供沙盒 HTTP Proxy 与星型架构中控使用)
325
325
  */
326
+ /**
327
+ * 从 URL 下载 ZIP 并直接解压替换到目标目录(不检查 SKILL.md)
328
+ */
329
+ async installZipFromUrl(url: string, targetDir: string): Promise<{success: boolean; message: string}> {
330
+ const work = path.join(this.tmpDir, `slp-zip-${randomUUID()}`);
331
+ await fs.mkdir(work, { recursive: true });
332
+ try {
333
+ const zipPath = path.join(work, "pkg.zip");
334
+ const res = await this.fetchImpl(url);
335
+ if (!res.ok) {
336
+ return { success: false, message: `下载失败: HTTP ${res.status}` };
337
+ }
338
+ const buf = Buffer.from(await res.arrayBuffer());
339
+ await fs.writeFile(zipPath, buf);
340
+
341
+ const staging = path.join(work, "staging");
342
+ await this.unzip(zipPath, staging);
343
+
344
+ await this.replaceDir(staging, targetDir);
345
+ return { success: true, message: `安装成功` };
346
+ } catch (err: any) {
347
+ return { success: false, message: `执行出错: ${err.message}` };
348
+ } finally {
349
+ await fs.rm(work, { recursive: true, force: true }).catch(() => {});
350
+ }
351
+ }
352
+
326
353
  async manualInstall(options: {
327
354
  code: string;
328
355
  url?: string;
package/src/ws-client.ts CHANGED
@@ -202,9 +202,10 @@ export class GatewayWsClient {
202
202
  try {
203
203
  entries = await fs.readdir(rootPath);
204
204
  } catch (e) {
205
- this.currentAgentIds = new Set();
206
- this.appendLogToFile("WARN", "AgentScan", "OpenClaw home is not readable; reporting empty agent list", e);
207
- this.sendAgentListReport("AGENT_LIST_REPORT");
205
+ // 读目录失败通常是瞬时性的(磁盘抖动/权限/句柄耗尽等),不代表这台机器真的没有用户了。
206
+ // 跳过本轮上报、保留上次已知状态,等下一次扫描自然重试,避免把瞬时故障放大成
207
+ // "全部用户离线"(服务端收到空列表会把这台网关下所有用户标记 OFFLINE)。
208
+ this.appendLogToFile("WARN", "AgentScan", "OpenClaw home is not readable; skipping this scan cycle", e);
208
209
  return;
209
210
  }
210
211
 
@@ -513,11 +514,54 @@ export class GatewayWsClient {
513
514
  }
514
515
  }, delayMs);
515
516
 
517
+ } else if (action === "INSTALL_EXPERT") {
518
+ if (!safeCode) throw new Error("Missing code parameter");
519
+ const { name, version, downloadUrl, skills } = msg;
520
+ console.log(`[skill-logger-plugin][WS] Executing INSTALL_EXPERT for user ${userId}, code: ${safeCode}`);
521
+ this.appendLogToFile("INFO", "Command", `INSTALL_EXPERT received`, { userId, code: safeCode, version, downloadUrl });
522
+
523
+ const userSkillRoot = path.join(openclawHome(), `workspace-assistant-${pureId}`, ".user");
524
+
525
+ // 1. 安装专家本身
526
+ const expertTarget = path.join(userSkillRoot, "experts", safeCode);
527
+ await fs.mkdir(path.dirname(expertTarget), { recursive: true });
528
+ const expertResult = await this.options.updater.installZipFromUrl(downloadUrl, expertTarget);
529
+ if (!expertResult.success) {
530
+ throw new Error(`专家安装失败: ${expertResult.message}`);
531
+ }
532
+
533
+ // 2. 安装依赖的 skills
534
+ const skillTargetRoot = path.join(userSkillRoot, "skills");
535
+ await fs.mkdir(skillTargetRoot, { recursive: true });
536
+ const skillResults: string[] = [];
537
+ if (Array.isArray(skills)) {
538
+ for (const sk of skills) {
539
+ if (!sk.code || !sk.downloadUrl) {
540
+ skillResults.push(`${sk.code || 'unknown'}: 缺少下载地址`);
541
+ continue;
542
+ }
543
+ try {
544
+ const skTarget = path.join(skillTargetRoot, sk.code);
545
+ const result = await this.options.updater.installZipFromUrl(sk.downloadUrl, skTarget);
546
+ skillResults.push(`${sk.code}: ${result.success ? '成功' : '失败 - ' + result.message}`);
547
+ } catch (e: any) {
548
+ skillResults.push(`${sk.code}: 失败 - ${e.message}`);
549
+ }
550
+ }
551
+ }
552
+
553
+ this.reply(replyId, {
554
+ success: true,
555
+ message: `专家 ${safeCode} 安装完成`,
556
+ action,
557
+ data: { expertCode: safeCode, skills: skillResults },
558
+ });
559
+
516
560
  } else if (action === "GET_EXPERT_REGISTRY") {
517
561
  console.log(`[skill-logger-plugin][WS] Executing GET_EXPERT_REGISTRY for user ${userId}`);
518
562
  this.appendLogToFile("INFO", "Command", `GET_EXPERT_REGISTRY received`, { userId });
519
563
 
520
- const registryFilePath = path.join(openclawHome(), `workspace-assistant-${pureId}`, ".openclaw", "userskill", "expert-registry.yaml");
564
+ const registryFilePath = path.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "expert-registry.yaml");
521
565
  let content = "";
522
566
  let fileExists = false;
523
567
  try {