@spzhongwin/skill-logger-plugin 1.0.15 → 1.0.17
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 +462 -121
- package/package.json +2 -2
- package/src/expert-skill-layout.test.ts +196 -0
- package/src/expert-skill-layout.ts +233 -0
- package/src/index.ts +26 -5
- package/src/semver.test.ts +11 -1
- package/src/semver.ts +5 -0
- package/src/updater.test.ts +117 -0
- package/src/updater.ts +52 -0
- package/src/ws-client.test.ts +119 -0
- package/src/ws-client.ts +175 -31
- package/dist/active-skills.js +0 -67
- package/dist/active-skills.test.js +0 -29
- package/dist/config-sync.js +0 -439
- package/dist/config-sync.test.js +0 -145
- package/dist/hooks.js +0 -337
- package/dist/hooks.test.js +0 -123
- package/dist/http.js +0 -54
- package/dist/identity.js +0 -56
- package/dist/index.test.js +0 -39
- package/dist/integration.test.js +0 -102
- package/dist/matcher.js +0 -362
- package/dist/matcher.test.js +0 -139
- package/dist/paths.js +0 -62
- package/dist/paths.test.js +0 -49
- package/dist/reporter.js +0 -267
- package/dist/reporter.test.js +0 -128
- package/dist/semver.js +0 -64
- package/dist/semver.test.js +0 -21
- package/dist/skill-version.js +0 -23
- package/dist/types.js +0 -9
- package/dist/updater.js +0 -352
- package/dist/updater.test.js +0 -212
- package/dist/ws-client.js +0 -484
package/dist/index.js
CHANGED
|
@@ -360,25 +360,25 @@ var require_utils = __commonJS({
|
|
|
360
360
|
}
|
|
361
361
|
mkdirSync(folder);
|
|
362
362
|
};
|
|
363
|
-
Utils.prototype.writeFileTo = function(
|
|
363
|
+
Utils.prototype.writeFileTo = function(path12, content, overwrite, attr) {
|
|
364
364
|
const self = this;
|
|
365
|
-
if (self.fs.existsSync(
|
|
365
|
+
if (self.fs.existsSync(path12)) {
|
|
366
366
|
if (!overwrite) return false;
|
|
367
|
-
var stat = self.fs.statSync(
|
|
367
|
+
var stat = self.fs.statSync(path12);
|
|
368
368
|
if (stat.isDirectory()) {
|
|
369
369
|
return false;
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
|
-
var folder = pth.dirname(
|
|
372
|
+
var folder = pth.dirname(path12);
|
|
373
373
|
if (!self.fs.existsSync(folder)) {
|
|
374
374
|
self.makeDir(folder);
|
|
375
375
|
}
|
|
376
376
|
var fd;
|
|
377
377
|
try {
|
|
378
|
-
fd = self.fs.openSync(
|
|
378
|
+
fd = self.fs.openSync(path12, "w", 438);
|
|
379
379
|
} catch (e) {
|
|
380
|
-
self.fs.chmodSync(
|
|
381
|
-
fd = self.fs.openSync(
|
|
380
|
+
self.fs.chmodSync(path12, 438);
|
|
381
|
+
fd = self.fs.openSync(path12, "w", 438);
|
|
382
382
|
}
|
|
383
383
|
if (fd) {
|
|
384
384
|
try {
|
|
@@ -387,22 +387,22 @@ var require_utils = __commonJS({
|
|
|
387
387
|
self.fs.closeSync(fd);
|
|
388
388
|
}
|
|
389
389
|
}
|
|
390
|
-
self.fs.chmodSync(
|
|
390
|
+
self.fs.chmodSync(path12, attr || 438);
|
|
391
391
|
return true;
|
|
392
392
|
};
|
|
393
|
-
Utils.prototype.writeFileToAsync = function(
|
|
393
|
+
Utils.prototype.writeFileToAsync = function(path12, content, overwrite, attr, callback) {
|
|
394
394
|
if (typeof attr === "function") {
|
|
395
395
|
callback = attr;
|
|
396
396
|
attr = void 0;
|
|
397
397
|
}
|
|
398
398
|
const self = this;
|
|
399
|
-
self.fs.exists(
|
|
399
|
+
self.fs.exists(path12, function(exist) {
|
|
400
400
|
if (exist && !overwrite) return callback(false);
|
|
401
|
-
self.fs.stat(
|
|
401
|
+
self.fs.stat(path12, function(err, stat) {
|
|
402
402
|
if (exist && stat && stat.isDirectory()) {
|
|
403
403
|
return callback(false);
|
|
404
404
|
}
|
|
405
|
-
var folder = pth.dirname(
|
|
405
|
+
var folder = pth.dirname(path12);
|
|
406
406
|
self.fs.exists(folder, function(exists) {
|
|
407
407
|
if (!exists) {
|
|
408
408
|
try {
|
|
@@ -415,16 +415,16 @@ var require_utils = __commonJS({
|
|
|
415
415
|
self.fs.write(fd, content, 0, content.length, 0, function(writeErr) {
|
|
416
416
|
self.fs.close(fd, function() {
|
|
417
417
|
if (writeErr) return callback(false);
|
|
418
|
-
self.fs.chmod(
|
|
418
|
+
self.fs.chmod(path12, attr || 438, function() {
|
|
419
419
|
callback(true);
|
|
420
420
|
});
|
|
421
421
|
});
|
|
422
422
|
});
|
|
423
423
|
};
|
|
424
|
-
self.fs.open(
|
|
424
|
+
self.fs.open(path12, "w", 438, function(err2, fd) {
|
|
425
425
|
if (err2) {
|
|
426
|
-
self.fs.chmod(
|
|
427
|
-
self.fs.open(
|
|
426
|
+
self.fs.chmod(path12, 438, function() {
|
|
427
|
+
self.fs.open(path12, "w", 438, function(retryErr, fd2) {
|
|
428
428
|
if (retryErr || !fd2) return callback(false);
|
|
429
429
|
writeToFd(fd2);
|
|
430
430
|
});
|
|
@@ -439,7 +439,7 @@ var require_utils = __commonJS({
|
|
|
439
439
|
});
|
|
440
440
|
});
|
|
441
441
|
};
|
|
442
|
-
Utils.prototype.findFiles = function(
|
|
442
|
+
Utils.prototype.findFiles = function(path12) {
|
|
443
443
|
const self = this;
|
|
444
444
|
function findSync(dir, pattern, recursive, visited) {
|
|
445
445
|
if (typeof pattern === "boolean") {
|
|
@@ -448,22 +448,22 @@ var require_utils = __commonJS({
|
|
|
448
448
|
}
|
|
449
449
|
let files = [];
|
|
450
450
|
self.fs.readdirSync(dir).forEach(function(file) {
|
|
451
|
-
const
|
|
452
|
-
const stat = self.fs.statSync(
|
|
453
|
-
if (!pattern || pattern.test(
|
|
454
|
-
files.push(pth.normalize(
|
|
451
|
+
const path13 = pth.join(dir, file);
|
|
452
|
+
const stat = self.fs.statSync(path13);
|
|
453
|
+
if (!pattern || pattern.test(path13)) {
|
|
454
|
+
files.push(pth.normalize(path13) + (stat.isDirectory() ? self.sep : ""));
|
|
455
455
|
}
|
|
456
456
|
if (stat.isDirectory() && recursive) {
|
|
457
|
-
const realDir = self.fs.realpathSync(
|
|
457
|
+
const realDir = self.fs.realpathSync(path13);
|
|
458
458
|
if (!visited.has(realDir)) {
|
|
459
459
|
visited.add(realDir);
|
|
460
|
-
files = files.concat(findSync(
|
|
460
|
+
files = files.concat(findSync(path13, pattern, recursive, visited));
|
|
461
461
|
}
|
|
462
462
|
}
|
|
463
463
|
});
|
|
464
464
|
return files;
|
|
465
465
|
}
|
|
466
|
-
return findSync(
|
|
466
|
+
return findSync(path12, void 0, true, /* @__PURE__ */ new Set([self.fs.realpathSync(path12)]));
|
|
467
467
|
};
|
|
468
468
|
Utils.prototype.findFilesAsync = function(dir, cb) {
|
|
469
469
|
const self = this;
|
|
@@ -539,14 +539,14 @@ var require_utils = __commonJS({
|
|
|
539
539
|
return "UNSUPPORTED (" + method + ")";
|
|
540
540
|
}
|
|
541
541
|
};
|
|
542
|
-
Utils.canonical = function(
|
|
543
|
-
if (!
|
|
544
|
-
const safeSuffix = pth.posix.normalize("/" +
|
|
542
|
+
Utils.canonical = function(path12) {
|
|
543
|
+
if (!path12) return "";
|
|
544
|
+
const safeSuffix = pth.posix.normalize("/" + path12.split("\\").join("/"));
|
|
545
545
|
return pth.join(".", safeSuffix);
|
|
546
546
|
};
|
|
547
|
-
Utils.zipnamefix = function(
|
|
548
|
-
if (!
|
|
549
|
-
const safeSuffix = pth.posix.normalize("/" +
|
|
547
|
+
Utils.zipnamefix = function(path12) {
|
|
548
|
+
if (!path12) return "";
|
|
549
|
+
const safeSuffix = pth.posix.normalize("/" + path12.split("\\").join("/"));
|
|
550
550
|
return pth.posix.join(".", safeSuffix);
|
|
551
551
|
};
|
|
552
552
|
Utils.findLast = function(arr, callback) {
|
|
@@ -563,9 +563,9 @@ var require_utils = __commonJS({
|
|
|
563
563
|
prefix = pth.resolve(pth.normalize(prefix));
|
|
564
564
|
var parts = name.split("/");
|
|
565
565
|
for (var i = 0, l = parts.length; i < l; i++) {
|
|
566
|
-
var
|
|
567
|
-
if (
|
|
568
|
-
return
|
|
566
|
+
var path12 = pth.normalize(pth.join(prefix, parts.slice(i, l).join(pth.sep)));
|
|
567
|
+
if (path12 === prefix || path12.startsWith(prefix + pth.sep)) {
|
|
568
|
+
return path12;
|
|
569
569
|
}
|
|
570
570
|
}
|
|
571
571
|
return pth.normalize(pth.join(prefix, pth.basename(name)));
|
|
@@ -611,8 +611,8 @@ var require_utils = __commonJS({
|
|
|
611
611
|
var require_fattr = __commonJS({
|
|
612
612
|
"node_modules/adm-zip/util/fattr.js"(exports, module) {
|
|
613
613
|
var pth = __require("path");
|
|
614
|
-
module.exports = function(
|
|
615
|
-
var _path =
|
|
614
|
+
module.exports = function(path12, { fs: fs10 }) {
|
|
615
|
+
var _path = path12 || "", _obj = newAttr(), _stat = null;
|
|
616
616
|
function newAttr() {
|
|
617
617
|
return {
|
|
618
618
|
directory: false,
|
|
@@ -623,8 +623,8 @@ var require_fattr = __commonJS({
|
|
|
623
623
|
atime: 0
|
|
624
624
|
};
|
|
625
625
|
}
|
|
626
|
-
if (_path &&
|
|
627
|
-
_stat =
|
|
626
|
+
if (_path && fs10.existsSync(_path)) {
|
|
627
|
+
_stat = fs10.statSync(_path);
|
|
628
628
|
_obj.directory = _stat.isDirectory();
|
|
629
629
|
_obj.mtime = _stat.mtime;
|
|
630
630
|
_obj.atime = _stat.atime;
|
|
@@ -2750,8 +2750,8 @@ var require_adm_zip = __commonJS({
|
|
|
2750
2750
|
});
|
|
2751
2751
|
|
|
2752
2752
|
// src/index.ts
|
|
2753
|
-
import
|
|
2754
|
-
import
|
|
2753
|
+
import fs9 from "node:fs";
|
|
2754
|
+
import path11 from "node:path";
|
|
2755
2755
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2756
2756
|
import os4 from "node:os";
|
|
2757
2757
|
|
|
@@ -3094,6 +3094,25 @@ var SkillUpdater = class {
|
|
|
3094
3094
|
}
|
|
3095
3095
|
return void 0;
|
|
3096
3096
|
}
|
|
3097
|
+
/** 在解压目录里定位专家根目录;专家包根必须同时包含 AGENTS.md 和 SOUL.md。 */
|
|
3098
|
+
async locateExpertRoot(dir, depth) {
|
|
3099
|
+
if (depth > 2) return void 0;
|
|
3100
|
+
let entries;
|
|
3101
|
+
try {
|
|
3102
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
3103
|
+
} catch {
|
|
3104
|
+
return void 0;
|
|
3105
|
+
}
|
|
3106
|
+
const files = new Set(entries.filter((entry) => entry.isFile()).map((entry) => entry.name));
|
|
3107
|
+
if (files.has("AGENTS.md") && files.has("SOUL.md")) return dir;
|
|
3108
|
+
for (const entry of entries) {
|
|
3109
|
+
if (entry.isDirectory()) {
|
|
3110
|
+
const found = await this.locateExpertRoot(path3.join(dir, entry.name), depth + 1);
|
|
3111
|
+
if (found) return found;
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
return void 0;
|
|
3115
|
+
}
|
|
3097
3116
|
/**
|
|
3098
3117
|
* 直接覆盖、不备份,但保证「不丢数据」:
|
|
3099
3118
|
* 先把新内容暂存到同级临时目录 → 旧目录改名挪开 → 新内容 rename 换入 → 删掉挪开的旧目录。
|
|
@@ -3157,6 +3176,29 @@ var SkillUpdater = class {
|
|
|
3157
3176
|
});
|
|
3158
3177
|
}
|
|
3159
3178
|
}
|
|
3179
|
+
async installExpertZipFromUrl(url, targetDir) {
|
|
3180
|
+
const work = path3.join(this.tmpDir, `slp-expert-${randomUUID()}`);
|
|
3181
|
+
await fs3.mkdir(work, { recursive: true });
|
|
3182
|
+
try {
|
|
3183
|
+
const zipPath = path3.join(work, "pkg.zip");
|
|
3184
|
+
const res = await this.fetchImpl(url);
|
|
3185
|
+
if (!res.ok) return { success: false, message: `\u4E0B\u8F7D\u5931\u8D25: HTTP ${res.status}` };
|
|
3186
|
+
await fs3.writeFile(zipPath, Buffer.from(await res.arrayBuffer()));
|
|
3187
|
+
const staging = path3.join(work, "staging");
|
|
3188
|
+
await this.unzip(zipPath, staging);
|
|
3189
|
+
const expertRoot = await this.locateExpertRoot(staging, 0);
|
|
3190
|
+
if (!expertRoot) {
|
|
3191
|
+
return { success: false, message: "\u4E0B\u8F7D\u5305\u5185\u672A\u627E\u5230\u540C\u65F6\u542B AGENTS.md \u548C SOUL.md \u7684\u4E13\u5BB6\u6839\u76EE\u5F55" };
|
|
3192
|
+
}
|
|
3193
|
+
await this.replaceDir(expertRoot, targetDir);
|
|
3194
|
+
return { success: true, message: "\u5B89\u88C5\u6210\u529F" };
|
|
3195
|
+
} catch (err) {
|
|
3196
|
+
return { success: false, message: `\u6267\u884C\u51FA\u9519: ${err.message}` };
|
|
3197
|
+
} finally {
|
|
3198
|
+
await fs3.rm(work, { recursive: true, force: true }).catch(() => {
|
|
3199
|
+
});
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3160
3202
|
async manualInstall(options) {
|
|
3161
3203
|
const { code, force, targetDir, additionalTargetDirs = [], trace } = options;
|
|
3162
3204
|
let { url, version } = options;
|
|
@@ -3178,6 +3220,11 @@ var SkillUpdater = class {
|
|
|
3178
3220
|
targetCount: 1 + additionalTargetDirs.length
|
|
3179
3221
|
});
|
|
3180
3222
|
try {
|
|
3223
|
+
if (!code || code === "." || code === ".." || code.includes("/") || code.includes("\\") || code.includes("\0") || path3.basename(code) !== code) {
|
|
3224
|
+
const message = `\u975E\u6CD5\u7684 Skill code: ${code}`;
|
|
3225
|
+
emit("install.failed", { stage: currentStage, message, elapsedMs: Date.now() - startedAt });
|
|
3226
|
+
return { success: false, message };
|
|
3227
|
+
}
|
|
3181
3228
|
if (!url) {
|
|
3182
3229
|
const lookupStartedAt = Date.now();
|
|
3183
3230
|
emit("download_url.lookup.start", { code, version: version || "latest" });
|
|
@@ -3658,6 +3705,9 @@ function parseCore(v) {
|
|
|
3658
3705
|
}
|
|
3659
3706
|
return nums.length > 0 ? nums : null;
|
|
3660
3707
|
}
|
|
3708
|
+
function isComparableVersion(v) {
|
|
3709
|
+
return parseCore(v) !== null;
|
|
3710
|
+
}
|
|
3661
3711
|
function compareVersions(a, b) {
|
|
3662
3712
|
const na = parseCore(a);
|
|
3663
3713
|
const nb = parseCore(b);
|
|
@@ -4395,35 +4445,284 @@ var Reporter = class {
|
|
|
4395
4445
|
}
|
|
4396
4446
|
};
|
|
4397
4447
|
|
|
4448
|
+
// src/expert-skill-layout.ts
|
|
4449
|
+
import fs7 from "node:fs/promises";
|
|
4450
|
+
import path8 from "node:path";
|
|
4451
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
4452
|
+
var ASSISTANT_WORKSPACE_RE = /^workspace-assistant-\d{5,}$/;
|
|
4453
|
+
var REPAIR_RESIDUE_RE = /^\.(.+)\.repair-(old|new)-(.+)$/;
|
|
4454
|
+
async function lstatOrUndefined(filePath) {
|
|
4455
|
+
try {
|
|
4456
|
+
return await fs7.lstat(filePath);
|
|
4457
|
+
} catch (error) {
|
|
4458
|
+
if (error?.code === "ENOENT") return void 0;
|
|
4459
|
+
throw error;
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
async function isRegularFile(filePath) {
|
|
4463
|
+
const stat = await lstatOrUndefined(filePath);
|
|
4464
|
+
return Boolean(stat?.isFile() && !stat.isSymbolicLink());
|
|
4465
|
+
}
|
|
4466
|
+
async function removeRealDirectory(dirPath) {
|
|
4467
|
+
const stat = await lstatOrUndefined(dirPath);
|
|
4468
|
+
if (!stat) return;
|
|
4469
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
4470
|
+
throw new Error(`\u62D2\u7EDD\u6E05\u7406\u975E\u771F\u5B9E\u76EE\u5F55: ${dirPath}`);
|
|
4471
|
+
}
|
|
4472
|
+
await fs7.rm(dirPath, { recursive: true });
|
|
4473
|
+
}
|
|
4474
|
+
async function removeNestedDirectoryIfPresent(dirPath) {
|
|
4475
|
+
const stat = await lstatOrUndefined(dirPath);
|
|
4476
|
+
if (!stat) return;
|
|
4477
|
+
if (stat.isSymbolicLink()) throw new Error(`\u62D2\u7EDD\u6E05\u7406\u7B26\u53F7\u94FE\u63A5: ${dirPath}`);
|
|
4478
|
+
if (stat.isDirectory()) await fs7.rm(dirPath, { recursive: true });
|
|
4479
|
+
}
|
|
4480
|
+
async function recoverRepairResidues(skillsRoot, entries, result) {
|
|
4481
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4482
|
+
for (const entry of entries) {
|
|
4483
|
+
const match2 = REPAIR_RESIDUE_RE.exec(entry.name);
|
|
4484
|
+
if (!match2) continue;
|
|
4485
|
+
const group = groups.get(match2[1]) ?? { old: [], stage: [] };
|
|
4486
|
+
group[match2[2] === "old" ? "old" : "stage"].push(path8.join(skillsRoot, entry.name));
|
|
4487
|
+
groups.set(match2[1], group);
|
|
4488
|
+
}
|
|
4489
|
+
for (const [code, group] of groups) {
|
|
4490
|
+
const targetDir = path8.join(skillsRoot, code);
|
|
4491
|
+
try {
|
|
4492
|
+
const targetStat = await lstatOrUndefined(targetDir);
|
|
4493
|
+
if (targetStat?.isSymbolicLink() || targetStat && !targetStat.isDirectory()) {
|
|
4494
|
+
throw new Error(`\u89C4\u8303\u8DEF\u5F84\u4E0D\u662F\u53EF\u5B89\u5168\u64CD\u4F5C\u7684\u771F\u5B9E\u76EE\u5F55: ${targetDir}`);
|
|
4495
|
+
}
|
|
4496
|
+
group.old.sort().reverse();
|
|
4497
|
+
if (!targetStat && group.old.length > 0) {
|
|
4498
|
+
const restore = group.old.shift();
|
|
4499
|
+
const restoreStat = await lstatOrUndefined(restore);
|
|
4500
|
+
if (!restoreStat?.isDirectory() || restoreStat.isSymbolicLink()) {
|
|
4501
|
+
throw new Error(`\u62D2\u7EDD\u4ECE\u975E\u771F\u5B9E\u76EE\u5F55\u6062\u590D: ${restore}`);
|
|
4502
|
+
}
|
|
4503
|
+
await fs7.rename(restore, targetDir);
|
|
4504
|
+
}
|
|
4505
|
+
if (await lstatOrUndefined(targetDir)) {
|
|
4506
|
+
for (const residue of [...group.old, ...group.stage]) {
|
|
4507
|
+
await removeRealDirectory(residue);
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
} catch (error) {
|
|
4511
|
+
result.errors.push({ path: targetDir, message: error?.message || String(error) });
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
async function promoteNestedSkill(sourceDir, targetDir) {
|
|
4516
|
+
const parent = path8.dirname(targetDir);
|
|
4517
|
+
const tag = `${Date.now()}-${randomUUID2().slice(0, 8)}`;
|
|
4518
|
+
const stage = path8.join(parent, `.${path8.basename(targetDir)}.repair-new-${tag}`);
|
|
4519
|
+
const old = path8.join(parent, `.${path8.basename(targetDir)}.repair-old-${tag}`);
|
|
4520
|
+
let movedOld = false;
|
|
4521
|
+
try {
|
|
4522
|
+
await fs7.cp(sourceDir, stage, { recursive: true });
|
|
4523
|
+
await removeNestedDirectoryIfPresent(path8.join(stage, path8.basename(targetDir)));
|
|
4524
|
+
await fs7.rename(targetDir, old);
|
|
4525
|
+
movedOld = true;
|
|
4526
|
+
await fs7.rename(stage, targetDir);
|
|
4527
|
+
} catch (error) {
|
|
4528
|
+
const rollbackErrors = [];
|
|
4529
|
+
try {
|
|
4530
|
+
await removeRealDirectory(stage);
|
|
4531
|
+
} catch (cleanupError) {
|
|
4532
|
+
rollbackErrors.push(`\u6E05\u7406\u6682\u5B58\u76EE\u5F55\u5931\u8D25: ${cleanupError?.message || String(cleanupError)}`);
|
|
4533
|
+
}
|
|
4534
|
+
if (movedOld) {
|
|
4535
|
+
try {
|
|
4536
|
+
await fs7.rename(old, targetDir);
|
|
4537
|
+
} catch (restoreError) {
|
|
4538
|
+
rollbackErrors.push(`\u6062\u590D\u65E7\u76EE\u5F55\u5931\u8D25: ${restoreError?.message || String(restoreError)}`);
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
if (rollbackErrors.length > 0) {
|
|
4542
|
+
throw new Error(`${error?.message || String(error)}; ${rollbackErrors.join("; ")}`);
|
|
4543
|
+
}
|
|
4544
|
+
throw error;
|
|
4545
|
+
}
|
|
4546
|
+
await removeRealDirectory(old);
|
|
4547
|
+
}
|
|
4548
|
+
async function repairNestedExpertSkillLayouts(openclawRoot) {
|
|
4549
|
+
const result = {
|
|
4550
|
+
scannedWorkspaces: 0,
|
|
4551
|
+
repaired: [],
|
|
4552
|
+
skipped: [],
|
|
4553
|
+
errors: []
|
|
4554
|
+
};
|
|
4555
|
+
let workspaceEntries;
|
|
4556
|
+
try {
|
|
4557
|
+
workspaceEntries = await fs7.readdir(openclawRoot, { withFileTypes: true });
|
|
4558
|
+
} catch (error) {
|
|
4559
|
+
result.errors.push({ path: openclawRoot, message: error?.message || String(error) });
|
|
4560
|
+
return result;
|
|
4561
|
+
}
|
|
4562
|
+
for (const workspaceEntry of workspaceEntries) {
|
|
4563
|
+
if (!workspaceEntry.isDirectory() || !ASSISTANT_WORKSPACE_RE.test(workspaceEntry.name)) continue;
|
|
4564
|
+
result.scannedWorkspaces += 1;
|
|
4565
|
+
const skillsRoot = path8.join(openclawRoot, workspaceEntry.name, ".user", "skills");
|
|
4566
|
+
let skillEntries;
|
|
4567
|
+
try {
|
|
4568
|
+
skillEntries = await fs7.readdir(skillsRoot, { withFileTypes: true });
|
|
4569
|
+
await recoverRepairResidues(skillsRoot, skillEntries, result);
|
|
4570
|
+
skillEntries = await fs7.readdir(skillsRoot, { withFileTypes: true });
|
|
4571
|
+
} catch (error) {
|
|
4572
|
+
if (error?.code !== "ENOENT") {
|
|
4573
|
+
result.errors.push({ path: skillsRoot, message: error?.message || String(error) });
|
|
4574
|
+
}
|
|
4575
|
+
continue;
|
|
4576
|
+
}
|
|
4577
|
+
for (const skillEntry of skillEntries) {
|
|
4578
|
+
if (!skillEntry.isDirectory() || skillEntry.name.startsWith(".")) continue;
|
|
4579
|
+
const outerDir = path8.join(skillsRoot, skillEntry.name);
|
|
4580
|
+
try {
|
|
4581
|
+
const candidates = [];
|
|
4582
|
+
const outerHasSkill = await isRegularFile(path8.join(outerDir, "SKILL.md"));
|
|
4583
|
+
if (outerHasSkill) {
|
|
4584
|
+
const version = await readSkillVersion(outerDir);
|
|
4585
|
+
if (!version) {
|
|
4586
|
+
result.skipped.push(outerDir);
|
|
4587
|
+
continue;
|
|
4588
|
+
}
|
|
4589
|
+
candidates.push({ dir: outerDir, version });
|
|
4590
|
+
}
|
|
4591
|
+
let cursor = outerDir;
|
|
4592
|
+
let nestedCount = 0;
|
|
4593
|
+
while (true) {
|
|
4594
|
+
const nestedDir = path8.join(cursor, skillEntry.name);
|
|
4595
|
+
const nestedStat = await lstatOrUndefined(nestedDir);
|
|
4596
|
+
if (!nestedStat) break;
|
|
4597
|
+
if (nestedStat.isSymbolicLink()) {
|
|
4598
|
+
throw new Error(`\u62D2\u7EDD\u8DDF\u968F\u540C\u540D\u7B26\u53F7\u94FE\u63A5: ${nestedDir}`);
|
|
4599
|
+
}
|
|
4600
|
+
if (!nestedStat.isDirectory()) break;
|
|
4601
|
+
cursor = nestedDir;
|
|
4602
|
+
const skillMd = path8.join(nestedDir, "SKILL.md");
|
|
4603
|
+
const skillStat = await lstatOrUndefined(skillMd);
|
|
4604
|
+
if (skillStat?.isSymbolicLink()) {
|
|
4605
|
+
throw new Error(`\u62D2\u7EDD\u8BFB\u53D6\u7B26\u53F7\u94FE\u63A5 SKILL.md: ${skillMd}`);
|
|
4606
|
+
}
|
|
4607
|
+
if (!skillStat?.isFile()) continue;
|
|
4608
|
+
nestedCount += 1;
|
|
4609
|
+
const version = await readSkillVersion(nestedDir);
|
|
4610
|
+
if (!version) {
|
|
4611
|
+
result.skipped.push(outerDir);
|
|
4612
|
+
candidates.length = 0;
|
|
4613
|
+
break;
|
|
4614
|
+
}
|
|
4615
|
+
candidates.push({ dir: nestedDir, version });
|
|
4616
|
+
}
|
|
4617
|
+
if (nestedCount === 0 || candidates.length === 0) continue;
|
|
4618
|
+
if (candidates.some((candidate) => !isComparableVersion(candidate.version))) {
|
|
4619
|
+
result.skipped.push(outerDir);
|
|
4620
|
+
continue;
|
|
4621
|
+
}
|
|
4622
|
+
let source = candidates[0];
|
|
4623
|
+
for (const candidate of candidates.slice(1)) {
|
|
4624
|
+
if (compareVersions(candidate.version, source.version) >= 0) source = candidate;
|
|
4625
|
+
}
|
|
4626
|
+
if (source.dir === outerDir) {
|
|
4627
|
+
result.skipped.push(outerDir);
|
|
4628
|
+
continue;
|
|
4629
|
+
}
|
|
4630
|
+
await promoteNestedSkill(source.dir, outerDir);
|
|
4631
|
+
result.repaired.push(outerDir);
|
|
4632
|
+
} catch (error) {
|
|
4633
|
+
result.errors.push({ path: outerDir, message: error?.message || String(error) });
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4637
|
+
return result;
|
|
4638
|
+
}
|
|
4639
|
+
|
|
4398
4640
|
// src/ws-client.ts
|
|
4399
4641
|
import WebSocket from "ws";
|
|
4400
|
-
import
|
|
4401
|
-
import
|
|
4642
|
+
import path9 from "path";
|
|
4643
|
+
import fs8 from "fs/promises";
|
|
4402
4644
|
import { DatabaseSync } from "node:sqlite";
|
|
4403
4645
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
4404
4646
|
var HEARTBEAT_ACK_TIMEOUT_MS = 75e3;
|
|
4405
4647
|
var AGENT_SCAN_INTERVAL_MS = 3 * 60 * 1e3;
|
|
4406
|
-
var ASSISTANT_WORKSPACE_PREFIX = "workspace-assistant-";
|
|
4407
4648
|
var ASSISTANT_AGENT_PREFIX = "assistant-";
|
|
4408
4649
|
var ASSISTANT_WORKSPACE_ID_RE = /^\d{5,}$/;
|
|
4409
|
-
function
|
|
4410
|
-
if (!
|
|
4411
|
-
const
|
|
4412
|
-
if (!
|
|
4413
|
-
|
|
4650
|
+
function enabledAgentIdsFromAccounts(config) {
|
|
4651
|
+
if (!config || typeof config !== "object") return [];
|
|
4652
|
+
const channels = config.channels;
|
|
4653
|
+
if (!channels || typeof channels !== "object") return [];
|
|
4654
|
+
const cworkConfig = channels.xg_cwork_im;
|
|
4655
|
+
if (!cworkConfig || typeof cworkConfig !== "object") return [];
|
|
4656
|
+
const accounts = cworkConfig.accounts;
|
|
4657
|
+
if (!accounts || typeof accounts !== "object" || Array.isArray(accounts)) return [];
|
|
4658
|
+
const agentIds = /* @__PURE__ */ new Set();
|
|
4659
|
+
for (const account of Object.values(accounts)) {
|
|
4660
|
+
if (!account || typeof account !== "object" || account.enabled === false) continue;
|
|
4661
|
+
if (typeof account.agentId !== "string") continue;
|
|
4662
|
+
const agentId = account.agentId.trim();
|
|
4663
|
+
if (agentId) agentIds.add(agentId);
|
|
4664
|
+
}
|
|
4665
|
+
return [...agentIds];
|
|
4666
|
+
}
|
|
4667
|
+
function resolveSkillInstallTarget(config, userId) {
|
|
4668
|
+
if (!config || typeof config !== "object") throw new Error("openclaw.json \u914D\u7F6E\u65E0\u6548");
|
|
4669
|
+
const root = config;
|
|
4670
|
+
const accounts = root.channels?.xg_cwork_im?.accounts;
|
|
4671
|
+
if (!accounts || typeof accounts !== "object" || Array.isArray(accounts)) {
|
|
4672
|
+
throw new Error(`\u672A\u627E\u5230 userId=${userId} \u7684 xg_cwork_im account`);
|
|
4673
|
+
}
|
|
4674
|
+
const matched = Object.entries(accounts).filter(([, account2]) => account2 && typeof account2 === "object" && account2.agentId === userId);
|
|
4675
|
+
if (matched.length !== 1) {
|
|
4676
|
+
throw new Error(matched.length > 1 ? `userId=${userId} \u5B58\u5728\u591A\u4E2A xg_cwork_im account\uFF0C\u65E0\u6CD5\u786E\u5B9A\u5B89\u88C5\u76EE\u5F55` : `\u672A\u627E\u5230 userId=${userId} \u7684 xg_cwork_im account`);
|
|
4677
|
+
}
|
|
4678
|
+
const [accountId, account] = matched[0];
|
|
4679
|
+
if (account.enabled === false) throw new Error(`userId=${userId} \u5BF9\u5E94 Agent \u5DF2\u7981\u7528`);
|
|
4680
|
+
const bindingAccountIds = /* @__PURE__ */ new Set([accountId, userId]);
|
|
4681
|
+
const bindings = Array.isArray(root.bindings) ? root.bindings : [];
|
|
4682
|
+
const binding = bindings.find((item) => {
|
|
4683
|
+
if (!item || typeof item !== "object") return false;
|
|
4684
|
+
const candidate = item;
|
|
4685
|
+
return candidate.match?.channel === "xg_cwork_im" && typeof candidate.match.accountId === "string" && bindingAccountIds.has(candidate.match.accountId) && typeof candidate.agentId === "string" && candidate.agentId.length > 0;
|
|
4686
|
+
});
|
|
4687
|
+
const localAgentId = typeof binding?.agentId === "string" ? binding.agentId : userId;
|
|
4688
|
+
const list = Array.isArray(root.agents?.list) ? root.agents.list : [];
|
|
4689
|
+
const agent = list.find(
|
|
4690
|
+
(item) => Boolean(item) && typeof item === "object" && item.id === localAgentId
|
|
4691
|
+
);
|
|
4692
|
+
if (!agent) throw new Error(`\u672A\u627E\u5230\u672C\u5730 Agent \u914D\u7F6E: ${localAgentId}`);
|
|
4693
|
+
const workspace = typeof agent.workspace === "string" ? agent.workspace : root.agents?.defaults?.workspace;
|
|
4694
|
+
if (typeof workspace !== "string" || !workspace.trim()) {
|
|
4695
|
+
throw new Error(`\u672C\u5730 Agent ${localAgentId} \u672A\u914D\u7F6E workspace`);
|
|
4696
|
+
}
|
|
4697
|
+
return { skillsDir: path9.join(workspace, "skills"), localAgentId };
|
|
4414
4698
|
}
|
|
4415
4699
|
function normalizeAssistantUserId(userId) {
|
|
4416
|
-
const safeUserId =
|
|
4700
|
+
const safeUserId = path9.basename(userId);
|
|
4417
4701
|
if (safeUserId !== userId) return void 0;
|
|
4418
4702
|
const pureId = safeUserId.startsWith(ASSISTANT_AGENT_PREFIX) ? safeUserId.slice(ASSISTANT_AGENT_PREFIX.length) : safeUserId;
|
|
4419
4703
|
if (!ASSISTANT_WORKSPACE_ID_RE.test(pureId)) return void 0;
|
|
4420
4704
|
return pureId;
|
|
4421
4705
|
}
|
|
4706
|
+
function normalizeCommandCode(code) {
|
|
4707
|
+
if (typeof code !== "string" || code.length === 0 || code === "." || code === "..") return void 0;
|
|
4708
|
+
if (code.includes("/") || code.includes("\\") || code.includes("\0")) return void 0;
|
|
4709
|
+
if (path9.basename(code) !== code) return void 0;
|
|
4710
|
+
return code;
|
|
4711
|
+
}
|
|
4422
4712
|
function shouldSyncBuiltInTemplate(action, isBuiltIn) {
|
|
4423
4713
|
return action === "UPDATE_SKILL" && isBuiltIn === true;
|
|
4424
4714
|
}
|
|
4715
|
+
async function findInstalledExpertSkillsRoot(rootPath, pureId, code) {
|
|
4716
|
+
const skillsRoot = path9.join(rootPath, `workspace-assistant-${pureId}`, ".user", "skills");
|
|
4717
|
+
try {
|
|
4718
|
+
const stat = await fs8.stat(path9.join(skillsRoot, code));
|
|
4719
|
+
return stat.isDirectory() ? skillsRoot : void 0;
|
|
4720
|
+
} catch {
|
|
4721
|
+
return void 0;
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4425
4724
|
function defaultOpenclawSqlitePath() {
|
|
4426
|
-
return
|
|
4725
|
+
return path9.join(openclawHome(), "state", "openclaw.sqlite");
|
|
4427
4726
|
}
|
|
4428
4727
|
function readCronJobsByAgentId(agentId, sqlitePath = defaultOpenclawSqlitePath(), onError) {
|
|
4429
4728
|
let db;
|
|
@@ -4463,10 +4762,10 @@ var GatewayWsClient = class {
|
|
|
4463
4762
|
}
|
|
4464
4763
|
}
|
|
4465
4764
|
logLine += "\n";
|
|
4466
|
-
const logsDir =
|
|
4467
|
-
|
|
4468
|
-
const logPath =
|
|
4469
|
-
|
|
4765
|
+
const logsDir = path9.join(openclawHome(), "logs");
|
|
4766
|
+
fs8.mkdir(logsDir, { recursive: true }).then(() => {
|
|
4767
|
+
const logPath = path9.join(logsDir, "skill-logger.err");
|
|
4768
|
+
fs8.appendFile(logPath, logLine).catch(() => {
|
|
4470
4769
|
});
|
|
4471
4770
|
}).catch(() => {
|
|
4472
4771
|
});
|
|
@@ -4481,6 +4780,17 @@ var GatewayWsClient = class {
|
|
|
4481
4780
|
});
|
|
4482
4781
|
};
|
|
4483
4782
|
}
|
|
4783
|
+
/** 普通 Skill 的所有读写操作共用这一个配置驱动的寻址入口。 */
|
|
4784
|
+
async resolveRegularSkillTarget(userId) {
|
|
4785
|
+
const configPath = path9.join(openclawHome(), "openclaw.json");
|
|
4786
|
+
let config;
|
|
4787
|
+
try {
|
|
4788
|
+
config = JSON.parse(await fs8.readFile(configPath, "utf-8"));
|
|
4789
|
+
} catch (err) {
|
|
4790
|
+
throw new Error(`\u65E0\u6CD5\u8BFB\u53D6 openclaw.json: ${err?.message || String(err)}`);
|
|
4791
|
+
}
|
|
4792
|
+
return resolveSkillInstallTarget(config, userId).skillsDir;
|
|
4793
|
+
}
|
|
4484
4794
|
constructor(options) {
|
|
4485
4795
|
this.options = options;
|
|
4486
4796
|
}
|
|
@@ -4576,26 +4886,26 @@ var GatewayWsClient = class {
|
|
|
4576
4886
|
}
|
|
4577
4887
|
});
|
|
4578
4888
|
}
|
|
4579
|
-
/**
|
|
4580
|
-
* 扫描 OpenClaw 根目录下的 workspace-assistant-{userId} 目录。
|
|
4581
|
-
* userId 必须是至少 5 位数字。
|
|
4582
|
-
*/
|
|
4889
|
+
/** 从 openclaw.json 的 xg_cwork_im accounts 读取当前启用的 agent。 */
|
|
4583
4890
|
async scanAndReportAgents(isInitialReport) {
|
|
4584
4891
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
4585
4892
|
try {
|
|
4586
|
-
const
|
|
4587
|
-
let
|
|
4893
|
+
const configPath = path9.join(openclawHome(), "openclaw.json");
|
|
4894
|
+
let rawConfig;
|
|
4588
4895
|
try {
|
|
4589
|
-
|
|
4896
|
+
rawConfig = await fs8.readFile(configPath, "utf-8");
|
|
4590
4897
|
} catch (e) {
|
|
4591
|
-
this.appendLogToFile("WARN", "AgentScan", "OpenClaw
|
|
4898
|
+
this.appendLogToFile("WARN", "AgentScan", "OpenClaw config is not readable; skipping this scan cycle", e);
|
|
4592
4899
|
return;
|
|
4593
4900
|
}
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4901
|
+
let config;
|
|
4902
|
+
try {
|
|
4903
|
+
config = JSON.parse(rawConfig);
|
|
4904
|
+
} catch (e) {
|
|
4905
|
+
this.appendLogToFile("WARN", "AgentScan", "OpenClaw config is invalid JSON; skipping this scan cycle", e);
|
|
4906
|
+
return;
|
|
4598
4907
|
}
|
|
4908
|
+
const newAgentIds = new Set(enabledAgentIdsFromAccounts(config));
|
|
4599
4909
|
let changed = false;
|
|
4600
4910
|
if (newAgentIds.size !== this.currentAgentIds.size) {
|
|
4601
4911
|
changed = true;
|
|
@@ -4747,7 +5057,7 @@ var GatewayWsClient = class {
|
|
|
4747
5057
|
return;
|
|
4748
5058
|
}
|
|
4749
5059
|
if (action === "GET_CRON_JOBS_BY_AGENT_ID") {
|
|
4750
|
-
const agentId = typeof msg.agent_id === "string" ? msg.agent_id : msg.agentId;
|
|
5060
|
+
const agentId = typeof msg.agent_id === "string" ? msg.agent_id : typeof msg.agentId === "string" ? msg.agentId : userId;
|
|
4751
5061
|
if (!agentId) {
|
|
4752
5062
|
this.reply(replyId, { success: false, message: "Missing agent_id parameter", action });
|
|
4753
5063
|
return;
|
|
@@ -4767,16 +5077,20 @@ var GatewayWsClient = class {
|
|
|
4767
5077
|
this.appendLogToFile("WARN", "Command", `Message dropped: missing userId`, msg);
|
|
4768
5078
|
return;
|
|
4769
5079
|
}
|
|
4770
|
-
const safeCode =
|
|
5080
|
+
const safeCode = normalizeCommandCode(code);
|
|
5081
|
+
if (code !== void 0 && !safeCode) {
|
|
5082
|
+
this.reply(replyId, { success: false, message: `Invalid code: ${String(code)}`, action });
|
|
5083
|
+
return;
|
|
5084
|
+
}
|
|
4771
5085
|
const pureId = normalizeAssistantUserId(userId);
|
|
4772
5086
|
if (!pureId) {
|
|
4773
5087
|
this.reply(replyId, { success: false, message: `Invalid userId: ${userId}`, action });
|
|
4774
5088
|
return;
|
|
4775
5089
|
}
|
|
4776
|
-
const targetDir = path8.join(openclawHome(), `workspace-assistant-${pureId}`, "skills");
|
|
4777
5090
|
try {
|
|
4778
5091
|
if (action === "INSTALL_SKILL") {
|
|
4779
5092
|
if (!safeCode) throw new Error("Missing code parameter");
|
|
5093
|
+
const targetDir = await this.resolveRegularSkillTarget(userId);
|
|
4780
5094
|
console.log(`[skill-logger-plugin][WS] Executing INSTALL for user ${userId}, code: ${safeCode}`);
|
|
4781
5095
|
this.appendLogToFile("INFO", "Command", `INSTALL_SKILL received`, { userId, code: safeCode, version });
|
|
4782
5096
|
const result = await this.options.updater.manualInstall({
|
|
@@ -4790,16 +5104,18 @@ var GatewayWsClient = class {
|
|
|
4790
5104
|
this.reply(replyId, { success: result.success, message: result.message, action });
|
|
4791
5105
|
} else if (action === "UNINSTALL_SKILL") {
|
|
4792
5106
|
if (!safeCode) throw new Error("Missing code parameter");
|
|
5107
|
+
const targetDir = await this.resolveRegularSkillTarget(userId);
|
|
4793
5108
|
console.log(`[skill-logger-plugin][WS] Executing UNINSTALL for user ${userId}, code: ${safeCode}`);
|
|
4794
5109
|
this.appendLogToFile("INFO", "Command", `UNINSTALL_SKILL received`, { userId, code: safeCode });
|
|
4795
|
-
const skillPath =
|
|
4796
|
-
await
|
|
5110
|
+
const skillPath = path9.join(targetDir, safeCode);
|
|
5111
|
+
await fs8.rm(skillPath, { recursive: true, force: true });
|
|
4797
5112
|
this.reply(replyId, { success: true, message: `Skill ${safeCode} removed`, action });
|
|
4798
5113
|
} else if (action === "LIST_SKILLS") {
|
|
5114
|
+
const targetDir = await this.resolveRegularSkillTarget(userId);
|
|
4799
5115
|
let list = [];
|
|
4800
5116
|
let targetDirExists = false;
|
|
4801
5117
|
try {
|
|
4802
|
-
const targetStat = await
|
|
5118
|
+
const targetStat = await fs8.stat(targetDir);
|
|
4803
5119
|
targetDirExists = targetStat.isDirectory();
|
|
4804
5120
|
} catch (err) {
|
|
4805
5121
|
if (err?.code !== "ENOENT") throw err;
|
|
@@ -4807,18 +5123,18 @@ var GatewayWsClient = class {
|
|
|
4807
5123
|
if (!targetDirExists) {
|
|
4808
5124
|
throw new Error(`Target skills directory does not exist: ${targetDir}`);
|
|
4809
5125
|
}
|
|
4810
|
-
const entries = await
|
|
5126
|
+
const entries = await fs8.readdir(targetDir, { withFileTypes: true });
|
|
4811
5127
|
const dirs = entries.filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith("."));
|
|
4812
5128
|
for (const e of dirs) {
|
|
4813
|
-
const skillDir =
|
|
4814
|
-
const skillMdPath =
|
|
5129
|
+
const skillDir = path9.join(targetDir, e.name);
|
|
5130
|
+
const skillMdPath = path9.join(skillDir, "SKILL.md");
|
|
4815
5131
|
try {
|
|
4816
|
-
const stat = await
|
|
5132
|
+
const stat = await fs8.stat(skillMdPath);
|
|
4817
5133
|
if (!stat.isFile()) continue;
|
|
4818
5134
|
} catch (err) {
|
|
4819
5135
|
continue;
|
|
4820
5136
|
}
|
|
4821
|
-
const metaPath =
|
|
5137
|
+
const metaPath = path9.join(skillDir, ".meta.json");
|
|
4822
5138
|
let isPlatform = false;
|
|
4823
5139
|
let isBuiltIn2 = e.isSymbolicLink();
|
|
4824
5140
|
let metaData = null;
|
|
@@ -4826,7 +5142,7 @@ var GatewayWsClient = class {
|
|
|
4826
5142
|
let description = "";
|
|
4827
5143
|
let skillVersion = "";
|
|
4828
5144
|
try {
|
|
4829
|
-
const mdContent = await
|
|
5145
|
+
const mdContent = await fs8.readFile(skillMdPath, "utf8");
|
|
4830
5146
|
const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(mdContent)?.[1] ?? "";
|
|
4831
5147
|
const parsedName = /(^|\n)name:\s*(.+)/.exec(fm)?.[2]?.trim();
|
|
4832
5148
|
if (parsedName) name = parsedName;
|
|
@@ -4837,7 +5153,7 @@ var GatewayWsClient = class {
|
|
|
4837
5153
|
} catch (err) {
|
|
4838
5154
|
}
|
|
4839
5155
|
try {
|
|
4840
|
-
const metaContent = await
|
|
5156
|
+
const metaContent = await fs8.readFile(metaPath, "utf8");
|
|
4841
5157
|
const parsed = JSON.parse(metaContent);
|
|
4842
5158
|
if (parsed) {
|
|
4843
5159
|
if (parsed.ownerId === "CMS" || parsed.ownerId === "CMS_COMPAT") isPlatform = true;
|
|
@@ -4885,13 +5201,10 @@ var GatewayWsClient = class {
|
|
|
4885
5201
|
});
|
|
4886
5202
|
setTimeout(async () => {
|
|
4887
5203
|
try {
|
|
4888
|
-
const
|
|
4889
|
-
const
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
additionalTargetDirs.push(userSkillDir);
|
|
4893
|
-
} catch {
|
|
4894
|
-
}
|
|
5204
|
+
const targetDir = await this.resolveRegularSkillTarget(userId);
|
|
5205
|
+
const additionalTargetDirs = syncBuiltInTemplate ? [path9.join(openclawHome(), "workspace-xgjk-assistant-template", "skills")] : [];
|
|
5206
|
+
const userSkillRoot = await findInstalledExpertSkillsRoot(openclawHome(), pureId, safeCode);
|
|
5207
|
+
if (userSkillRoot) additionalTargetDirs.push(userSkillRoot);
|
|
4895
5208
|
const result = await this.options.updater.manualInstall({
|
|
4896
5209
|
code: safeCode,
|
|
4897
5210
|
url,
|
|
@@ -4935,12 +5248,12 @@ var GatewayWsClient = class {
|
|
|
4935
5248
|
const { name, version: version2, downloadUrl, skills } = msg;
|
|
4936
5249
|
console.log(`[skill-logger-plugin][WS] Executing INSTALL_EXPERT for user ${userId}, code: ${safeCode}`);
|
|
4937
5250
|
this.appendLogToFile("INFO", "Command", `INSTALL_EXPERT received`, { userId, code: safeCode, version: version2, downloadUrl });
|
|
4938
|
-
const userSkillRoot =
|
|
4939
|
-
const expertTarget =
|
|
5251
|
+
const userSkillRoot = path9.join(openclawHome(), `workspace-assistant-${pureId}`, ".user");
|
|
5252
|
+
const expertTarget = path9.join(userSkillRoot, "experts", safeCode);
|
|
4940
5253
|
let skipInstall = false;
|
|
4941
|
-
const metaPath =
|
|
5254
|
+
const metaPath = path9.join(expertTarget, ".meta.json");
|
|
4942
5255
|
try {
|
|
4943
|
-
const raw = await
|
|
5256
|
+
const raw = await fs8.readFile(metaPath, "utf-8");
|
|
4944
5257
|
const existing = JSON.parse(raw);
|
|
4945
5258
|
if (existing.version && existing.version === (version2 || "1.0.0")) {
|
|
4946
5259
|
skipInstall = true;
|
|
@@ -4948,29 +5261,39 @@ var GatewayWsClient = class {
|
|
|
4948
5261
|
} catch {
|
|
4949
5262
|
}
|
|
4950
5263
|
if (!skipInstall) {
|
|
4951
|
-
await
|
|
4952
|
-
const expertResult = await this.options.updater.
|
|
5264
|
+
await fs8.mkdir(path9.dirname(expertTarget), { recursive: true });
|
|
5265
|
+
const expertResult = await this.options.updater.installExpertZipFromUrl(downloadUrl, expertTarget);
|
|
4953
5266
|
if (!expertResult.success) {
|
|
4954
5267
|
throw new Error(`\u4E13\u5BB6\u5B89\u88C5\u5931\u8D25: ${expertResult.message}`);
|
|
4955
5268
|
}
|
|
4956
5269
|
}
|
|
4957
5270
|
const meta = { code: safeCode, name, version: version2 || "1.0.0", installedAt: Date.now() };
|
|
4958
|
-
await
|
|
4959
|
-
const skillTargetRoot =
|
|
4960
|
-
await
|
|
5271
|
+
await fs8.writeFile(metaPath, JSON.stringify(meta, null, 2));
|
|
5272
|
+
const skillTargetRoot = path9.join(userSkillRoot, "skills");
|
|
5273
|
+
await fs8.mkdir(skillTargetRoot, { recursive: true });
|
|
4961
5274
|
const skillResults = [];
|
|
4962
5275
|
if (Array.isArray(skills)) {
|
|
4963
5276
|
for (const sk of skills) {
|
|
4964
|
-
|
|
4965
|
-
|
|
5277
|
+
const skillCode = normalizeCommandCode(sk?.code);
|
|
5278
|
+
if (!skillCode) {
|
|
5279
|
+
skillResults.push(`${String(sk?.code || "unknown")}: \u5931\u8D25 - \u975E\u6CD5\u7684 Skill code`);
|
|
5280
|
+
continue;
|
|
5281
|
+
}
|
|
5282
|
+
if (!sk.downloadUrl) {
|
|
5283
|
+
skillResults.push(`${skillCode}: \u5931\u8D25 - \u7F3A\u5C11\u4E0B\u8F7D\u5730\u5740`);
|
|
4966
5284
|
continue;
|
|
4967
5285
|
}
|
|
4968
5286
|
try {
|
|
4969
|
-
const
|
|
4970
|
-
|
|
4971
|
-
|
|
5287
|
+
const result = await this.options.updater.manualInstall({
|
|
5288
|
+
code: skillCode,
|
|
5289
|
+
url: sk.downloadUrl,
|
|
5290
|
+
version: sk.version,
|
|
5291
|
+
force: true,
|
|
5292
|
+
targetDir: skillTargetRoot
|
|
5293
|
+
});
|
|
5294
|
+
skillResults.push(`${skillCode}: ${result.success ? "\u6210\u529F" : "\u5931\u8D25 - " + result.message}`);
|
|
4972
5295
|
} catch (e) {
|
|
4973
|
-
skillResults.push(`${
|
|
5296
|
+
skillResults.push(`${skillCode}: \u5931\u8D25 - ${e.message}`);
|
|
4974
5297
|
}
|
|
4975
5298
|
}
|
|
4976
5299
|
}
|
|
@@ -4984,23 +5307,23 @@ var GatewayWsClient = class {
|
|
|
4984
5307
|
if (!safeCode) throw new Error("Missing code parameter");
|
|
4985
5308
|
console.log(`[skill-logger-plugin][WS] Executing UNINSTALL_EXPERT for user ${userId}, code: ${safeCode}`);
|
|
4986
5309
|
this.appendLogToFile("INFO", "Command", `UNINSTALL_EXPERT received`, { userId, code: safeCode });
|
|
4987
|
-
const expertPath =
|
|
4988
|
-
await
|
|
5310
|
+
const expertPath = path9.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "experts", safeCode);
|
|
5311
|
+
await fs8.rm(expertPath, { recursive: true, force: true });
|
|
4989
5312
|
this.reply(replyId, { success: true, message: `\u4E13\u5BB6 ${safeCode} \u5DF2\u5378\u8F7D`, action });
|
|
4990
5313
|
} else if (action === "LIST_EXPERTS") {
|
|
4991
5314
|
console.log(`[skill-logger-plugin][WS] Executing LIST_EXPERTS for user ${userId}`);
|
|
4992
5315
|
this.appendLogToFile("INFO", "Command", `LIST_EXPERTS received`, { userId });
|
|
4993
|
-
const expertsDir =
|
|
5316
|
+
const expertsDir = path9.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "experts");
|
|
4994
5317
|
const list = [];
|
|
4995
5318
|
try {
|
|
4996
|
-
const stat = await
|
|
5319
|
+
const stat = await fs8.stat(expertsDir);
|
|
4997
5320
|
if (stat.isDirectory()) {
|
|
4998
|
-
const entries = await
|
|
5321
|
+
const entries = await fs8.readdir(expertsDir, { withFileTypes: true });
|
|
4999
5322
|
for (const e of entries) {
|
|
5000
5323
|
if (!e.isDirectory()) continue;
|
|
5001
|
-
const metaPath =
|
|
5324
|
+
const metaPath = path9.join(expertsDir, e.name, ".meta.json");
|
|
5002
5325
|
try {
|
|
5003
|
-
const raw = await
|
|
5326
|
+
const raw = await fs8.readFile(metaPath, "utf-8");
|
|
5004
5327
|
const meta = JSON.parse(raw);
|
|
5005
5328
|
list.push({
|
|
5006
5329
|
code: meta.code || e.name,
|
|
@@ -5054,8 +5377,8 @@ var GatewayWsClient = class {
|
|
|
5054
5377
|
};
|
|
5055
5378
|
|
|
5056
5379
|
// src/hooks.ts
|
|
5057
|
-
import
|
|
5058
|
-
import { randomUUID as
|
|
5380
|
+
import path10 from "node:path";
|
|
5381
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
5059
5382
|
var PENDING_TTL_MS = 30 * 60 * 1e3;
|
|
5060
5383
|
var PENDING_MAX = 5e3;
|
|
5061
5384
|
function toMySQLDateTime(d) {
|
|
@@ -5063,7 +5386,7 @@ function toMySQLDateTime(d) {
|
|
|
5063
5386
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
5064
5387
|
}
|
|
5065
5388
|
function isSkillMdReadPath(filePath) {
|
|
5066
|
-
return
|
|
5389
|
+
return path10.basename(filePath) === "SKILL.md";
|
|
5067
5390
|
}
|
|
5068
5391
|
function extractAppKey(event, ctx) {
|
|
5069
5392
|
try {
|
|
@@ -5173,7 +5496,7 @@ var Hooks = class {
|
|
|
5173
5496
|
}
|
|
5174
5497
|
buildPendingEvent(p, status, error, durationMs, appKey) {
|
|
5175
5498
|
return {
|
|
5176
|
-
event_id:
|
|
5499
|
+
event_id: randomUUID3(),
|
|
5177
5500
|
event_type: "function_call",
|
|
5178
5501
|
skill_name: p.skillName,
|
|
5179
5502
|
skill_version: p.skillVersion,
|
|
@@ -5243,8 +5566,8 @@ var Hooks = class {
|
|
|
5243
5566
|
if (toolName === "read") {
|
|
5244
5567
|
const filePath = params.path ?? params.file_path ?? "";
|
|
5245
5568
|
if (!isSkillMdReadPath(filePath)) return;
|
|
5246
|
-
const rootDir =
|
|
5247
|
-
const skillName = this.configSync.resolveSkillName(rootDir) ||
|
|
5569
|
+
const rootDir = path10.dirname(filePath);
|
|
5570
|
+
const skillName = this.configSync.resolveSkillName(rootDir) || path10.basename(rootDir);
|
|
5248
5571
|
if (!skillName) return;
|
|
5249
5572
|
this.debug(`Intercepted 'read' for SKILL.md. Attributed to skill: ${skillName}`);
|
|
5250
5573
|
this.recordTrigger(skillName, "inline", "read", ctx, appKey);
|
|
@@ -5329,7 +5652,7 @@ var Hooks = class {
|
|
|
5329
5652
|
/** 记 skill 触发事件 + 标记激活 + 懒拉配置。 */
|
|
5330
5653
|
recordTrigger(skillName, invokeMode, invokeTool, ctx, appKey) {
|
|
5331
5654
|
this.emit({
|
|
5332
|
-
event_id:
|
|
5655
|
+
event_id: randomUUID3(),
|
|
5333
5656
|
event_type: "skill_trigger",
|
|
5334
5657
|
skill_name: skillName,
|
|
5335
5658
|
skill_version: this.configSync.getVersion(skillName),
|
|
@@ -5349,7 +5672,7 @@ var Hooks = class {
|
|
|
5349
5672
|
}
|
|
5350
5673
|
buildFunctionCall(res, command, invokeTool, status, error, durationMs, ctx, appKey) {
|
|
5351
5674
|
return {
|
|
5352
|
-
event_id:
|
|
5675
|
+
event_id: randomUUID3(),
|
|
5353
5676
|
event_type: "function_call",
|
|
5354
5677
|
skill_name: res.skillName,
|
|
5355
5678
|
skill_version: res.skillVersion,
|
|
@@ -5391,7 +5714,7 @@ var Hooks = class {
|
|
|
5391
5714
|
return;
|
|
5392
5715
|
}
|
|
5393
5716
|
this.emit({
|
|
5394
|
-
event_id:
|
|
5717
|
+
event_id: randomUUID3(),
|
|
5395
5718
|
event_type: "function_call",
|
|
5396
5719
|
skill_name: skillName,
|
|
5397
5720
|
match_type: void 0,
|
|
@@ -5444,9 +5767,9 @@ var definition = {
|
|
|
5444
5767
|
register(api) {
|
|
5445
5768
|
let pkgVersion = "unknown";
|
|
5446
5769
|
try {
|
|
5447
|
-
const dir =
|
|
5448
|
-
const pkgPath =
|
|
5449
|
-
const pkg = JSON.parse(
|
|
5770
|
+
const dir = path11.dirname(fileURLToPath2(import.meta.url));
|
|
5771
|
+
const pkgPath = path11.join(dir, "..", "package.json");
|
|
5772
|
+
const pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
|
|
5450
5773
|
if (pkg.version) pkgVersion = pkg.version;
|
|
5451
5774
|
} catch {
|
|
5452
5775
|
}
|
|
@@ -5463,6 +5786,25 @@ var definition = {
|
|
|
5463
5786
|
const configSync = new ConfigSync({ paths, getConfig, updater });
|
|
5464
5787
|
const reporter = new Reporter({ paths, getConfig });
|
|
5465
5788
|
const hooks = new Hooks(reporter, configSync, activeSkills, getConfig);
|
|
5789
|
+
let expertSkillLayoutRepair;
|
|
5790
|
+
const repairExpertSkillLayouts = () => {
|
|
5791
|
+
if (expertSkillLayoutRepair) return expertSkillLayoutRepair;
|
|
5792
|
+
expertSkillLayoutRepair = (async () => {
|
|
5793
|
+
const result = await repairNestedExpertSkillLayouts(openclawHome());
|
|
5794
|
+
if (result.repaired.length > 0) {
|
|
5795
|
+
console.log(`[skill-logger-plugin] \u5DF2\u4FEE\u590D ${result.repaired.length} \u4E2A\u4E13\u5BB6 Skill \u5D4C\u5957\u76EE\u5F55`);
|
|
5796
|
+
}
|
|
5797
|
+
for (const skippedPath of result.skipped) {
|
|
5798
|
+
console.warn(`[skill-logger-plugin] \u4E13\u5BB6 Skill \u5D4C\u5957\u76EE\u5F55\u7248\u672C\u65E0\u6CD5\u5B89\u5168\u63D0\u5347\uFF0C\u5DF2\u4FDD\u7559\u73B0\u573A: ${skippedPath}`);
|
|
5799
|
+
}
|
|
5800
|
+
for (const error of result.errors) {
|
|
5801
|
+
console.warn(`[skill-logger-plugin] \u4FEE\u590D\u4E13\u5BB6 Skill \u76EE\u5F55\u5931\u8D25: ${error.path}`, error.message);
|
|
5802
|
+
}
|
|
5803
|
+
})().finally(() => {
|
|
5804
|
+
expertSkillLayoutRepair = void 0;
|
|
5805
|
+
});
|
|
5806
|
+
return expertSkillLayoutRepair;
|
|
5807
|
+
};
|
|
5466
5808
|
let reconcileTimer;
|
|
5467
5809
|
const sessionUpdatedSkills = /* @__PURE__ */ new Set();
|
|
5468
5810
|
api.on("message_received", (event, ctx) => {
|
|
@@ -5513,9 +5855,8 @@ var definition = {
|
|
|
5513
5855
|
enableFileLog: currentConfig2.enableFileLog
|
|
5514
5856
|
// 将日志开关透传给客户端模块
|
|
5515
5857
|
});
|
|
5516
|
-
wsClient.connect();
|
|
5517
5858
|
}
|
|
5518
|
-
void configSync.load().then(() => configSync.reconcile()).catch((err) => console.warn("[skill-logger-plugin] \u542F\u52A8\u521D\u59CB\u5316\u5F02\u5E38", err));
|
|
5859
|
+
void repairExpertSkillLayouts().then(() => wsClient?.connect()).then(() => configSync.load()).then(() => configSync.reconcile()).catch((err) => console.warn("[skill-logger-plugin] \u542F\u52A8\u521D\u59CB\u5316\u5F02\u5E38", err));
|
|
5519
5860
|
reporter.startTimer();
|
|
5520
5861
|
if (!reconcileTimer) {
|
|
5521
5862
|
reconcileTimer = setInterval(() => void configSync.reconcile(), RECONCILE_INTERVAL_MS);
|