@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 +147 -127
- package/package.json +1 -1
- package/src/config-sync.ts +2 -2
- package/src/skill-version.ts +33 -2
- package/src/updater.ts +16 -18
- package/src/ws-client.ts +5 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import
|
|
3
|
-
import
|
|
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
|
|
104
|
+
import fs3 from "node:fs/promises";
|
|
105
105
|
import fsSync from "node:fs";
|
|
106
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
213
|
-
await
|
|
233
|
+
const work = path3.join(this.tmpDir, `slp-update-${randomUUID()}`);
|
|
234
|
+
await fs3.mkdir(work, { recursive: true });
|
|
214
235
|
try {
|
|
215
|
-
const zipPath =
|
|
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
|
|
223
|
-
const 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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
310
|
-
await
|
|
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 =
|
|
313
|
-
const old =
|
|
314
|
-
await
|
|
315
|
-
await
|
|
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
|
|
337
|
+
if (hadTarget) await fs3.rename(target, old);
|
|
318
338
|
try {
|
|
319
|
-
await
|
|
339
|
+
await fs3.rename(stage, target);
|
|
320
340
|
} catch (err) {
|
|
321
|
-
if (hadTarget) await
|
|
341
|
+
if (hadTarget) await fs3.rename(old, target).catch(() => {
|
|
322
342
|
});
|
|
323
|
-
await
|
|
343
|
+
await fs3.rm(stage, { recursive: true, force: true }).catch(() => {
|
|
324
344
|
});
|
|
325
345
|
throw err;
|
|
326
346
|
}
|
|
327
|
-
await
|
|
347
|
+
await fs3.rm(old, { recursive: true, force: true }).catch(() => {
|
|
328
348
|
});
|
|
329
349
|
}
|
|
330
350
|
async exists(p) {
|
|
331
351
|
try {
|
|
332
|
-
await
|
|
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 =
|
|
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 =
|
|
357
|
-
await
|
|
376
|
+
const work = path3.join(this.tmpDir, `slp-manual-${randomUUID()}`);
|
|
377
|
+
await fs3.mkdir(work, { recursive: true });
|
|
358
378
|
try {
|
|
359
|
-
const zipPath =
|
|
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
|
|
366
|
-
const 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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
403
|
-
import
|
|
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
|
|
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,
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
890
|
+
await walk(path5.join(dir, e.name), depth + 1);
|
|
871
891
|
} else if (e.name === "SKILL.md") {
|
|
872
|
-
const skillMd =
|
|
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
|
|
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() ||
|
|
918
|
-
const version =
|
|
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
|
|
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 =
|
|
935
|
-
const entries = await
|
|
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
|
|
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 ||
|
|
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 =
|
|
1098
|
-
const raw = await
|
|
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
|
|
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
|
|
1123
|
-
await
|
|
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
|
|
1132
|
-
import
|
|
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 =
|
|
1202
|
-
await
|
|
1203
|
-
await
|
|
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
|
|
1237
|
-
await
|
|
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 =
|
|
1327
|
+
const logDir = path6.dirname(this.paths.eventsLogPath);
|
|
1308
1328
|
try {
|
|
1309
|
-
await
|
|
1329
|
+
await fs5.access(this.paths.eventsLogPath);
|
|
1310
1330
|
const timestamp = Date.now();
|
|
1311
|
-
const rotatedPath =
|
|
1312
|
-
await
|
|
1313
|
-
this.debug(`Rotated active log to ${
|
|
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
|
|
1319
|
-
files = dirEntries.filter((f) => f.startsWith("events.") && f.endsWith(".jsonl") && f !== "events.jsonl").map((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
|
|
1352
|
+
const content = await fs5.readFile(filePath, "utf-8");
|
|
1333
1353
|
const lines = this.linesOf(content);
|
|
1334
1354
|
if (lines.length === 0) {
|
|
1335
|
-
await
|
|
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 ${
|
|
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 ${
|
|
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
|
|
1379
|
-
this.debug(`Deleted fully processed file: ${
|
|
1398
|
+
await fs5.unlink(filePath);
|
|
1399
|
+
this.debug(`Deleted fully processed file: ${path6.basename(filePath)}`);
|
|
1380
1400
|
} else {
|
|
1381
|
-
this.debug(`File ${
|
|
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 ${
|
|
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
|
|
1398
|
-
import
|
|
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 =
|
|
1429
|
-
|
|
1430
|
-
const logPath =
|
|
1431
|
-
|
|
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
|
|
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 ===
|
|
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 =
|
|
1693
|
-
const safeCode = code ?
|
|
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 =
|
|
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 =
|
|
1714
|
-
await
|
|
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
|
|
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
|
|
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 =
|
|
1732
|
-
const skillMdPath =
|
|
1751
|
+
const skillDir = path7.join(targetDir, e.name);
|
|
1752
|
+
const skillMdPath = path7.join(skillDir, "SKILL.md");
|
|
1733
1753
|
try {
|
|
1734
|
-
const stat = await
|
|
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 =
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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 =
|
|
1975
|
-
const skillName = this.configSync.resolveSkillName(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 =
|
|
2157
|
-
const pkgPath =
|
|
2158
|
-
const pkg = JSON.parse(
|
|
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
package/src/config-sync.ts
CHANGED
|
@@ -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 =
|
|
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/skill-version.ts
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
-
|
|
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:
|
|
449
|
+
version: skillVersion,
|
|
448
450
|
name,
|
|
449
451
|
description,
|
|
450
452
|
publishedAt: metaData?.publishedAt
|