@spzhongwin/skill-logger-plugin 1.0.2 → 1.0.3

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 readSkillVersion2(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);
253
+ packageVersion = await readSkillVersion(srcRoot);
244
254
  } catch {
245
- console.warn(`[skill-logger-plugin] ${skillName}@${version} \u8BFB\u53D6\u4E0B\u8F7D\u5305 SKILL.md \u5931\u8D25\uFF0C\u653E\u5F03\u8986\u76D6`);
255
+ console.warn(`[skill-logger-plugin] ${skillName}@${version} \u8BFB\u53D6\u4E0B\u8F7D\u5305\u7248\u672C\u53F7\u5931\u8D25\uFF0C\u653E\u5F03\u8986\u76D6`);
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,38 @@ 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");
397
+ const newSkillMd = await fs3.readFile(path3.join(srcRoot, "SKILL.md"), "utf-8");
378
398
  parsedVersion = parseSkillVersion(newSkillMd) || "unknown";
379
399
  } catch {
380
400
  }
381
401
  }
382
402
  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({
403
+ await fs3.writeFile(metaPath, JSON.stringify({
384
404
  ownerId: "CMS_COMPAT",
385
405
  slug: code,
386
406
  version: parsedVersion,
@@ -392,19 +412,19 @@ var SkillUpdater = class {
392
412
  } catch (err) {
393
413
  return { success: false, message: `\u6267\u884C\u51FA\u9519: ${err.message}` };
394
414
  } finally {
395
- await fs2.rm(work, { recursive: true, force: true }).catch(() => {
415
+ await fs3.rm(work, { recursive: true, force: true }).catch(() => {
396
416
  });
397
417
  }
398
418
  }
399
419
  };
400
420
 
401
421
  // src/config-sync.ts
402
- import fs3 from "node:fs/promises";
403
- import path4 from "node:path";
422
+ import fs4 from "node:fs/promises";
423
+ import path5 from "node:path";
404
424
  import { fileURLToPath } from "node:url";
405
425
 
406
426
  // src/matcher.ts
407
- import path3 from "node:path";
427
+ import path4 from "node:path";
408
428
  var INTERPRETERS = /* @__PURE__ */ new Set([
409
429
  "python",
410
430
  "python3",
@@ -452,7 +472,7 @@ function buildIndex(configs) {
452
472
  };
453
473
  switch (fn.match.type) {
454
474
  case "script":
455
- pushBucket(index.scriptByBasename, path3.basename(fn.match.script), indexed);
475
+ pushBucket(index.scriptByBasename, path4.basename(fn.match.script), indexed);
456
476
  break;
457
477
  case "command":
458
478
  pushBucket(index.commandByHead, fn.match.command, indexed);
@@ -536,7 +556,7 @@ function commandHead(tokens) {
536
556
  for (let i = 0; i < tokens.length; i++) {
537
557
  const t = tokens[i];
538
558
  if (/^[A-Za-z_][\w]*=/.test(t)) continue;
539
- const base = path3.basename(t);
559
+ const base = path4.basename(t);
540
560
  if (INTERPRETERS.has(base)) continue;
541
561
  return base;
542
562
  }
@@ -640,7 +660,7 @@ function match(call, activeSkills, index) {
640
660
  const tokens = tokenize(command);
641
661
  const flags = parseFlags(tokens);
642
662
  for (const tok of tokens) {
643
- const fns = index.scriptByBasename.get(path3.basename(tok));
663
+ const fns = index.scriptByBasename.get(path4.basename(tok));
644
664
  if (!fns) continue;
645
665
  for (const f of fns) {
646
666
  const rule = f.rule;
@@ -818,7 +838,7 @@ var ConfigSync = class {
818
838
  /** 从磁盘加载已缓存的同步状态并重建索引(gateway 启动时调一次)。 */
819
839
  async load() {
820
840
  try {
821
- const raw = await fs3.readFile(this.paths.syncStatePath, "utf-8");
841
+ const raw = await fs4.readFile(this.paths.syncStatePath, "utf-8");
822
842
  const state = JSON.parse(raw);
823
843
  if (state.skills) {
824
844
  for (const [name, entry] of Object.entries(state.skills)) {
@@ -860,16 +880,16 @@ var ConfigSync = class {
860
880
  if (depth > MAX_SCAN_DEPTH) return;
861
881
  let entries;
862
882
  try {
863
- entries = await fs3.readdir(dir, { withFileTypes: true });
883
+ entries = await fs4.readdir(dir, { withFileTypes: true });
864
884
  } catch {
865
885
  return;
866
886
  }
867
887
  for (const e of entries) {
868
888
  if (e.isDirectory()) {
869
889
  if (SKIP_DIRS.has(e.name)) continue;
870
- await walk(path4.join(dir, e.name), depth + 1);
890
+ await walk(path5.join(dir, e.name), depth + 1);
871
891
  } else if (e.name === "SKILL.md") {
872
- const skillMd = path4.join(dir, e.name);
892
+ const skillMd = path5.join(dir, e.name);
873
893
  const skill = await this.readSkill(dir, skillMd);
874
894
  if (skill) {
875
895
  out.push(skill);
@@ -912,10 +932,10 @@ var ConfigSync = class {
912
932
  }
913
933
  async readSkill(rootDir, skillMdPath) {
914
934
  try {
915
- const content = await fs3.readFile(skillMdPath, "utf-8");
935
+ const content = await fs4.readFile(skillMdPath, "utf-8");
916
936
  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);
937
+ const name = /(^|\n)name:\s*(.+)/.exec(fm)?.[2]?.trim() || path5.basename(rootDir);
938
+ const version = await readSkillVersion2(rootDir, content);
919
939
  const signature = await this.computeSignature(rootDir, skillMdPath, version);
920
940
  return { name, version, rootDir, signature };
921
941
  } catch {
@@ -926,16 +946,16 @@ var ConfigSync = class {
926
946
  async computeSignature(rootDir, skillMdPath, version) {
927
947
  const parts = [version ?? ""];
928
948
  try {
929
- const st = await fs3.stat(skillMdPath);
949
+ const st = await fs4.stat(skillMdPath);
930
950
  parts.push(`md:${st.mtimeMs}:${st.size}`);
931
951
  } catch {
932
952
  }
933
953
  try {
934
- const scriptsDir = path4.join(rootDir, "scripts");
935
- const entries = await fs3.readdir(scriptsDir, { withFileTypes: true });
954
+ const scriptsDir = path5.join(rootDir, "scripts");
955
+ const entries = await fs4.readdir(scriptsDir, { withFileTypes: true });
936
956
  for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
937
957
  if (!e.isFile()) continue;
938
- const st = await fs3.stat(path4.join(scriptsDir, e.name));
958
+ const st = await fs4.stat(path5.join(scriptsDir, e.name));
939
959
  parts.push(`s:${e.name}:${st.mtimeMs}:${st.size}`);
940
960
  }
941
961
  } catch {
@@ -1018,7 +1038,7 @@ var ConfigSync = class {
1018
1038
  /** 懒触发:仅检查某个被触发的 skill,缺配置/签名变才拉。`ident` 可为规范名或目录名。 */
1019
1039
  async lazyCheck(ident) {
1020
1040
  try {
1021
- const find = (list) => list.find((x) => x.name === ident || path4.basename(x.rootDir) === ident);
1041
+ const find = (list) => list.find((x) => x.name === ident || path5.basename(x.rootDir) === ident);
1022
1042
  let s = find(await this.scanInstalledSkillsCached());
1023
1043
  if (!s) {
1024
1044
  const fresh = await this.scanInstalledSkills();
@@ -1094,8 +1114,8 @@ var ConfigSync = class {
1094
1114
  async loadSampleConfigs() {
1095
1115
  if (this.sampleConfigs) return this.sampleConfigs;
1096
1116
  try {
1097
- const here = path4.dirname(fileURLToPath(import.meta.url));
1098
- const raw = await fs3.readFile(path4.join(here, "sample-config.json"), "utf-8");
1117
+ const here = path5.dirname(fileURLToPath(import.meta.url));
1118
+ const raw = await fs4.readFile(path5.join(here, "sample-config.json"), "utf-8");
1099
1119
  this.sampleConfigs = JSON.parse(raw).configs;
1100
1120
  } catch {
1101
1121
  this.sampleConfigs = [];
@@ -1117,10 +1137,10 @@ var ConfigSync = class {
1117
1137
  for (const [name, copies] of this.installations) {
1118
1138
  state.installations[name] = copies.map((c) => ({ rootDir: c.rootDir, version: c.version }));
1119
1139
  }
1120
- await fs3.mkdir(path4.dirname(this.paths.syncStatePath), { recursive: true });
1140
+ await fs4.mkdir(path5.dirname(this.paths.syncStatePath), { recursive: true });
1121
1141
  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);
1142
+ await fs4.writeFile(tmp, JSON.stringify(state));
1143
+ await fs4.rename(tmp, this.paths.syncStatePath);
1124
1144
  } catch (err) {
1125
1145
  console.warn("[skill-logger-plugin] \u6301\u4E45\u5316\u540C\u6B65\u72B6\u6001\u5931\u8D25", err);
1126
1146
  }
@@ -1128,8 +1148,8 @@ var ConfigSync = class {
1128
1148
  };
1129
1149
 
1130
1150
  // src/reporter.ts
1131
- import fs4 from "node:fs/promises";
1132
- import path5 from "node:path";
1151
+ import fs5 from "node:fs/promises";
1152
+ import path6 from "node:path";
1133
1153
 
1134
1154
  // src/identity.ts
1135
1155
  import os3 from "node:os";
@@ -1198,9 +1218,9 @@ var Reporter = class {
1198
1218
  const ts = (/* @__PURE__ */ new Date()).toISOString();
1199
1219
  const logLine = `[${ts}] [${level}] [skill-logger-plugin] ${msg}
1200
1220
  `;
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);
1221
+ const logDir = path6.dirname(this.paths.eventsLogPath);
1222
+ await fs5.mkdir(logDir, { recursive: true });
1223
+ await fs5.appendFile(path6.join(logDir, "skill-logger.err.log"), logLine);
1204
1224
  if (level === "INFO" && this.isDebug) {
1205
1225
  console.log("[skill-logger-plugin/reporter]", ...args);
1206
1226
  } else if (level === "WARN" || level === "ERROR") {
@@ -1233,8 +1253,8 @@ var Reporter = class {
1233
1253
  } catch {
1234
1254
  return;
1235
1255
  }
1236
- await fs4.mkdir(path5.dirname(this.paths.eventsLogPath), { recursive: true });
1237
- await fs4.appendFile(this.paths.eventsLogPath, line + "\n");
1256
+ await fs5.mkdir(path6.dirname(this.paths.eventsLogPath), { recursive: true });
1257
+ await fs5.appendFile(this.paths.eventsLogPath, line + "\n");
1238
1258
  } catch (err) {
1239
1259
  void this.writeFallbackLog("ERROR", "\u5199\u4E8B\u4EF6\u65E5\u5FD7\u5931\u8D25", this.paths.eventsLogPath, err);
1240
1260
  }
@@ -1304,19 +1324,19 @@ var Reporter = class {
1304
1324
  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
1325
  return;
1306
1326
  }
1307
- const logDir = path5.dirname(this.paths.eventsLogPath);
1327
+ const logDir = path6.dirname(this.paths.eventsLogPath);
1308
1328
  try {
1309
- await fs4.access(this.paths.eventsLogPath);
1329
+ await fs5.access(this.paths.eventsLogPath);
1310
1330
  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)}`);
1331
+ const rotatedPath = path6.join(logDir, `events.${timestamp}.jsonl`);
1332
+ await fs5.rename(this.paths.eventsLogPath, rotatedPath);
1333
+ this.debug(`Rotated active log to ${path6.basename(rotatedPath)}`);
1314
1334
  } catch {
1315
1335
  }
1316
1336
  let files = [];
1317
1337
  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));
1338
+ const dirEntries = await fs5.readdir(logDir);
1339
+ files = dirEntries.filter((f) => f.startsWith("events.") && f.endsWith(".jsonl") && f !== "events.jsonl").map((f) => path6.join(logDir, f));
1320
1340
  } catch {
1321
1341
  return;
1322
1342
  }
@@ -1329,10 +1349,10 @@ var Reporter = class {
1329
1349
  const url = config.reportBaseUrl.replace(/\/$/, "") + "/skill_report/batch";
1330
1350
  for (const filePath of files) {
1331
1351
  try {
1332
- const content = await fs4.readFile(filePath, "utf-8");
1352
+ const content = await fs5.readFile(filePath, "utf-8");
1333
1353
  const lines = this.linesOf(content);
1334
1354
  if (lines.length === 0) {
1335
- await fs4.unlink(filePath);
1355
+ await fs5.unlink(filePath);
1336
1356
  continue;
1337
1357
  }
1338
1358
  let cursor = 0;
@@ -1362,7 +1382,7 @@ var Reporter = class {
1362
1382
  const res = await this.fetchImpl(url, { method: "POST", headers, body });
1363
1383
  if (!res.ok) {
1364
1384
  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);
1385
+ 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
1386
  if (res.status === 400 || res.status === 413 || res.status === 422) {
1367
1387
  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
1388
  cursor += slice.length;
@@ -1371,17 +1391,17 @@ var Reporter = class {
1371
1391
  allSuccess = false;
1372
1392
  break;
1373
1393
  }
1374
- this.debug(`Successfully reported batch of ${events.length} events from ${path5.basename(filePath)}`);
1394
+ this.debug(`Successfully reported batch of ${events.length} events from ${path6.basename(filePath)}`);
1375
1395
  cursor += slice.length;
1376
1396
  }
1377
1397
  if (allSuccess) {
1378
- await fs4.unlink(filePath);
1379
- this.debug(`Deleted fully processed file: ${path5.basename(filePath)}`);
1398
+ await fs5.unlink(filePath);
1399
+ this.debug(`Deleted fully processed file: ${path6.basename(filePath)}`);
1380
1400
  } else {
1381
- this.debug(`File ${path5.basename(filePath)} partially failed. Keeping it for next flush.`);
1401
+ this.debug(`File ${path6.basename(filePath)} partially failed. Keeping it for next flush.`);
1382
1402
  }
1383
1403
  } catch (err) {
1384
- await this.writeFallbackLog("ERROR", `\u5904\u7406\u6587\u4EF6 ${path5.basename(filePath)} \u5F02\u5E38:`, err);
1404
+ await this.writeFallbackLog("ERROR", `\u5904\u7406\u6587\u4EF6 ${path6.basename(filePath)} \u5F02\u5E38:`, err);
1385
1405
  }
1386
1406
  }
1387
1407
  } catch (err) {
@@ -1394,8 +1414,8 @@ var Reporter = class {
1394
1414
 
1395
1415
  // src/ws-client.ts
1396
1416
  import WebSocket from "ws";
1397
- import path6 from "path";
1398
- import fs5 from "fs/promises";
1417
+ import path7 from "path";
1418
+ import fs6 from "fs/promises";
1399
1419
  var HEARTBEAT_INTERVAL_MS = 3e4;
1400
1420
  var HEARTBEAT_ACK_TIMEOUT_MS = 75e3;
1401
1421
  var AGENT_SCAN_INTERVAL_MS = 3 * 60 * 1e3;
@@ -1425,10 +1445,10 @@ var GatewayWsClient = class {
1425
1445
  }
1426
1446
  }
1427
1447
  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(() => {
1448
+ const logsDir = path7.join(openclawHome(), "logs");
1449
+ fs6.mkdir(logsDir, { recursive: true }).then(() => {
1450
+ const logPath = path7.join(logsDir, "skill-logger.err");
1451
+ fs6.appendFile(logPath, logLine).catch(() => {
1432
1452
  });
1433
1453
  }).catch(() => {
1434
1454
  });
@@ -1532,7 +1552,7 @@ var GatewayWsClient = class {
1532
1552
  const rootPath = openclawHome();
1533
1553
  let entries = [];
1534
1554
  try {
1535
- entries = await fs5.readdir(rootPath);
1555
+ entries = await fs6.readdir(rootPath);
1536
1556
  } catch (e) {
1537
1557
  this.currentAgentIds = /* @__PURE__ */ new Set();
1538
1558
  this.appendLogToFile("WARN", "AgentScan", "OpenClaw home is not readable; reporting empty agent list", e);
@@ -1543,7 +1563,7 @@ var GatewayWsClient = class {
1543
1563
  for (const entry of entries) {
1544
1564
  if (entry.startsWith("workspace-assistant-")) {
1545
1565
  const suffix = entry.replace("workspace-assistant-", "").trim();
1546
- if (suffix && suffix === path6.basename(suffix) && !suffix.startsWith(".")) {
1566
+ if (suffix && suffix === path7.basename(suffix) && !suffix.startsWith(".")) {
1547
1567
  newAgentIds.add(`assistant-${suffix}`);
1548
1568
  }
1549
1569
  }
@@ -1689,10 +1709,10 @@ var GatewayWsClient = class {
1689
1709
  this.appendLogToFile("WARN", "Command", `Message dropped: missing action or userId`, msg);
1690
1710
  return;
1691
1711
  }
1692
- const safeUserId = path6.basename(userId);
1693
- const safeCode = code ? path6.basename(code) : void 0;
1712
+ const safeUserId = path7.basename(userId);
1713
+ const safeCode = code ? path7.basename(code) : void 0;
1694
1714
  const pureId = safeUserId.replace(/^assistant-/, "");
1695
- const targetDir = path6.join(openclawHome(), `workspace-assistant-${pureId}`, "skills");
1715
+ const targetDir = path7.join(openclawHome(), `workspace-assistant-${pureId}`, "skills");
1696
1716
  try {
1697
1717
  if (action === "INSTALL_SKILL") {
1698
1718
  if (!safeCode) throw new Error("Missing code parameter");
@@ -1710,14 +1730,14 @@ var GatewayWsClient = class {
1710
1730
  if (!safeCode) throw new Error("Missing code parameter");
1711
1731
  console.log(`[skill-logger-plugin][WS] Executing UNINSTALL for user ${userId}, code: ${safeCode}`);
1712
1732
  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 });
1733
+ const skillPath = path7.join(targetDir, safeCode);
1734
+ await fs6.rm(skillPath, { recursive: true, force: true });
1715
1735
  this.reply(replyId, { success: true, message: `Skill ${safeCode} removed`, action });
1716
1736
  } else if (action === "LIST_SKILLS") {
1717
1737
  let list = [];
1718
1738
  let targetDirExists = false;
1719
1739
  try {
1720
- const targetStat = await fs5.stat(targetDir);
1740
+ const targetStat = await fs6.stat(targetDir);
1721
1741
  targetDirExists = targetStat.isDirectory();
1722
1742
  } catch (err) {
1723
1743
  if (err?.code !== "ENOENT") throw err;
@@ -1725,18 +1745,18 @@ var GatewayWsClient = class {
1725
1745
  if (!targetDirExists) {
1726
1746
  throw new Error(`Target skills directory does not exist: ${targetDir}`);
1727
1747
  }
1728
- const entries = await fs5.readdir(targetDir, { withFileTypes: true });
1748
+ const entries = await fs6.readdir(targetDir, { withFileTypes: true });
1729
1749
  const dirs = entries.filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith("."));
1730
1750
  for (const e of dirs) {
1731
- const skillDir = path6.join(targetDir, e.name);
1732
- const skillMdPath = path6.join(skillDir, "SKILL.md");
1751
+ const skillDir = path7.join(targetDir, e.name);
1752
+ const skillMdPath = path7.join(skillDir, "SKILL.md");
1733
1753
  try {
1734
- const stat = await fs5.stat(skillMdPath);
1754
+ const stat = await fs6.stat(skillMdPath);
1735
1755
  if (!stat.isFile()) continue;
1736
1756
  } catch (err) {
1737
1757
  continue;
1738
1758
  }
1739
- const metaPath = path6.join(skillDir, ".meta.json");
1759
+ const metaPath = path7.join(skillDir, ".meta.json");
1740
1760
  let isPlatform = false;
1741
1761
  let isBuiltIn = e.isSymbolicLink();
1742
1762
  let metaData = null;
@@ -1744,12 +1764,10 @@ var GatewayWsClient = class {
1744
1764
  let description = "";
1745
1765
  let skillVersion = "";
1746
1766
  try {
1747
- const mdContent = await fs5.readFile(skillMdPath, "utf8");
1767
+ const mdContent = await fs6.readFile(skillMdPath, "utf8");
1748
1768
  const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(mdContent)?.[1] ?? "";
1749
1769
  const parsedName = /(^|\n)name:\s*(.+)/.exec(fm)?.[2]?.trim();
1750
1770
  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
1771
  const descMatch = /(^|\n)description:\s*(?:>\s*\n\s*)?(.*?)(?=\n[a-z]+:|\n---|$)/is.exec(fm);
1754
1772
  if (descMatch && descMatch[2]) {
1755
1773
  description = descMatch[2].replace(/\n\s+/g, " ").trim();
@@ -1757,7 +1775,7 @@ var GatewayWsClient = class {
1757
1775
  } catch (err) {
1758
1776
  }
1759
1777
  try {
1760
- const metaContent = await fs5.readFile(metaPath, "utf8");
1778
+ const metaContent = await fs6.readFile(metaPath, "utf8");
1761
1779
  const parsed = JSON.parse(metaContent);
1762
1780
  if (parsed) {
1763
1781
  if (parsed.ownerId === "CMS" || parsed.ownerId === "CMS_COMPAT") isPlatform = true;
@@ -1766,12 +1784,14 @@ var GatewayWsClient = class {
1766
1784
  }
1767
1785
  } catch (err) {
1768
1786
  }
1787
+ const resolvedVersion = await readSkillVersion2(skillDir);
1788
+ if (resolvedVersion) skillVersion = resolvedVersion;
1769
1789
  if (isPlatform) {
1770
1790
  list.push({
1771
1791
  code: e.name,
1772
1792
  isPlatform: true,
1773
1793
  isBuiltIn,
1774
- version: metaData?.version || skillVersion,
1794
+ version: skillVersion,
1775
1795
  name,
1776
1796
  description,
1777
1797
  publishedAt: metaData?.publishedAt
@@ -1840,7 +1860,7 @@ var GatewayWsClient = class {
1840
1860
  };
1841
1861
 
1842
1862
  // src/hooks.ts
1843
- import path7 from "node:path";
1863
+ import path8 from "node:path";
1844
1864
  import { randomUUID as randomUUID2 } from "node:crypto";
1845
1865
  var PENDING_TTL_MS = 30 * 60 * 1e3;
1846
1866
  var PENDING_MAX = 5e3;
@@ -1849,7 +1869,7 @@ function toMySQLDateTime(d) {
1849
1869
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1850
1870
  }
1851
1871
  function isSkillMdReadPath(filePath) {
1852
- return path7.basename(filePath) === "SKILL.md";
1872
+ return path8.basename(filePath) === "SKILL.md";
1853
1873
  }
1854
1874
  function extractAppKey(event, ctx) {
1855
1875
  try {
@@ -1971,8 +1991,8 @@ var Hooks = class {
1971
1991
  if (toolName === "read") {
1972
1992
  const filePath = params.path ?? params.file_path ?? "";
1973
1993
  if (!isSkillMdReadPath(filePath)) return;
1974
- const rootDir = path7.dirname(filePath);
1975
- const skillName = this.configSync.resolveSkillName(rootDir) || path7.basename(rootDir);
1994
+ const rootDir = path8.dirname(filePath);
1995
+ const skillName = this.configSync.resolveSkillName(rootDir) || path8.basename(rootDir);
1976
1996
  if (!skillName) return;
1977
1997
  this.debug(`Intercepted 'read' for SKILL.md. Attributed to skill: ${skillName}`);
1978
1998
  this.recordTrigger(skillName, "inline", "read", ctx, appKey);
@@ -2153,9 +2173,9 @@ var definition = {
2153
2173
  register(api) {
2154
2174
  let pkgVersion = "unknown";
2155
2175
  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"));
2176
+ const dir = path9.dirname(fileURLToPath2(import.meta.url));
2177
+ const pkgPath = path9.join(dir, "..", "package.json");
2178
+ const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
2159
2179
  if (pkg.version) pkgVersion = pkg.version;
2160
2180
  } catch {
2161
2181
  }
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.3",
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 {
@@ -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