@spzhongwin/skill-logger-plugin 1.0.2 → 1.0.4

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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
- import fs6 from "node:fs";
3
- import path8 from "node:path";
2
+ import fs7 from "node:fs";
3
+ import path9 from "node:path";
4
4
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5
5
  import os4 from "node:os";
6
6
 
@@ -101,17 +101,38 @@ var ActiveSkills = class {
101
101
  };
102
102
 
103
103
  // src/updater.ts
104
- import fs2 from "node:fs/promises";
104
+ import fs3 from "node:fs/promises";
105
105
  import fsSync from "node:fs";
106
- import path2 from "node:path";
106
+ import path3 from "node:path";
107
107
  import os2 from "node:os";
108
108
  import { execFile } from "node:child_process";
109
109
  import { promisify } from "node:util";
110
110
  import { randomUUID, createHash } from "node:crypto";
111
111
 
112
112
  // src/skill-version.ts
113
+ import fs2 from "node:fs/promises";
114
+ import path2 from "node:path";
115
+ async function readSkillVersion(rootDir, skillMdContent) {
116
+ try {
117
+ const metaPath = path2.join(rootDir, ".meta.json");
118
+ const metaContent = await fs2.readFile(metaPath, "utf-8");
119
+ const meta = JSON.parse(metaContent);
120
+ if (meta && typeof meta.version === "string" && meta.version.trim() && meta.version !== "unknown") {
121
+ return meta.version.trim();
122
+ }
123
+ } catch {
124
+ }
125
+ if (!skillMdContent) {
126
+ try {
127
+ skillMdContent = await fs2.readFile(path2.join(rootDir, "SKILL.md"), "utf-8");
128
+ } catch {
129
+ return void 0;
130
+ }
131
+ }
132
+ return parseSkillVersion(skillMdContent);
133
+ }
113
134
  function parseSkillVersion(content) {
114
- const m = /(?:^|\r?\n)[ \t]*(?:version|版本号|版本)[ \t]*[::][ \t]*(.+)/i.exec(content);
135
+ const m = /(?:^|\r?\n)[ \t]*(?:version|当前版本号|当前版本|版本号|版本)[ \t]*[::][ \t]*(.+)/i.exec(content);
115
136
  if (!m) return void 0;
116
137
  const v = m[1].replace(/\s+#.*$/, "").trim().replace(/^["']|["']$/g, "").trim();
117
138
  return v || void 0;
@@ -121,7 +142,7 @@ function parseSkillVersion(content) {
121
142
  var execFileAsync = promisify(execFile);
122
143
  var ATTEMPT_COOLDOWN_MS = 30 * 60 * 1e3;
123
144
  async function systemUnzip(zipPath, destDir) {
124
- await fs2.mkdir(destDir, { recursive: true });
145
+ await fs3.mkdir(destDir, { recursive: true });
125
146
  await execFileAsync("unzip", ["-o", "-q", zipPath, "-d", destDir]);
126
147
  }
127
148
  var SkillUpdater = class {
@@ -170,7 +191,7 @@ var SkillUpdater = class {
170
191
  for (const [k, v] of this.lastAttempt) {
171
192
  obj[k] = v;
172
193
  }
173
- await fs2.writeFile(this.cooldownStatePath, JSON.stringify(obj), "utf-8");
194
+ await fs3.writeFile(this.cooldownStatePath, JSON.stringify(obj), "utf-8");
174
195
  } catch {
175
196
  }
176
197
  }
@@ -209,46 +230,45 @@ var SkillUpdater = class {
209
230
  async updateOne(skillName, version, targets) {
210
231
  const dl = await this.fetchDownloadUrl(skillName, version);
211
232
  if (!dl?.url) return;
212
- const work = path2.join(this.tmpDir, `slp-update-${randomUUID()}`);
213
- await fs2.mkdir(work, { recursive: true });
233
+ const work = path3.join(this.tmpDir, `slp-update-${randomUUID()}`);
234
+ await fs3.mkdir(work, { recursive: true });
214
235
  try {
215
- const zipPath = path2.join(work, "pkg.zip");
236
+ const zipPath = path3.join(work, "pkg.zip");
216
237
  const res = await this.fetchImpl(dl.url);
217
238
  if (!res.ok) {
218
239
  console.warn(`[skill-logger-plugin] \u4E0B\u8F7D ${skillName}@${version} \u5931\u8D25 HTTP`, res.status);
219
240
  return;
220
241
  }
221
242
  const buf = Buffer.from(await res.arrayBuffer());
222
- await fs2.writeFile(zipPath, buf);
223
- const staging = path2.join(work, "staging");
243
+ await fs3.writeFile(zipPath, buf);
244
+ const staging = path3.join(work, "staging");
224
245
  await this.unzip(zipPath, staging);
225
246
  const srcRoot = await this.locateSkillRoot(staging, 0);
226
247
  if (!srcRoot) {
227
248
  console.warn(`[skill-logger-plugin] ${skillName}@${version} \u4E0B\u8F7D\u5305\u5185\u672A\u627E\u5230 SKILL.md\uFF0C\u653E\u5F03\u8986\u76D6`);
228
249
  return;
229
250
  }
230
- const metaPath = path2.join(srcRoot, ".meta.json");
231
- if (!await this.exists(metaPath)) {
232
- this.debug(`[skill-logger-plugin] \u4E3A ${skillName}@${version} \u81EA\u52A8\u751F\u6210 .meta.json`);
233
- await fs2.writeFile(metaPath, JSON.stringify({
234
- ownerId: "CMS_COMPAT",
235
- slug: skillName,
236
- version,
237
- publishedAt: Date.now()
238
- }, null, 2));
239
- }
240
251
  let packageVersion;
241
252
  try {
242
- const newSkillMd = await fs2.readFile(path2.join(srcRoot, "SKILL.md"), "utf-8");
243
- packageVersion = parseSkillVersion(newSkillMd);
244
- } catch {
245
- console.warn(`[skill-logger-plugin] ${skillName}@${version} \u8BFB\u53D6\u4E0B\u8F7D\u5305 SKILL.md \u5931\u8D25\uFF0C\u653E\u5F03\u8986\u76D6`);
253
+ packageVersion = await readSkillVersion(srcRoot);
254
+ } catch (err) {
255
+ console.warn(`[skill-logger-plugin] ${skillName}@${version} \u8BFB\u53D6\u4E0B\u8F7D\u5305\u7248\u672C\u53F7\u5931\u8D25\uFF0C\u653E\u5F03\u8986\u76D6`, err);
246
256
  return;
247
257
  }
248
258
  if (!packageVersion) {
249
- console.warn(`[skill-logger-plugin] ${skillName}@${version} \u4E0B\u8F7D\u5305 SKILL.md \u65E0\u7248\u672C\u53F7\uFF0C\u653E\u5F03\u8986\u76D6`);
259
+ console.warn(`[skill-logger-plugin] ${skillName}@${version} \u4E0B\u8F7D\u5305\u65E0\u7248\u672C\u53F7\uFF0C\u653E\u5F03\u8986\u76D6`);
250
260
  return;
251
261
  }
262
+ const metaPath = path3.join(srcRoot, ".meta.json");
263
+ if (!await this.exists(metaPath)) {
264
+ this.debug(`[skill-logger-plugin] \u4E3A ${skillName}@${version} \u81EA\u52A8\u751F\u6210 .meta.json`);
265
+ await fs3.writeFile(metaPath, JSON.stringify({
266
+ ownerId: "CMS_COMPAT",
267
+ slug: skillName,
268
+ version: packageVersion,
269
+ publishedAt: Date.now()
270
+ }, null, 2));
271
+ }
252
272
  for (const target of targets) {
253
273
  try {
254
274
  await this.replaceDir(srcRoot, target);
@@ -258,7 +278,7 @@ var SkillUpdater = class {
258
278
  }
259
279
  }
260
280
  } finally {
261
- await fs2.rm(work, { recursive: true, force: true });
281
+ await fs3.rm(work, { recursive: true, force: true });
262
282
  }
263
283
  }
264
284
  /** POST 下载接口取包 URL。返回 undefined 表示拿不到(调用方放弃本次更新)。 */
@@ -287,14 +307,14 @@ var SkillUpdater = class {
287
307
  if (depth > 2) return void 0;
288
308
  let entries;
289
309
  try {
290
- entries = await fs2.readdir(dir, { withFileTypes: true });
310
+ entries = await fs3.readdir(dir, { withFileTypes: true });
291
311
  } catch {
292
312
  return void 0;
293
313
  }
294
314
  if (entries.some((e) => e.isFile() && e.name === "SKILL.md")) return dir;
295
315
  for (const e of entries) {
296
316
  if (e.isDirectory()) {
297
- const found = await this.locateSkillRoot(path2.join(dir, e.name), depth + 1);
317
+ const found = await this.locateSkillRoot(path3.join(dir, e.name), depth + 1);
298
318
  if (found) return found;
299
319
  }
300
320
  }
@@ -306,30 +326,30 @@ var SkillUpdater = class {
306
326
  * 换入失败时把旧目录还原,避免目标被清空。过程中的临时/旧目录都是隐藏且即时清理,不保留 .bak。
307
327
  */
308
328
  async replaceDir(src, target) {
309
- const parent = path2.dirname(target);
310
- await fs2.mkdir(parent, { recursive: true });
329
+ const parent = path3.dirname(target);
330
+ await fs3.mkdir(parent, { recursive: true });
311
331
  const tag = `${Date.now()}-${randomUUID().slice(0, 8)}`;
312
- const stage = path2.join(parent, `.${path2.basename(target)}.new-${tag}`);
313
- const old = path2.join(parent, `.${path2.basename(target)}.old-${tag}`);
314
- await fs2.rm(stage, { recursive: true, force: true });
315
- await fs2.cp(src, stage, { recursive: true });
332
+ const stage = path3.join(parent, `.${path3.basename(target)}.new-${tag}`);
333
+ const old = path3.join(parent, `.${path3.basename(target)}.old-${tag}`);
334
+ await fs3.rm(stage, { recursive: true, force: true });
335
+ await fs3.cp(src, stage, { recursive: true });
316
336
  const hadTarget = await this.exists(target);
317
- if (hadTarget) await fs2.rename(target, old);
337
+ if (hadTarget) await fs3.rename(target, old);
318
338
  try {
319
- await fs2.rename(stage, target);
339
+ await fs3.rename(stage, target);
320
340
  } catch (err) {
321
- if (hadTarget) await fs2.rename(old, target).catch(() => {
341
+ if (hadTarget) await fs3.rename(old, target).catch(() => {
322
342
  });
323
- await fs2.rm(stage, { recursive: true, force: true }).catch(() => {
343
+ await fs3.rm(stage, { recursive: true, force: true }).catch(() => {
324
344
  });
325
345
  throw err;
326
346
  }
327
- await fs2.rm(old, { recursive: true, force: true }).catch(() => {
347
+ await fs3.rm(old, { recursive: true, force: true }).catch(() => {
328
348
  });
329
349
  }
330
350
  async exists(p) {
331
351
  try {
332
- await fs2.access(p);
352
+ await fs3.access(p);
333
353
  return true;
334
354
  } catch {
335
355
  return false;
@@ -349,38 +369,37 @@ var SkillUpdater = class {
349
369
  url = dl.url;
350
370
  version = dl.version || version;
351
371
  }
352
- const targetSkillPath = path2.join(targetDir, code);
372
+ const targetSkillPath = path3.join(targetDir, code);
353
373
  if (!force && await this.exists(targetSkillPath)) {
354
374
  return { success: false, message: `\u6280\u80FD ${code} \u5DF2\u5B58\u5728\u4E8E\u76EE\u6807\u76EE\u5F55` };
355
375
  }
356
- const work = path2.join(this.tmpDir, `slp-manual-${randomUUID()}`);
357
- await fs2.mkdir(work, { recursive: true });
376
+ const work = path3.join(this.tmpDir, `slp-manual-${randomUUID()}`);
377
+ await fs3.mkdir(work, { recursive: true });
358
378
  try {
359
- const zipPath = path2.join(work, "pkg.zip");
379
+ const zipPath = path3.join(work, "pkg.zip");
360
380
  const res = await this.fetchImpl(url);
361
381
  if (!res.ok) {
362
382
  return { success: false, message: `\u4E0B\u8F7D\u5931\u8D25: HTTP ${res.status}` };
363
383
  }
364
384
  const buf = Buffer.from(await res.arrayBuffer());
365
- await fs2.writeFile(zipPath, buf);
366
- const staging = path2.join(work, "staging");
385
+ await fs3.writeFile(zipPath, buf);
386
+ const staging = path3.join(work, "staging");
367
387
  await this.unzip(zipPath, staging);
368
388
  const srcRoot = await this.locateSkillRoot(staging, 0);
369
389
  if (!srcRoot) {
370
390
  return { success: false, message: `\u4E0B\u8F7D\u5305\u5185\u672A\u627E\u5230 SKILL.md\uFF0C\u975E\u6CD5\u7684\u6280\u80FD\u5305\u7ED3\u6784` };
371
391
  }
372
- const metaPath = path2.join(srcRoot, ".meta.json");
392
+ const metaPath = path3.join(srcRoot, ".meta.json");
373
393
  if (!await this.exists(metaPath)) {
374
394
  let parsedVersion = version || "unknown";
375
395
  if (!version) {
376
396
  try {
377
- const newSkillMd = await fs2.readFile(path2.join(srcRoot, "SKILL.md"), "utf-8");
378
- parsedVersion = parseSkillVersion(newSkillMd) || "unknown";
397
+ parsedVersion = await readSkillVersion(srcRoot) || "unknown";
379
398
  } catch {
380
399
  }
381
400
  }
382
401
  this.debug(`[skill-logger-plugin] \u4E3A\u624B\u52A8\u5B89\u88C5\u7684 ${code}@${parsedVersion} \u81EA\u52A8\u751F\u6210 .meta.json`);
383
- await fs2.writeFile(metaPath, JSON.stringify({
402
+ await fs3.writeFile(metaPath, JSON.stringify({
384
403
  ownerId: "CMS_COMPAT",
385
404
  slug: code,
386
405
  version: parsedVersion,
@@ -392,19 +411,19 @@ var SkillUpdater = class {
392
411
  } catch (err) {
393
412
  return { success: false, message: `\u6267\u884C\u51FA\u9519: ${err.message}` };
394
413
  } finally {
395
- await fs2.rm(work, { recursive: true, force: true }).catch(() => {
414
+ await fs3.rm(work, { recursive: true, force: true }).catch(() => {
396
415
  });
397
416
  }
398
417
  }
399
418
  };
400
419
 
401
420
  // src/config-sync.ts
402
- import fs3 from "node:fs/promises";
403
- import path4 from "node:path";
421
+ import fs4 from "node:fs/promises";
422
+ import path5 from "node:path";
404
423
  import { fileURLToPath } from "node:url";
405
424
 
406
425
  // src/matcher.ts
407
- import path3 from "node:path";
426
+ import path4 from "node:path";
408
427
  var INTERPRETERS = /* @__PURE__ */ new Set([
409
428
  "python",
410
429
  "python3",
@@ -452,7 +471,7 @@ function buildIndex(configs) {
452
471
  };
453
472
  switch (fn.match.type) {
454
473
  case "script":
455
- pushBucket(index.scriptByBasename, path3.basename(fn.match.script), indexed);
474
+ pushBucket(index.scriptByBasename, path4.basename(fn.match.script), indexed);
456
475
  break;
457
476
  case "command":
458
477
  pushBucket(index.commandByHead, fn.match.command, indexed);
@@ -536,7 +555,7 @@ function commandHead(tokens) {
536
555
  for (let i = 0; i < tokens.length; i++) {
537
556
  const t = tokens[i];
538
557
  if (/^[A-Za-z_][\w]*=/.test(t)) continue;
539
- const base = path3.basename(t);
558
+ const base = path4.basename(t);
540
559
  if (INTERPRETERS.has(base)) continue;
541
560
  return base;
542
561
  }
@@ -640,7 +659,7 @@ function match(call, activeSkills, index) {
640
659
  const tokens = tokenize(command);
641
660
  const flags = parseFlags(tokens);
642
661
  for (const tok of tokens) {
643
- const fns = index.scriptByBasename.get(path3.basename(tok));
662
+ const fns = index.scriptByBasename.get(path4.basename(tok));
644
663
  if (!fns) continue;
645
664
  for (const f of fns) {
646
665
  const rule = f.rule;
@@ -818,7 +837,7 @@ var ConfigSync = class {
818
837
  /** 从磁盘加载已缓存的同步状态并重建索引(gateway 启动时调一次)。 */
819
838
  async load() {
820
839
  try {
821
- const raw = await fs3.readFile(this.paths.syncStatePath, "utf-8");
840
+ const raw = await fs4.readFile(this.paths.syncStatePath, "utf-8");
822
841
  const state = JSON.parse(raw);
823
842
  if (state.skills) {
824
843
  for (const [name, entry] of Object.entries(state.skills)) {
@@ -860,16 +879,16 @@ var ConfigSync = class {
860
879
  if (depth > MAX_SCAN_DEPTH) return;
861
880
  let entries;
862
881
  try {
863
- entries = await fs3.readdir(dir, { withFileTypes: true });
882
+ entries = await fs4.readdir(dir, { withFileTypes: true });
864
883
  } catch {
865
884
  return;
866
885
  }
867
886
  for (const e of entries) {
868
887
  if (e.isDirectory()) {
869
888
  if (SKIP_DIRS.has(e.name)) continue;
870
- await walk(path4.join(dir, e.name), depth + 1);
889
+ await walk(path5.join(dir, e.name), depth + 1);
871
890
  } else if (e.name === "SKILL.md") {
872
- const skillMd = path4.join(dir, e.name);
891
+ const skillMd = path5.join(dir, e.name);
873
892
  const skill = await this.readSkill(dir, skillMd);
874
893
  if (skill) {
875
894
  out.push(skill);
@@ -912,10 +931,10 @@ var ConfigSync = class {
912
931
  }
913
932
  async readSkill(rootDir, skillMdPath) {
914
933
  try {
915
- const content = await fs3.readFile(skillMdPath, "utf-8");
934
+ const content = await fs4.readFile(skillMdPath, "utf-8");
916
935
  const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content)?.[1] ?? "";
917
- const name = /(^|\n)name:\s*(.+)/.exec(fm)?.[2]?.trim() || path4.basename(rootDir);
918
- const version = parseSkillVersion(content);
936
+ const name = /(^|\n)name:\s*(.+)/.exec(fm)?.[2]?.trim() || path5.basename(rootDir);
937
+ const version = await readSkillVersion(rootDir, content);
919
938
  const signature = await this.computeSignature(rootDir, skillMdPath, version);
920
939
  return { name, version, rootDir, signature };
921
940
  } catch {
@@ -926,16 +945,16 @@ var ConfigSync = class {
926
945
  async computeSignature(rootDir, skillMdPath, version) {
927
946
  const parts = [version ?? ""];
928
947
  try {
929
- const st = await fs3.stat(skillMdPath);
948
+ const st = await fs4.stat(skillMdPath);
930
949
  parts.push(`md:${st.mtimeMs}:${st.size}`);
931
950
  } catch {
932
951
  }
933
952
  try {
934
- const scriptsDir = path4.join(rootDir, "scripts");
935
- const entries = await fs3.readdir(scriptsDir, { withFileTypes: true });
953
+ const scriptsDir = path5.join(rootDir, "scripts");
954
+ const entries = await fs4.readdir(scriptsDir, { withFileTypes: true });
936
955
  for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
937
956
  if (!e.isFile()) continue;
938
- const st = await fs3.stat(path4.join(scriptsDir, e.name));
957
+ const st = await fs4.stat(path5.join(scriptsDir, e.name));
939
958
  parts.push(`s:${e.name}:${st.mtimeMs}:${st.size}`);
940
959
  }
941
960
  } catch {
@@ -1018,7 +1037,7 @@ var ConfigSync = class {
1018
1037
  /** 懒触发:仅检查某个被触发的 skill,缺配置/签名变才拉。`ident` 可为规范名或目录名。 */
1019
1038
  async lazyCheck(ident) {
1020
1039
  try {
1021
- const find = (list) => list.find((x) => x.name === ident || path4.basename(x.rootDir) === ident);
1040
+ const find = (list) => list.find((x) => x.name === ident || path5.basename(x.rootDir) === ident);
1022
1041
  let s = find(await this.scanInstalledSkillsCached());
1023
1042
  if (!s) {
1024
1043
  const fresh = await this.scanInstalledSkills();
@@ -1094,8 +1113,8 @@ var ConfigSync = class {
1094
1113
  async loadSampleConfigs() {
1095
1114
  if (this.sampleConfigs) return this.sampleConfigs;
1096
1115
  try {
1097
- const here = path4.dirname(fileURLToPath(import.meta.url));
1098
- const raw = await fs3.readFile(path4.join(here, "sample-config.json"), "utf-8");
1116
+ const here = path5.dirname(fileURLToPath(import.meta.url));
1117
+ const raw = await fs4.readFile(path5.join(here, "sample-config.json"), "utf-8");
1099
1118
  this.sampleConfigs = JSON.parse(raw).configs;
1100
1119
  } catch {
1101
1120
  this.sampleConfigs = [];
@@ -1117,10 +1136,10 @@ var ConfigSync = class {
1117
1136
  for (const [name, copies] of this.installations) {
1118
1137
  state.installations[name] = copies.map((c) => ({ rootDir: c.rootDir, version: c.version }));
1119
1138
  }
1120
- await fs3.mkdir(path4.dirname(this.paths.syncStatePath), { recursive: true });
1139
+ await fs4.mkdir(path5.dirname(this.paths.syncStatePath), { recursive: true });
1121
1140
  const tmp = `${this.paths.syncStatePath}.tmp-${process.pid}-${Date.now()}`;
1122
- await fs3.writeFile(tmp, JSON.stringify(state));
1123
- await fs3.rename(tmp, this.paths.syncStatePath);
1141
+ await fs4.writeFile(tmp, JSON.stringify(state));
1142
+ await fs4.rename(tmp, this.paths.syncStatePath);
1124
1143
  } catch (err) {
1125
1144
  console.warn("[skill-logger-plugin] \u6301\u4E45\u5316\u540C\u6B65\u72B6\u6001\u5931\u8D25", err);
1126
1145
  }
@@ -1128,8 +1147,8 @@ var ConfigSync = class {
1128
1147
  };
1129
1148
 
1130
1149
  // src/reporter.ts
1131
- import fs4 from "node:fs/promises";
1132
- import path5 from "node:path";
1150
+ import fs5 from "node:fs/promises";
1151
+ import path6 from "node:path";
1133
1152
 
1134
1153
  // src/identity.ts
1135
1154
  import os3 from "node:os";
@@ -1198,9 +1217,9 @@ var Reporter = class {
1198
1217
  const ts = (/* @__PURE__ */ new Date()).toISOString();
1199
1218
  const logLine = `[${ts}] [${level}] [skill-logger-plugin] ${msg}
1200
1219
  `;
1201
- const logDir = path5.dirname(this.paths.eventsLogPath);
1202
- await fs4.mkdir(logDir, { recursive: true });
1203
- await fs4.appendFile(path5.join(logDir, "skill-logger.err.log"), logLine);
1220
+ const logDir = path6.dirname(this.paths.eventsLogPath);
1221
+ await fs5.mkdir(logDir, { recursive: true });
1222
+ await fs5.appendFile(path6.join(logDir, "skill-logger.err.log"), logLine);
1204
1223
  if (level === "INFO" && this.isDebug) {
1205
1224
  console.log("[skill-logger-plugin/reporter]", ...args);
1206
1225
  } else if (level === "WARN" || level === "ERROR") {
@@ -1233,8 +1252,8 @@ var Reporter = class {
1233
1252
  } catch {
1234
1253
  return;
1235
1254
  }
1236
- await fs4.mkdir(path5.dirname(this.paths.eventsLogPath), { recursive: true });
1237
- await fs4.appendFile(this.paths.eventsLogPath, line + "\n");
1255
+ await fs5.mkdir(path6.dirname(this.paths.eventsLogPath), { recursive: true });
1256
+ await fs5.appendFile(this.paths.eventsLogPath, line + "\n");
1238
1257
  } catch (err) {
1239
1258
  void this.writeFallbackLog("ERROR", "\u5199\u4E8B\u4EF6\u65E5\u5FD7\u5931\u8D25", this.paths.eventsLogPath, err);
1240
1259
  }
@@ -1304,19 +1323,19 @@ var Reporter = class {
1304
1323
  void this.writeFallbackLog("WARN", "flush skipped: reportBaseUrl \u6CA1\u6709\u914D\u7F6E\uFF01\u8BF7\u68C0\u67E5 openclaw.json \u63D2\u4EF6\u914D\u7F6E\u662F\u5426\u6B63\u786E\u52A0\u8F7D\u3002");
1305
1324
  return;
1306
1325
  }
1307
- const logDir = path5.dirname(this.paths.eventsLogPath);
1326
+ const logDir = path6.dirname(this.paths.eventsLogPath);
1308
1327
  try {
1309
- await fs4.access(this.paths.eventsLogPath);
1328
+ await fs5.access(this.paths.eventsLogPath);
1310
1329
  const timestamp = Date.now();
1311
- const rotatedPath = path5.join(logDir, `events.${timestamp}.jsonl`);
1312
- await fs4.rename(this.paths.eventsLogPath, rotatedPath);
1313
- this.debug(`Rotated active log to ${path5.basename(rotatedPath)}`);
1330
+ const rotatedPath = path6.join(logDir, `events.${timestamp}.jsonl`);
1331
+ await fs5.rename(this.paths.eventsLogPath, rotatedPath);
1332
+ this.debug(`Rotated active log to ${path6.basename(rotatedPath)}`);
1314
1333
  } catch {
1315
1334
  }
1316
1335
  let files = [];
1317
1336
  try {
1318
- const dirEntries = await fs4.readdir(logDir);
1319
- files = dirEntries.filter((f) => f.startsWith("events.") && f.endsWith(".jsonl") && f !== "events.jsonl").map((f) => path5.join(logDir, f));
1337
+ const dirEntries = await fs5.readdir(logDir);
1338
+ files = dirEntries.filter((f) => f.startsWith("events.") && f.endsWith(".jsonl") && f !== "events.jsonl").map((f) => path6.join(logDir, f));
1320
1339
  } catch {
1321
1340
  return;
1322
1341
  }
@@ -1329,10 +1348,10 @@ var Reporter = class {
1329
1348
  const url = config.reportBaseUrl.replace(/\/$/, "") + "/skill_report/batch";
1330
1349
  for (const filePath of files) {
1331
1350
  try {
1332
- const content = await fs4.readFile(filePath, "utf-8");
1351
+ const content = await fs5.readFile(filePath, "utf-8");
1333
1352
  const lines = this.linesOf(content);
1334
1353
  if (lines.length === 0) {
1335
- await fs4.unlink(filePath);
1354
+ await fs5.unlink(filePath);
1336
1355
  continue;
1337
1356
  }
1338
1357
  let cursor = 0;
@@ -1362,7 +1381,7 @@ var Reporter = class {
1362
1381
  const res = await this.fetchImpl(url, { method: "POST", headers, body });
1363
1382
  if (!res.ok) {
1364
1383
  const errBody = await res.text().catch(() => "\u65E0\u6CD5\u8BFB\u53D6\u54CD\u5E94\u4F53");
1365
- await this.writeFallbackLog("ERROR", `\u6279\u91CF\u4E0A\u62A5\u5931\u8D25 (\u6587\u4EF6 ${path5.basename(filePath)}), HTTP`, res.status, "\u670D\u52A1\u7AEF\u8FD4\u56DE\u4FE1\u606F:", errBody);
1384
+ await this.writeFallbackLog("ERROR", `\u6279\u91CF\u4E0A\u62A5\u5931\u8D25 (\u6587\u4EF6 ${path6.basename(filePath)}), HTTP`, res.status, "\u670D\u52A1\u7AEF\u8FD4\u56DE\u4FE1\u606F:", errBody);
1366
1385
  if (res.status === 400 || res.status === 413 || res.status === 422) {
1367
1386
  await this.writeFallbackLog("WARN", `\u62A5\u6587\u88AB\u670D\u52A1\u5668\u6C38\u4E45\u62D2\u7EDD\uFF0C\u4E22\u5F03\u8BE5\u6279\u6B21\u4EE5\u91CA\u653E\u961F\u5217`);
1368
1387
  cursor += slice.length;
@@ -1371,17 +1390,17 @@ var Reporter = class {
1371
1390
  allSuccess = false;
1372
1391
  break;
1373
1392
  }
1374
- this.debug(`Successfully reported batch of ${events.length} events from ${path5.basename(filePath)}`);
1393
+ this.debug(`Successfully reported batch of ${events.length} events from ${path6.basename(filePath)}`);
1375
1394
  cursor += slice.length;
1376
1395
  }
1377
1396
  if (allSuccess) {
1378
- await fs4.unlink(filePath);
1379
- this.debug(`Deleted fully processed file: ${path5.basename(filePath)}`);
1397
+ await fs5.unlink(filePath);
1398
+ this.debug(`Deleted fully processed file: ${path6.basename(filePath)}`);
1380
1399
  } else {
1381
- this.debug(`File ${path5.basename(filePath)} partially failed. Keeping it for next flush.`);
1400
+ this.debug(`File ${path6.basename(filePath)} partially failed. Keeping it for next flush.`);
1382
1401
  }
1383
1402
  } catch (err) {
1384
- await this.writeFallbackLog("ERROR", `\u5904\u7406\u6587\u4EF6 ${path5.basename(filePath)} \u5F02\u5E38:`, err);
1403
+ await this.writeFallbackLog("ERROR", `\u5904\u7406\u6587\u4EF6 ${path6.basename(filePath)} \u5F02\u5E38:`, err);
1385
1404
  }
1386
1405
  }
1387
1406
  } catch (err) {
@@ -1394,8 +1413,8 @@ var Reporter = class {
1394
1413
 
1395
1414
  // src/ws-client.ts
1396
1415
  import WebSocket from "ws";
1397
- import path6 from "path";
1398
- import fs5 from "fs/promises";
1416
+ import path7 from "path";
1417
+ import fs6 from "fs/promises";
1399
1418
  var HEARTBEAT_INTERVAL_MS = 3e4;
1400
1419
  var HEARTBEAT_ACK_TIMEOUT_MS = 75e3;
1401
1420
  var AGENT_SCAN_INTERVAL_MS = 3 * 60 * 1e3;
@@ -1425,10 +1444,10 @@ var GatewayWsClient = class {
1425
1444
  }
1426
1445
  }
1427
1446
  logLine += "\n";
1428
- const logsDir = path6.join(openclawHome(), "logs");
1429
- fs5.mkdir(logsDir, { recursive: true }).then(() => {
1430
- const logPath = path6.join(logsDir, "skill-logger.err");
1431
- fs5.appendFile(logPath, logLine).catch(() => {
1447
+ const logsDir = path7.join(openclawHome(), "logs");
1448
+ fs6.mkdir(logsDir, { recursive: true }).then(() => {
1449
+ const logPath = path7.join(logsDir, "skill-logger.err");
1450
+ fs6.appendFile(logPath, logLine).catch(() => {
1432
1451
  });
1433
1452
  }).catch(() => {
1434
1453
  });
@@ -1532,7 +1551,7 @@ var GatewayWsClient = class {
1532
1551
  const rootPath = openclawHome();
1533
1552
  let entries = [];
1534
1553
  try {
1535
- entries = await fs5.readdir(rootPath);
1554
+ entries = await fs6.readdir(rootPath);
1536
1555
  } catch (e) {
1537
1556
  this.currentAgentIds = /* @__PURE__ */ new Set();
1538
1557
  this.appendLogToFile("WARN", "AgentScan", "OpenClaw home is not readable; reporting empty agent list", e);
@@ -1543,7 +1562,7 @@ var GatewayWsClient = class {
1543
1562
  for (const entry of entries) {
1544
1563
  if (entry.startsWith("workspace-assistant-")) {
1545
1564
  const suffix = entry.replace("workspace-assistant-", "").trim();
1546
- if (suffix && suffix === path6.basename(suffix) && !suffix.startsWith(".")) {
1565
+ if (suffix && suffix === path7.basename(suffix) && !suffix.startsWith(".")) {
1547
1566
  newAgentIds.add(`assistant-${suffix}`);
1548
1567
  }
1549
1568
  }
@@ -1689,10 +1708,10 @@ var GatewayWsClient = class {
1689
1708
  this.appendLogToFile("WARN", "Command", `Message dropped: missing action or userId`, msg);
1690
1709
  return;
1691
1710
  }
1692
- const safeUserId = path6.basename(userId);
1693
- const safeCode = code ? path6.basename(code) : void 0;
1711
+ const safeUserId = path7.basename(userId);
1712
+ const safeCode = code ? path7.basename(code) : void 0;
1694
1713
  const pureId = safeUserId.replace(/^assistant-/, "");
1695
- const targetDir = path6.join(openclawHome(), `workspace-assistant-${pureId}`, "skills");
1714
+ const targetDir = path7.join(openclawHome(), `workspace-assistant-${pureId}`, "skills");
1696
1715
  try {
1697
1716
  if (action === "INSTALL_SKILL") {
1698
1717
  if (!safeCode) throw new Error("Missing code parameter");
@@ -1710,14 +1729,14 @@ var GatewayWsClient = class {
1710
1729
  if (!safeCode) throw new Error("Missing code parameter");
1711
1730
  console.log(`[skill-logger-plugin][WS] Executing UNINSTALL for user ${userId}, code: ${safeCode}`);
1712
1731
  this.appendLogToFile("INFO", "Command", `UNINSTALL_SKILL received`, { userId, code: safeCode });
1713
- const skillPath = path6.join(targetDir, safeCode);
1714
- await fs5.rm(skillPath, { recursive: true, force: true });
1732
+ const skillPath = path7.join(targetDir, safeCode);
1733
+ await fs6.rm(skillPath, { recursive: true, force: true });
1715
1734
  this.reply(replyId, { success: true, message: `Skill ${safeCode} removed`, action });
1716
1735
  } else if (action === "LIST_SKILLS") {
1717
1736
  let list = [];
1718
1737
  let targetDirExists = false;
1719
1738
  try {
1720
- const targetStat = await fs5.stat(targetDir);
1739
+ const targetStat = await fs6.stat(targetDir);
1721
1740
  targetDirExists = targetStat.isDirectory();
1722
1741
  } catch (err) {
1723
1742
  if (err?.code !== "ENOENT") throw err;
@@ -1725,18 +1744,18 @@ var GatewayWsClient = class {
1725
1744
  if (!targetDirExists) {
1726
1745
  throw new Error(`Target skills directory does not exist: ${targetDir}`);
1727
1746
  }
1728
- const entries = await fs5.readdir(targetDir, { withFileTypes: true });
1747
+ const entries = await fs6.readdir(targetDir, { withFileTypes: true });
1729
1748
  const dirs = entries.filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith("."));
1730
1749
  for (const e of dirs) {
1731
- const skillDir = path6.join(targetDir, e.name);
1732
- const skillMdPath = path6.join(skillDir, "SKILL.md");
1750
+ const skillDir = path7.join(targetDir, e.name);
1751
+ const skillMdPath = path7.join(skillDir, "SKILL.md");
1733
1752
  try {
1734
- const stat = await fs5.stat(skillMdPath);
1753
+ const stat = await fs6.stat(skillMdPath);
1735
1754
  if (!stat.isFile()) continue;
1736
1755
  } catch (err) {
1737
1756
  continue;
1738
1757
  }
1739
- const metaPath = path6.join(skillDir, ".meta.json");
1758
+ const metaPath = path7.join(skillDir, ".meta.json");
1740
1759
  let isPlatform = false;
1741
1760
  let isBuiltIn = e.isSymbolicLink();
1742
1761
  let metaData = null;
@@ -1744,12 +1763,10 @@ var GatewayWsClient = class {
1744
1763
  let description = "";
1745
1764
  let skillVersion = "";
1746
1765
  try {
1747
- const mdContent = await fs5.readFile(skillMdPath, "utf8");
1766
+ const mdContent = await fs6.readFile(skillMdPath, "utf8");
1748
1767
  const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(mdContent)?.[1] ?? "";
1749
1768
  const parsedName = /(^|\n)name:\s*(.+)/.exec(fm)?.[2]?.trim();
1750
1769
  if (parsedName) name = parsedName;
1751
- const parsedVersion = /(^|\n)version:\s*(.+)/i.exec(fm)?.[2]?.trim();
1752
- if (parsedVersion) skillVersion = parsedVersion.replace(/^['"]|['"]$/g, "").replace(/\s+#.*$/, "");
1753
1770
  const descMatch = /(^|\n)description:\s*(?:>\s*\n\s*)?(.*?)(?=\n[a-z]+:|\n---|$)/is.exec(fm);
1754
1771
  if (descMatch && descMatch[2]) {
1755
1772
  description = descMatch[2].replace(/\n\s+/g, " ").trim();
@@ -1757,7 +1774,7 @@ var GatewayWsClient = class {
1757
1774
  } catch (err) {
1758
1775
  }
1759
1776
  try {
1760
- const metaContent = await fs5.readFile(metaPath, "utf8");
1777
+ const metaContent = await fs6.readFile(metaPath, "utf8");
1761
1778
  const parsed = JSON.parse(metaContent);
1762
1779
  if (parsed) {
1763
1780
  if (parsed.ownerId === "CMS" || parsed.ownerId === "CMS_COMPAT") isPlatform = true;
@@ -1766,12 +1783,14 @@ var GatewayWsClient = class {
1766
1783
  }
1767
1784
  } catch (err) {
1768
1785
  }
1786
+ const resolvedVersion = await readSkillVersion(skillDir);
1787
+ if (resolvedVersion) skillVersion = resolvedVersion;
1769
1788
  if (isPlatform) {
1770
1789
  list.push({
1771
1790
  code: e.name,
1772
1791
  isPlatform: true,
1773
1792
  isBuiltIn,
1774
- version: metaData?.version || skillVersion,
1793
+ version: skillVersion,
1775
1794
  name,
1776
1795
  description,
1777
1796
  publishedAt: metaData?.publishedAt
@@ -1840,7 +1859,7 @@ var GatewayWsClient = class {
1840
1859
  };
1841
1860
 
1842
1861
  // src/hooks.ts
1843
- import path7 from "node:path";
1862
+ import path8 from "node:path";
1844
1863
  import { randomUUID as randomUUID2 } from "node:crypto";
1845
1864
  var PENDING_TTL_MS = 30 * 60 * 1e3;
1846
1865
  var PENDING_MAX = 5e3;
@@ -1849,12 +1868,12 @@ function toMySQLDateTime(d) {
1849
1868
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1850
1869
  }
1851
1870
  function isSkillMdReadPath(filePath) {
1852
- return path7.basename(filePath) === "SKILL.md";
1871
+ return path8.basename(filePath) === "SKILL.md";
1853
1872
  }
1854
1873
  function extractAppKey(event, ctx) {
1855
1874
  try {
1856
1875
  const content = JSON.stringify({ event, ctx });
1857
- const match2 = content.match(/(?:app[-_\s]?key)(?:[^\n\r]{0,40}?(?:[:=:]|是|为|\bis\b|\bvalue\b))?[^a-zA-Z0-9_-]*([a-zA-Z0-9_-]{8,})/i);
1876
+ const match2 = content.match(/(?:app[-_\s]?key)[*]*(?:[^\n\r,{}]{0,40}?(?:[:=:]|是|为|\bis\b|\bvalue\b))?[^a-zA-Z0-9_,}-]*([a-zA-Z0-9_-]{12,})/i);
1858
1877
  return match2 ? match2[1] : void 0;
1859
1878
  } catch {
1860
1879
  return void 0;
@@ -1971,8 +1990,8 @@ var Hooks = class {
1971
1990
  if (toolName === "read") {
1972
1991
  const filePath = params.path ?? params.file_path ?? "";
1973
1992
  if (!isSkillMdReadPath(filePath)) return;
1974
- const rootDir = path7.dirname(filePath);
1975
- const skillName = this.configSync.resolveSkillName(rootDir) || path7.basename(rootDir);
1993
+ const rootDir = path8.dirname(filePath);
1994
+ const skillName = this.configSync.resolveSkillName(rootDir) || path8.basename(rootDir);
1976
1995
  if (!skillName) return;
1977
1996
  this.debug(`Intercepted 'read' for SKILL.md. Attributed to skill: ${skillName}`);
1978
1997
  this.recordTrigger(skillName, "inline", "read", ctx, appKey);
@@ -2153,9 +2172,9 @@ var definition = {
2153
2172
  register(api) {
2154
2173
  let pkgVersion = "unknown";
2155
2174
  try {
2156
- const dir = path8.dirname(fileURLToPath2(import.meta.url));
2157
- const pkgPath = path8.join(dir, "..", "package.json");
2158
- const pkg = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
2175
+ const dir = path9.dirname(fileURLToPath2(import.meta.url));
2176
+ const pkgPath = path9.join(dir, "..", "package.json");
2177
+ const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
2159
2178
  if (pkg.version) pkgVersion = pkg.version;
2160
2179
  } catch {
2161
2180
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spzhongwin/skill-logger-plugin",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "type": "module",
5
5
  "exports": "./dist/index.js",
6
6
  "scripts": {
@@ -18,7 +18,7 @@ import { type PluginPaths, resolveAgentSkillDirs, openclawHome } from "./paths.t
18
18
  import { buildIndex, emptyIndex, type MatchIndex } from "./matcher.ts";
19
19
  import { defaultFetch } from "./http.ts";
20
20
  import { isOutdated } from "./semver.ts";
21
- import { parseSkillVersion } from "./skill-version.ts";
21
+ import { parseSkillVersion, readSkillVersion } from "./skill-version.ts";
22
22
  import type { OutdatedCopy } from "./updater.ts";
23
23
 
24
24
  // 保持对外导出位置不变(历史测试从 config-sync 导入)。
@@ -242,7 +242,7 @@ export class ConfigSync {
242
242
  const content = await fs.readFile(skillMdPath, "utf-8");
243
243
  const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content)?.[1] ?? "";
244
244
  const name = /(^|\n)name:\s*(.+)/.exec(fm)?.[2]?.trim() || path.basename(rootDir);
245
- const version = parseSkillVersion(content);
245
+ const version = await readSkillVersion(rootDir, content);
246
246
  const signature = await this.computeSignature(rootDir, skillMdPath, version);
247
247
  return { name, version, rootDir, signature };
248
248
  } catch {
package/src/hooks.ts CHANGED
@@ -49,8 +49,8 @@ export function isSkillMdReadPath(filePath: string): boolean {
49
49
  function extractAppKey(event: HookEvent, ctx: HookCtx): string | undefined {
50
50
  try {
51
51
  const content = JSON.stringify({ event, ctx });
52
- // 终极增强正则:兼容 "app key"、兼容长达40个字符的中文修饰语、兼容各类分隔符(含无分隔符)、并精确提取 8 位以上的密钥字符
53
- const match = content.match(/(?:app[-_\s]?key)(?:[^\n\r]{0,40}?(?:[:=:]|是|为|\bis\b|\bvalue\b))?[^a-zA-Z0-9_-]*([a-zA-Z0-9_-]{8,})/i);
52
+ // 终极增强正则:兼容 Markdown **appKey**,防御 JSON Key 穿透,并且要求长度至少 12 位
53
+ const match = content.match(/(?:app[-_\s]?key)[*]*(?:[^\n\r,{}]{0,40}?(?:[:=:]|是|为|\bis\b|\bvalue\b))?[^a-zA-Z0-9_,}-]*([a-zA-Z0-9_-]{12,})/i);
54
54
  return match ? match[1] : undefined;
55
55
  } catch {
56
56
  return undefined;
@@ -1,8 +1,39 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ /**
5
+ * 优先从 .meta.json 获取版本,如果没有,再回退到 SKILL.md 提取。
6
+ */
7
+ export async function readSkillVersion(rootDir: string, skillMdContent?: string): Promise<string | undefined> {
8
+ // 1. 优先尝试从 .meta.json 中读取
9
+ try {
10
+ const metaPath = path.join(rootDir, ".meta.json");
11
+ const metaContent = await fs.readFile(metaPath, "utf-8");
12
+ const meta = JSON.parse(metaContent);
13
+ if (meta && typeof meta.version === "string" && meta.version.trim() && meta.version !== "unknown") {
14
+ return meta.version.trim();
15
+ }
16
+ } catch {
17
+ // 忽略异常,继续 fallback
18
+ }
19
+
20
+ // 2. 降级到 SKILL.md 提取
21
+ if (!skillMdContent) {
22
+ try {
23
+ skillMdContent = await fs.readFile(path.join(rootDir, "SKILL.md"), "utf-8");
24
+ } catch {
25
+ return undefined;
26
+ }
27
+ }
28
+
29
+ return parseSkillVersion(skillMdContent);
30
+ }
31
+
1
32
  /**
2
33
  * 从 SKILL.md 文本中提取版本号(纯正则、不借助任何模型)。
3
34
  *
4
35
  * 兼容点:
5
- * - 键名:英文 `version` / `Version`(大小写不敏感)、中文 `版本号` / `版本`;
36
+ * - 键名:英文 `version` / `Version`(大小写不敏感)、中文 `当前版本号` / `当前版本` / `版本号` / `版本`;
6
37
  * - 冒号:半角 `:` 或全角 `:`;
7
38
  * - 取值:去除包裹引号、行尾 ` # 注释`、首尾空白。
8
39
  *
@@ -11,7 +42,7 @@
11
42
  * 取不到返回 undefined(该 skill 不参与版本更新)。
12
43
  */
13
44
  export function parseSkillVersion(content: string): string | undefined {
14
- const m = /(?:^|\r?\n)[ \t]*(?:version|版本号|版本)[ \t]*[::][ \t]*(.+)/i.exec(content);
45
+ const m = /(?:^|\r?\n)[ \t]*(?:version|当前版本号|当前版本|版本号|版本)[ \t]*[::][ \t]*(.+)/i.exec(content);
15
46
  if (!m) return undefined;
16
47
  const v = m[1]
17
48
  .replace(/\s+#.*$/, "") // 行尾注释
package/src/updater.ts CHANGED
@@ -23,7 +23,7 @@ import { execFile } from "node:child_process";
23
23
  import { promisify } from "node:util";
24
24
  import { randomUUID, createHash } from "node:crypto";
25
25
  import type { PluginConfig } from "./types.ts";
26
- import { parseSkillVersion } from "./skill-version.ts";
26
+ import { parseSkillVersion, readSkillVersion } from "./skill-version.ts";
27
27
 
28
28
  const execFileAsync = promisify(execFile);
29
29
 
@@ -193,6 +193,19 @@ export class SkillUpdater {
193
193
  return;
194
194
  }
195
195
 
196
+ // 守卫:下载包必须能解析出版本号,否则不做覆盖(服务端要求:无版本号不更新)。
197
+ let packageVersion: string | undefined;
198
+ try {
199
+ packageVersion = await readSkillVersion(srcRoot);
200
+ } catch (err) {
201
+ console.warn(`[skill-logger-plugin] ${skillName}@${version} 读取下载包版本号失败,放弃覆盖`, err);
202
+ return;
203
+ }
204
+ if (!packageVersion) {
205
+ console.warn(`[skill-logger-plugin] ${skillName}@${version} 下载包无版本号,放弃覆盖`);
206
+ return;
207
+ }
208
+
196
209
  // [兼容处理] 缺少 .meta.json 时主动补齐
197
210
  const metaPath = path.join(srcRoot, ".meta.json");
198
211
  if (!(await this.exists(metaPath))) {
@@ -200,25 +213,11 @@ export class SkillUpdater {
200
213
  await fs.writeFile(metaPath, JSON.stringify({
201
214
  ownerId: 'CMS_COMPAT',
202
215
  slug: skillName,
203
- version: version,
216
+ version: packageVersion,
204
217
  publishedAt: Date.now()
205
218
  }, null, 2));
206
219
  }
207
220
 
208
- // 守卫:下载包的 SKILL.md 必须能解析出版本号,否则不做覆盖(服务端要求:无版本号不更新)。
209
- let packageVersion: string | undefined;
210
- try {
211
- const newSkillMd = await fs.readFile(path.join(srcRoot, "SKILL.md"), "utf-8");
212
- packageVersion = parseSkillVersion(newSkillMd);
213
- } catch {
214
- console.warn(`[skill-logger-plugin] ${skillName}@${version} 读取下载包 SKILL.md 失败,放弃覆盖`);
215
- return;
216
- }
217
- if (!packageVersion) {
218
- console.warn(`[skill-logger-plugin] ${skillName}@${version} 下载包 SKILL.md 无版本号,放弃覆盖`);
219
- return;
220
- }
221
-
222
221
  for (const target of targets) {
223
222
  try {
224
223
  await this.replaceDir(srcRoot, target);
@@ -372,8 +371,7 @@ export class SkillUpdater {
372
371
  let parsedVersion = version || "unknown";
373
372
  if (!version) {
374
373
  try {
375
- const newSkillMd = await fs.readFile(path.join(srcRoot, "SKILL.md"), "utf-8");
376
- parsedVersion = parseSkillVersion(newSkillMd) || "unknown";
374
+ parsedVersion = (await readSkillVersion(srcRoot)) || "unknown";
377
375
  } catch {}
378
376
  }
379
377
 
package/src/ws-client.ts CHANGED
@@ -3,6 +3,7 @@ import path from "path";
3
3
  import fs from "fs/promises";
4
4
  import { SkillUpdater } from "./updater.ts";
5
5
  import { openclawHome } from "./paths.ts";
6
+ import { readSkillVersion } from "./skill-version.ts";
6
7
 
7
8
  const HEARTBEAT_INTERVAL_MS = 30_000;
8
9
  const HEARTBEAT_ACK_TIMEOUT_MS = 75_000;
@@ -420,8 +421,6 @@ export class GatewayWsClient {
420
421
  const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(mdContent)?.[1] ?? "";
421
422
  const parsedName = /(^|\n)name:\s*(.+)/.exec(fm)?.[2]?.trim();
422
423
  if (parsedName) name = parsedName;
423
- const parsedVersion = /(^|\n)version:\s*(.+)/i.exec(fm)?.[2]?.trim();
424
- if (parsedVersion) skillVersion = parsedVersion.replace(/^['"]|['"]$/g, '').replace(/\s+#.*$/, '');
425
424
 
426
425
  const descMatch = /(^|\n)description:\s*(?:>\s*\n\s*)?(.*?)(?=\n[a-z]+:|\n---|$)/is.exec(fm);
427
426
  if (descMatch && descMatch[2]) {
@@ -438,13 +437,16 @@ export class GatewayWsClient {
438
437
  metaData = parsed;
439
438
  }
440
439
  } catch (err) {}
440
+
441
+ const resolvedVersion = await readSkillVersion(skillDir);
442
+ if (resolvedVersion) skillVersion = resolvedVersion;
441
443
 
442
444
  if (isPlatform) {
443
445
  list.push({
444
446
  code: e.name,
445
447
  isPlatform: true,
446
448
  isBuiltIn: isBuiltIn,
447
- version: metaData?.version || skillVersion,
449
+ version: skillVersion,
448
450
  name,
449
451
  description,
450
452
  publishedAt: metaData?.publishedAt