@spzhongwin/skill-logger-plugin 1.0.14 → 1.0.16

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.
Files changed (57) hide show
  1. package/dist/index.js +80 -285
  2. package/openclaw.plugin.json +50 -50
  3. package/package.json +34 -37
  4. package/src/active-skills.test.ts +32 -32
  5. package/src/active-skills.ts +77 -77
  6. package/src/config-sync.test.ts +165 -165
  7. package/src/config-sync.ts +544 -544
  8. package/src/expert-skill-layout.test.ts +196 -0
  9. package/src/expert-skill-layout.ts +233 -0
  10. package/src/global.d.ts +4 -0
  11. package/src/hooks.test.ts +228 -251
  12. package/src/hooks.ts +494 -517
  13. package/src/http.ts +61 -61
  14. package/src/identity.ts +88 -64
  15. package/src/index.test.ts +53 -53
  16. package/src/index.ts +218 -226
  17. package/src/integration.test.ts +119 -119
  18. package/src/matcher.test.ts +170 -170
  19. package/src/matcher.ts +393 -393
  20. package/src/paths.test.ts +57 -57
  21. package/src/paths.ts +84 -84
  22. package/src/reporter.test.ts +139 -139
  23. package/src/reporter.ts +303 -298
  24. package/src/sample-config.json +72 -72
  25. package/src/semver.test.ts +33 -23
  26. package/src/semver.ts +65 -60
  27. package/src/skill-version.ts +53 -53
  28. package/src/types.ts +202 -198
  29. package/src/updater.test.ts +431 -325
  30. package/src/updater.ts +584 -549
  31. package/src/ws-client.test.ts +158 -128
  32. package/src/ws-client.ts +805 -769
  33. package/test-ws.ts +17 -17
  34. package/tsconfig.json +18 -14
  35. package/dist/active-skills.js +0 -67
  36. package/dist/active-skills.test.js +0 -29
  37. package/dist/config-sync.js +0 -439
  38. package/dist/config-sync.test.js +0 -145
  39. package/dist/hooks.js +0 -337
  40. package/dist/hooks.test.js +0 -123
  41. package/dist/http.js +0 -54
  42. package/dist/identity.js +0 -56
  43. package/dist/index.test.js +0 -39
  44. package/dist/integration.test.js +0 -102
  45. package/dist/matcher.js +0 -362
  46. package/dist/matcher.test.js +0 -139
  47. package/dist/paths.js +0 -62
  48. package/dist/paths.test.js +0 -49
  49. package/dist/reporter.js +0 -267
  50. package/dist/reporter.test.js +0 -128
  51. package/dist/semver.js +0 -64
  52. package/dist/semver.test.js +0 -21
  53. package/dist/skill-version.js +0 -23
  54. package/dist/types.js +0 -9
  55. package/dist/updater.js +0 -352
  56. package/dist/updater.test.js +0 -212
  57. package/dist/ws-client.js +0 -484
package/dist/index.js CHANGED
@@ -106,6 +106,7 @@ import fsSync from "node:fs";
106
106
  import path3 from "node:path";
107
107
  import os2 from "node:os";
108
108
  import { execFile } from "node:child_process";
109
+ import { promisify } from "node:util";
109
110
  import { randomUUID, createHash } from "node:crypto";
110
111
 
111
112
  // src/skill-version.ts
@@ -138,37 +139,14 @@ function parseSkillVersion(content) {
138
139
  }
139
140
 
140
141
  // src/updater.ts
141
- var EXTRACT_TIMEOUT_MS = 3e4;
142
+ var execFileAsync = promisify(execFile);
142
143
  var ATTEMPT_COOLDOWN_MS = 30 * 60 * 1e3;
143
144
  function skillIdentityHash(code, version) {
144
145
  return createHash("sha256").update(`${code} ${code} ${version}`).digest("hex");
145
146
  }
146
- function extractorCommandForPlatform(platform, zipPath, destDir) {
147
- return platform === "darwin" ? { command: "ditto", args: ["-x", "-k", zipPath, destDir] } : { command: "unzip", args: ["-o", "-q", zipPath, "-d", destDir] };
148
- }
149
- function runExtractor(command, args) {
150
- return new Promise((resolve, reject) => {
151
- const child = execFile(command, args, {
152
- encoding: "utf8",
153
- timeout: EXTRACT_TIMEOUT_MS,
154
- killSignal: "SIGKILL",
155
- maxBuffer: 1024 * 1024
156
- }, (error, _stdout, stderr) => {
157
- if (!error) {
158
- resolve();
159
- return;
160
- }
161
- const detail = String(stderr || "").trim();
162
- const timeout = error.killed ? `\uFF0C\u8D85\u8FC7 ${EXTRACT_TIMEOUT_MS}ms \u5DF2\u7EC8\u6B62` : "";
163
- reject(new Error(`${command} \u89E3\u538B\u5931\u8D25${timeout}: ${detail || error.message}`));
164
- });
165
- child.stdin?.end();
166
- });
167
- }
168
147
  async function systemUnzip(zipPath, destDir) {
169
148
  await fs3.mkdir(destDir, { recursive: true });
170
- const { command, args } = extractorCommandForPlatform(process.platform, zipPath, destDir);
171
- await runExtractor(command, args);
149
+ await execFileAsync("unzip", ["-o", "-q", zipPath, "-d", destDir]);
172
150
  }
173
151
  var SkillUpdater = class {
174
152
  getConfig;
@@ -417,85 +395,36 @@ var SkillUpdater = class {
417
395
  }
418
396
  }
419
397
  async manualInstall(options) {
420
- const { code, force, targetDir, additionalTargetDirs = [], trace } = options;
398
+ const { code, force, targetDir } = options;
421
399
  let { url, version } = options;
422
- const startedAt = Date.now();
423
- let currentStage = "install.start";
424
- let work;
425
- const emit = (stage, data) => {
426
- currentStage = stage;
427
- try {
428
- trace?.(stage, data);
429
- } catch {
400
+ if (!url) {
401
+ const dl = await this.fetchDownloadUrl(code, version || "latest");
402
+ if (!dl?.url) {
403
+ return { success: false, message: `\u65E0\u6CD5\u83B7\u53D6\u6280\u80FD ${code} \u7684\u4E0B\u8F7D\u5730\u5740` };
430
404
  }
431
- };
432
- emit("install.start", {
433
- code,
434
- version: version || "latest",
435
- force,
436
- hasDirectUrl: Boolean(url),
437
- targetCount: 1 + additionalTargetDirs.length
438
- });
405
+ url = dl.url;
406
+ version = dl.version || version;
407
+ }
408
+ const targetSkillPath = path3.join(targetDir, code);
409
+ if (!force && await this.exists(targetSkillPath)) {
410
+ return { success: false, message: `\u6280\u80FD ${code} \u5DF2\u5B58\u5728\u4E8E\u76EE\u6807\u76EE\u5F55` };
411
+ }
412
+ const work = path3.join(this.tmpDir, `slp-manual-${randomUUID()}`);
413
+ await fs3.mkdir(work, { recursive: true });
439
414
  try {
440
- if (!url) {
441
- const lookupStartedAt = Date.now();
442
- emit("download_url.lookup.start", { code, version: version || "latest" });
443
- const dl = await this.fetchDownloadUrl(code, version || "latest");
444
- if (!dl?.url) {
445
- const message = `\u65E0\u6CD5\u83B7\u53D6\u6280\u80FD ${code} \u7684\u4E0B\u8F7D\u5730\u5740`;
446
- emit("install.failed", { stage: currentStage, message, elapsedMs: Date.now() - startedAt });
447
- return { success: false, message };
448
- }
449
- url = dl.url;
450
- version = dl.version || version;
451
- emit("download_url.lookup.completed", {
452
- version: version || "latest",
453
- url,
454
- elapsedMs: Date.now() - lookupStartedAt
455
- });
456
- }
457
- const targetSkillPath = path3.join(targetDir, code);
458
- if (!force && await this.exists(targetSkillPath)) {
459
- const message = `\u6280\u80FD ${code} \u5DF2\u5B58\u5728\u4E8E\u76EE\u6807\u76EE\u5F55`;
460
- emit("install.failed", { stage: currentStage, message, elapsedMs: Date.now() - startedAt });
461
- return { success: false, message };
462
- }
463
- work = path3.join(this.tmpDir, `slp-manual-${randomUUID()}`);
464
- await fs3.mkdir(work, { recursive: true });
465
415
  const zipPath = path3.join(work, "pkg.zip");
466
- const downloadStartedAt = Date.now();
467
- emit("download.request.start", { url });
468
416
  const res = await this.fetchImpl(url);
469
- emit("download.headers.received", {
470
- status: res.status,
471
- contentLength: res.headers?.get("content-length") || void 0,
472
- contentType: res.headers?.get("content-type") || void 0,
473
- elapsedMs: Date.now() - downloadStartedAt
474
- });
475
417
  if (!res.ok) {
476
- const message = `\u4E0B\u8F7D\u5931\u8D25: HTTP ${res.status}`;
477
- emit("install.failed", { stage: currentStage, message, elapsedMs: Date.now() - startedAt });
478
- return { success: false, message };
418
+ return { success: false, message: `\u4E0B\u8F7D\u5931\u8D25: HTTP ${res.status}` };
479
419
  }
480
420
  const buf = Buffer.from(await res.arrayBuffer());
481
- emit("download.body.completed", {
482
- bytes: buf.byteLength,
483
- elapsedMs: Date.now() - downloadStartedAt
484
- });
485
421
  await fs3.writeFile(zipPath, buf);
486
- emit("download.file.written", { bytes: buf.byteLength });
487
422
  const staging = path3.join(work, "staging");
488
- const unzipStartedAt = Date.now();
489
- emit("unzip.start", { extractor: process.platform === "darwin" ? "ditto" : "unzip" });
490
423
  await this.unzip(zipPath, staging);
491
- emit("unzip.completed", { elapsedMs: Date.now() - unzipStartedAt });
492
424
  const srcRoot = await this.locateSkillRoot(staging, 0);
493
425
  if (!srcRoot) {
494
- const message = `\u4E0B\u8F7D\u5305\u5185\u672A\u627E\u5230 SKILL.md\uFF0C\u975E\u6CD5\u7684\u6280\u80FD\u5305\u7ED3\u6784`;
495
- emit("install.failed", { stage: currentStage, message, elapsedMs: Date.now() - startedAt });
496
- return { success: false, message };
426
+ return { success: false, message: `\u4E0B\u8F7D\u5305\u5185\u672A\u627E\u5230 SKILL.md\uFF0C\u975E\u6CD5\u7684\u6280\u80FD\u5305\u7ED3\u6784` };
497
427
  }
498
- emit("skill_root.located");
499
428
  const metaPath = path3.join(srcRoot, ".meta.json");
500
429
  if (!await this.exists(metaPath)) {
501
430
  let parsedVersion = version || "unknown";
@@ -513,37 +442,13 @@ var SkillUpdater = class {
513
442
  publishedAt: Date.now()
514
443
  }, null, 2));
515
444
  }
516
- const targets = [targetDir, ...additionalTargetDirs];
517
- for (let index = 0; index < targets.length; index += 1) {
518
- const rootDir = targets[index];
519
- const targetPath = path3.join(rootDir, code);
520
- const replaceStartedAt = Date.now();
521
- emit("target.replace.start", { targetDir: rootDir, targetIndex: index, targetCount: targets.length });
522
- await this.replaceDir(srcRoot, targetPath);
523
- emit("target.replace.completed", {
524
- targetDir: rootDir,
525
- targetIndex: index,
526
- targetCount: targets.length,
527
- elapsedMs: Date.now() - replaceStartedAt
528
- });
529
- }
530
- emit("install.completed", { elapsedMs: Date.now() - startedAt, targetCount: targets.length });
445
+ await this.replaceDir(srcRoot, targetSkillPath);
531
446
  return { success: true, message: `\u6280\u80FD ${code} \u5B89\u88C5/\u66F4\u65B0\u6210\u529F` };
532
447
  } catch (err) {
533
- emit("install.failed", {
534
- stage: currentStage,
535
- errorName: err?.name,
536
- message: err?.message || String(err),
537
- stack: err?.stack,
538
- elapsedMs: Date.now() - startedAt
539
- });
540
448
  return { success: false, message: `\u6267\u884C\u51FA\u9519: ${err.message}` };
541
449
  } finally {
542
- if (work) {
543
- await fs3.rm(work, { recursive: true, force: true }).catch(() => {
544
- });
545
- emit("cleanup.completed", { elapsedMs: Date.now() - startedAt });
546
- }
450
+ await fs3.rm(work, { recursive: true, force: true }).catch(() => {
451
+ });
547
452
  }
548
453
  }
549
454
  };
@@ -1376,11 +1281,11 @@ import path6 from "node:path";
1376
1281
  // src/identity.ts
1377
1282
  import os3 from "node:os";
1378
1283
  import { execFile as execFile2 } from "node:child_process";
1379
- import { promisify } from "node:util";
1380
- var execFileAsync = promisify(execFile2);
1284
+ import { promisify as promisify2 } from "node:util";
1285
+ var execFileAsync2 = promisify2(execFile2);
1381
1286
  async function getGitConfigValue(key) {
1382
1287
  try {
1383
- const { stdout } = await execFileAsync("git", ["config", "--global", key]);
1288
+ const { stdout } = await execFileAsync2("git", ["config", "--global", key]);
1384
1289
  return stdout.trim();
1385
1290
  } catch {
1386
1291
  console.warn(
@@ -1638,7 +1543,6 @@ var Reporter = class {
1638
1543
  import WebSocket from "ws";
1639
1544
  import path7 from "path";
1640
1545
  import fs6 from "fs/promises";
1641
- import { DatabaseSync } from "node:sqlite";
1642
1546
  var HEARTBEAT_INTERVAL_MS = 3e4;
1643
1547
  var HEARTBEAT_ACK_TIMEOUT_MS = 75e3;
1644
1548
  var AGENT_SCAN_INTERVAL_MS = 3 * 60 * 1e3;
@@ -1658,24 +1562,6 @@ function normalizeAssistantUserId(userId) {
1658
1562
  if (!ASSISTANT_WORKSPACE_ID_RE.test(pureId)) return void 0;
1659
1563
  return pureId;
1660
1564
  }
1661
- function shouldSyncBuiltInTemplate(action, isBuiltIn) {
1662
- return action === "UPDATE_SKILL" && isBuiltIn === true;
1663
- }
1664
- function defaultOpenclawSqlitePath() {
1665
- return path7.join(openclawHome(), "state", "openclaw.sqlite");
1666
- }
1667
- function readCronJobsByAgentId(agentId, sqlitePath = defaultOpenclawSqlitePath(), onError) {
1668
- let db;
1669
- try {
1670
- db = new DatabaseSync(sqlitePath, { readOnly: true });
1671
- return db.prepare("SELECT * FROM cron_jobs WHERE agent_id = ?").all(agentId);
1672
- } catch (err) {
1673
- onError?.(err);
1674
- return [];
1675
- } finally {
1676
- db?.close();
1677
- }
1678
- }
1679
1565
  var GatewayWsClient = class {
1680
1566
  ws = null;
1681
1567
  options;
@@ -1712,14 +1598,6 @@ var GatewayWsClient = class {
1712
1598
  } catch (e) {
1713
1599
  }
1714
1600
  }
1715
- createInstallTrace(context) {
1716
- return (stage, data) => {
1717
- this.appendLogToFile(stage === "install.failed" ? "ERROR" : "INFO", "Install", stage, {
1718
- ...context,
1719
- ...data
1720
- });
1721
- };
1722
- }
1723
1601
  constructor(options) {
1724
1602
  this.options = options;
1725
1603
  }
@@ -1971,39 +1849,10 @@ var GatewayWsClient = class {
1971
1849
  * 核心指令分发中心:完全跳过沙盒,基于 userId 直接进行底层物理文件操作
1972
1850
  */
1973
1851
  async handleMessage(msg) {
1974
- const { action, userId, code, url, force, version, replyId, isBuiltIn } = msg;
1975
- this.appendLogToFile("INFO", "Command", `Received WS message`, {
1976
- action,
1977
- userId,
1978
- code,
1979
- version,
1980
- replyId,
1981
- isBuiltIn,
1982
- hasDirectUrl: Boolean(url)
1983
- });
1984
- if (!action) {
1985
- this.appendLogToFile("WARN", "Command", `Message dropped: missing action`, msg);
1986
- return;
1987
- }
1988
- if (action === "GET_CRON_JOBS_BY_AGENT_ID") {
1989
- const agentId = typeof msg.agent_id === "string" ? msg.agent_id : msg.agentId;
1990
- if (!agentId) {
1991
- this.reply(replyId, { success: false, message: "Missing agent_id parameter", action });
1992
- return;
1993
- }
1994
- try {
1995
- const data = readCronJobsByAgentId(agentId, defaultOpenclawSqlitePath(), (err) => {
1996
- this.appendLogToFile("WARN", "Command", `GET_CRON_JOBS_BY_AGENT_ID sqlite lookup skipped`, err);
1997
- });
1998
- this.reply(replyId, { success: true, data, action });
1999
- } catch (err) {
2000
- this.appendLogToFile("ERROR", "Command", `GET_CRON_JOBS_BY_AGENT_ID threw`, err);
2001
- this.reply(replyId, { success: false, message: err.message, action });
2002
- }
2003
- return;
2004
- }
2005
- if (!userId) {
2006
- this.appendLogToFile("WARN", "Command", `Message dropped: missing userId`, msg);
1852
+ const { action, userId, code, url, force, version, replyId } = msg;
1853
+ this.appendLogToFile("INFO", "Command", `Received WS message`, { action, userId, code, version, replyId });
1854
+ if (!action || !userId) {
1855
+ this.appendLogToFile("WARN", "Command", `Message dropped: missing action or userId`, msg);
2007
1856
  return;
2008
1857
  }
2009
1858
  const safeCode = code ? path7.basename(code) : void 0;
@@ -2023,8 +1872,7 @@ var GatewayWsClient = class {
2023
1872
  url,
2024
1873
  version,
2025
1874
  force: force !== false,
2026
- targetDir,
2027
- trace: this.createInstallTrace({ action, replyId, userId, code: safeCode })
1875
+ targetDir
2028
1876
  });
2029
1877
  this.reply(replyId, { success: result.success, message: result.message, action });
2030
1878
  } else if (action === "UNINSTALL_SKILL") {
@@ -2059,7 +1907,7 @@ var GatewayWsClient = class {
2059
1907
  }
2060
1908
  const metaPath = path7.join(skillDir, ".meta.json");
2061
1909
  let isPlatform = false;
2062
- let isBuiltIn2 = e.isSymbolicLink();
1910
+ let isBuiltIn = e.isSymbolicLink();
2063
1911
  let metaData = null;
2064
1912
  let name = e.name;
2065
1913
  let description = "";
@@ -2080,7 +1928,7 @@ var GatewayWsClient = class {
2080
1928
  const parsed = JSON.parse(metaContent);
2081
1929
  if (parsed) {
2082
1930
  if (parsed.ownerId === "CMS" || parsed.ownerId === "CMS_COMPAT") isPlatform = true;
2083
- if (parsed.isBuiltIn === true || parsed.ownerId === "built-in") isBuiltIn2 = true;
1931
+ if (parsed.isBuiltIn === true || parsed.ownerId === "built-in") isBuiltIn = true;
2084
1932
  metaData = parsed;
2085
1933
  }
2086
1934
  } catch (err) {
@@ -2091,7 +1939,7 @@ var GatewayWsClient = class {
2091
1939
  list.push({
2092
1940
  code: e.name,
2093
1941
  isPlatform: true,
2094
- isBuiltIn: isBuiltIn2,
1942
+ isBuiltIn,
2095
1943
  version: skillVersion,
2096
1944
  name,
2097
1945
  description,
@@ -2101,7 +1949,7 @@ var GatewayWsClient = class {
2101
1949
  list.push({
2102
1950
  code: e.name,
2103
1951
  isPlatform: false,
2104
- isBuiltIn: isBuiltIn2,
1952
+ isBuiltIn,
2105
1953
  version: skillVersion,
2106
1954
  name,
2107
1955
  description
@@ -2112,60 +1960,21 @@ var GatewayWsClient = class {
2112
1960
  } else if (action === "UPDATE_SKILL") {
2113
1961
  if (!safeCode) throw new Error("Missing code parameter");
2114
1962
  const delayMs = Math.random() * 5e3;
2115
- const syncBuiltInTemplate = shouldSyncBuiltInTemplate(action, isBuiltIn);
2116
1963
  console.log(`[skill-logger-plugin][WS] Scheduled UPDATE for user ${userId}, code: ${safeCode} in ${Math.round(delayMs)}ms`);
2117
- this.appendLogToFile("INFO", "Command", `Scheduled UPDATE_SKILL`, {
2118
- userId,
2119
- code: safeCode,
2120
- version,
2121
- isBuiltIn,
2122
- syncBuiltInTemplate,
2123
- delayMs: Math.round(delayMs)
2124
- });
1964
+ this.appendLogToFile("INFO", "Command", `Scheduled UPDATE_SKILL`, { userId, code: safeCode, version, delayMs: Math.round(delayMs) });
2125
1965
  setTimeout(async () => {
2126
1966
  try {
2127
- const additionalTargetDirs = syncBuiltInTemplate ? [path7.join(openclawHome(), "workspace-xgjk-assistant-template", "skills")] : [];
2128
- const userSkillDir = path7.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "skills", safeCode);
2129
- try {
2130
- await fs6.stat(userSkillDir);
2131
- additionalTargetDirs.push(userSkillDir);
2132
- } catch {
2133
- }
2134
- const result = await this.options.updater.manualInstall({
1967
+ await this.options.updater.manualInstall({
2135
1968
  code: safeCode,
2136
1969
  url,
2137
1970
  version,
2138
1971
  force: true,
2139
- targetDir,
2140
- additionalTargetDirs,
2141
- trace: this.createInstallTrace({
2142
- action,
2143
- replyId,
2144
- userId,
2145
- code: safeCode,
2146
- isBuiltIn,
2147
- syncBuiltInTemplate
2148
- })
2149
- });
2150
- this.appendLogToFile(result.success ? "INFO" : "ERROR", "Command", `UPDATE_SKILL completed`, {
2151
- userId,
2152
- code: safeCode,
2153
- replyId,
2154
- success: result.success,
2155
- message: result.message,
2156
- syncBuiltInTemplate
1972
+ targetDir
2157
1973
  });
2158
1974
  if (replyId) {
2159
- this.reply(replyId, { success: result.success, message: result.message, action });
1975
+ this.reply(replyId, { success: true, message: `Skill ${safeCode} updated successfully`, action });
2160
1976
  }
2161
1977
  } catch (e) {
2162
- this.appendLogToFile("ERROR", "Command", `UPDATE_SKILL threw`, {
2163
- userId,
2164
- code: safeCode,
2165
- replyId,
2166
- message: e?.message || String(e),
2167
- stack: e?.stack
2168
- });
2169
1978
  if (replyId) this.reply(replyId, { success: false, message: e.message, action });
2170
1979
  }
2171
1980
  }, delayMs);
@@ -2176,25 +1985,11 @@ var GatewayWsClient = class {
2176
1985
  this.appendLogToFile("INFO", "Command", `INSTALL_EXPERT received`, { userId, code: safeCode, version: version2, downloadUrl });
2177
1986
  const userSkillRoot = path7.join(openclawHome(), `workspace-assistant-${pureId}`, ".user");
2178
1987
  const expertTarget = path7.join(userSkillRoot, "experts", safeCode);
2179
- let skipInstall = false;
2180
- const metaPath = path7.join(expertTarget, ".meta.json");
2181
- try {
2182
- const raw = await fs6.readFile(metaPath, "utf-8");
2183
- const existing = JSON.parse(raw);
2184
- if (existing.version && existing.version === (version2 || "1.0.0")) {
2185
- skipInstall = true;
2186
- }
2187
- } catch {
1988
+ await fs6.mkdir(path7.dirname(expertTarget), { recursive: true });
1989
+ const expertResult = await this.options.updater.installZipFromUrl(downloadUrl, expertTarget);
1990
+ if (!expertResult.success) {
1991
+ throw new Error(`\u4E13\u5BB6\u5B89\u88C5\u5931\u8D25: ${expertResult.message}`);
2188
1992
  }
2189
- if (!skipInstall) {
2190
- await fs6.mkdir(path7.dirname(expertTarget), { recursive: true });
2191
- const expertResult = await this.options.updater.installZipFromUrl(downloadUrl, expertTarget);
2192
- if (!expertResult.success) {
2193
- throw new Error(`\u4E13\u5BB6\u5B89\u88C5\u5931\u8D25: ${expertResult.message}`);
2194
- }
2195
- }
2196
- const meta = { code: safeCode, name, version: version2 || "1.0.0", installedAt: Date.now() };
2197
- await fs6.writeFile(metaPath, JSON.stringify(meta, null, 2));
2198
1993
  const skillTargetRoot = path7.join(userSkillRoot, "skills");
2199
1994
  await fs6.mkdir(skillTargetRoot, { recursive: true });
2200
1995
  const skillResults = [];
@@ -2219,42 +2014,48 @@ var GatewayWsClient = class {
2219
2014
  action,
2220
2015
  data: { expertCode: safeCode, skills: skillResults }
2221
2016
  });
2222
- } else if (action === "UNINSTALL_EXPERT") {
2223
- if (!safeCode) throw new Error("Missing code parameter");
2224
- console.log(`[skill-logger-plugin][WS] Executing UNINSTALL_EXPERT for user ${userId}, code: ${safeCode}`);
2225
- this.appendLogToFile("INFO", "Command", `UNINSTALL_EXPERT received`, { userId, code: safeCode });
2226
- const expertPath = path7.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "experts", safeCode);
2227
- await fs6.rm(expertPath, { recursive: true, force: true });
2228
- this.reply(replyId, { success: true, message: `\u4E13\u5BB6 ${safeCode} \u5DF2\u5378\u8F7D`, action });
2229
- } else if (action === "LIST_EXPERTS") {
2230
- console.log(`[skill-logger-plugin][WS] Executing LIST_EXPERTS for user ${userId}`);
2231
- this.appendLogToFile("INFO", "Command", `LIST_EXPERTS received`, { userId });
2232
- const expertsDir = path7.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "experts");
2233
- const list = [];
2017
+ } else if (action === "GET_EXPERT_REGISTRY") {
2018
+ console.log(`[skill-logger-plugin][WS] Executing GET_EXPERT_REGISTRY for user ${userId}`);
2019
+ this.appendLogToFile("INFO", "Command", `GET_EXPERT_REGISTRY received`, { userId });
2020
+ const registryFilePath = path7.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "expert-registry.yaml");
2021
+ let content = "";
2022
+ let fileExists = false;
2234
2023
  try {
2235
- const stat = await fs6.stat(expertsDir);
2236
- if (stat.isDirectory()) {
2237
- const entries = await fs6.readdir(expertsDir, { withFileTypes: true });
2238
- for (const e of entries) {
2239
- if (!e.isDirectory()) continue;
2240
- const metaPath = path7.join(expertsDir, e.name, ".meta.json");
2241
- try {
2242
- const raw = await fs6.readFile(metaPath, "utf-8");
2243
- const meta = JSON.parse(raw);
2244
- list.push({
2245
- code: meta.code || e.name,
2246
- name: meta.name || e.name,
2247
- version: meta.version || "",
2248
- installedAt: meta.installedAt
2249
- });
2250
- } catch {
2251
- list.push({ code: e.name, name: e.name, version: "" });
2024
+ const stat = await fs6.stat(registryFilePath);
2025
+ fileExists = stat.isFile();
2026
+ } catch (err) {
2027
+ if (err?.code !== "ENOENT") throw err;
2028
+ }
2029
+ if (fileExists) {
2030
+ content = await fs6.readFile(registryFilePath, "utf8");
2031
+ const expertsList = [];
2032
+ const lines = content.split("\n");
2033
+ let currentExpert = null;
2034
+ for (const line of lines) {
2035
+ const trimmed = line.trim();
2036
+ if (trimmed.startsWith("#")) continue;
2037
+ const idMatch = line.match(/^\s*-\s*id:\s*(.+)$/);
2038
+ if (idMatch) {
2039
+ if (currentExpert && currentExpert.id) expertsList.push(currentExpert);
2040
+ currentExpert = { id: idMatch[1].trim() };
2041
+ continue;
2042
+ }
2043
+ if (currentExpert) {
2044
+ const nameMatch = line.match(/^\s*name:\s*(.+)$/);
2045
+ if (nameMatch) {
2046
+ currentExpert.name = nameMatch[1].trim();
2047
+ }
2048
+ const descMatch = line.match(/^\s*description:\s*(.+)$/);
2049
+ if (descMatch) {
2050
+ currentExpert.description = descMatch[1].trim();
2252
2051
  }
2253
2052
  }
2254
2053
  }
2255
- } catch {
2054
+ if (currentExpert && currentExpert.id) expertsList.push(currentExpert);
2055
+ this.reply(replyId, { success: true, data: expertsList, action });
2056
+ } else {
2057
+ this.reply(replyId, { success: false, message: `\u672A\u627E\u5230\u7528\u6237\u6280\u80FD\u914D\u7F6E\u6587\u4EF6\uFF0C\u53EF\u80FD\u5C1A\u672A\u6CE8\u518C\u6216\u6587\u4EF6\u5DF2\u4E22\u5931`, action });
2256
2058
  }
2257
- this.reply(replyId, { success: true, data: list, action });
2258
2059
  } else {
2259
2060
  console.warn(`[skill-logger-plugin][WS] Unknown action: ${action}`);
2260
2061
  this.appendLogToFile("WARN", "Command", `Unknown action: ${action}`);
@@ -2271,13 +2072,7 @@ var GatewayWsClient = class {
2271
2072
  this.appendLogToFile("WARN", "Command", `Cannot reply: WS closed or missing replyId`, { replyId });
2272
2073
  return;
2273
2074
  }
2274
- this.ws.send(JSON.stringify({ type: "REPLY", replyId, ...payload }), (err) => {
2275
- if (err) {
2276
- this.appendLogToFile("ERROR", "Command", `Reply send failed`, { replyId, message: err.message });
2277
- return;
2278
- }
2279
- this.appendLogToFile("INFO", "Command", `Reply sent`, { replyId });
2280
- });
2075
+ this.ws.send(JSON.stringify({ type: "REPLY", replyId, ...payload }));
2281
2076
  }
2282
2077
  destroy() {
2283
2078
  this.isDestroyed = true;
@@ -1,50 +1,50 @@
1
- {
2
- "id": "skill-logger-plugin",
3
- "name": "Skill Logger",
4
- "description": "追踪 openclaw skill 内功能点(脚本/命令/工具/HTTP)使用与报错,落本地并批量上报",
5
- "activation": {
6
- "onStartup": true
7
- },
8
- "configSchema": {
9
- "type": "object",
10
- "additionalProperties": true,
11
- "properties": {
12
- "pluginId": {
13
- "type": "string",
14
- "description": "自定义插件 ID"
15
- },
16
- "pluginName": {
17
- "type": "string",
18
- "description": "自定义插件名称"
19
- },
20
- "platformBaseUrl": {
21
- "type": "string",
22
- "description": "拉取 skill 标准配置的服务地址;缺省时使用内置静态桩"
23
- },
24
- "reportBaseUrl": {
25
- "type": "string",
26
- "description": "批量上报的服务地址;缺省时只落本地、不上报"
27
- },
28
- "authToken": {
29
- "type": "string",
30
- "description": "拉取/上报接口的 Authorization 头值(如 'Bearer xxx')"
31
- },
32
- "recordUnattributed": {
33
- "type": "boolean",
34
- "description": "是否记录无法归属到具体功能点的 exec 调用(默认 true)"
35
- },
36
- "enableFileLog": {
37
- "type": "boolean",
38
- "description": "是否开启插件专属文件日志(落盘为 skill-logger.err),用于排查 WS 连接和指令运行情况"
39
- },
40
- "autoUpdateSkills": {
41
- "type": "boolean",
42
- "description": "是否开启 skill 版本自动更新(检测到本地落后于平台最新版本时下载并覆盖各 workspace 下的副本);默认开启,仅显式设为 false 才关闭"
43
- },
44
- "versionCheckIntervalMinutes": {
45
- "type": "number",
46
- "description": "版本检查+自动更新的周期(分钟)。每隔该周期按本地所有已装 skill 向服务端拉最新版本并按需更新;默认 30。本地扫描发现固定 3 分钟,与此独立"
47
- }
48
- }
49
- }
50
- }
1
+ {
2
+ "id": "skill-logger-plugin",
3
+ "name": "Skill Logger",
4
+ "description": "追踪 openclaw skill 内功能点(脚本/命令/工具/HTTP)使用与报错,落本地并批量上报",
5
+ "activation": {
6
+ "onStartup": true
7
+ },
8
+ "configSchema": {
9
+ "type": "object",
10
+ "additionalProperties": true,
11
+ "properties": {
12
+ "pluginId": {
13
+ "type": "string",
14
+ "description": "自定义插件 ID"
15
+ },
16
+ "pluginName": {
17
+ "type": "string",
18
+ "description": "自定义插件名称"
19
+ },
20
+ "platformBaseUrl": {
21
+ "type": "string",
22
+ "description": "拉取 skill 标准配置的服务地址;缺省时使用内置静态桩"
23
+ },
24
+ "reportBaseUrl": {
25
+ "type": "string",
26
+ "description": "批量上报的服务地址;缺省时只落本地、不上报"
27
+ },
28
+ "authToken": {
29
+ "type": "string",
30
+ "description": "拉取/上报接口的 Authorization 头值(如 'Bearer xxx')"
31
+ },
32
+ "recordUnattributed": {
33
+ "type": "boolean",
34
+ "description": "是否记录无法归属到具体功能点的 exec 调用(默认 true)"
35
+ },
36
+ "enableFileLog": {
37
+ "type": "boolean",
38
+ "description": "是否开启插件专属文件日志(落盘为 skill-logger.err),用于排查 WS 连接和指令运行情况"
39
+ },
40
+ "autoUpdateSkills": {
41
+ "type": "boolean",
42
+ "description": "是否开启 skill 版本自动更新(检测到本地落后于平台最新版本时下载并覆盖各 workspace 下的副本);默认开启,仅显式设为 false 才关闭"
43
+ },
44
+ "versionCheckIntervalMinutes": {
45
+ "type": "number",
46
+ "description": "版本检查+自动更新的周期(分钟)。每隔该周期按本地所有已装 skill 向服务端拉最新版本并按需更新;默认 30。本地扫描发现固定 3 分钟,与此独立"
47
+ }
48
+ }
49
+ }
50
+ }