@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 CHANGED
@@ -360,25 +360,25 @@ var require_utils = __commonJS({
360
360
  }
361
361
  mkdirSync(folder);
362
362
  };
363
- Utils.prototype.writeFileTo = function(path11, content, overwrite, attr) {
363
+ Utils.prototype.writeFileTo = function(path12, content, overwrite, attr) {
364
364
  const self = this;
365
- if (self.fs.existsSync(path11)) {
365
+ if (self.fs.existsSync(path12)) {
366
366
  if (!overwrite) return false;
367
- var stat = self.fs.statSync(path11);
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(path11);
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(path11, "w", 438);
378
+ fd = self.fs.openSync(path12, "w", 438);
379
379
  } catch (e) {
380
- self.fs.chmodSync(path11, 438);
381
- fd = self.fs.openSync(path11, "w", 438);
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(path11, attr || 438);
390
+ self.fs.chmodSync(path12, attr || 438);
391
391
  return true;
392
392
  };
393
- Utils.prototype.writeFileToAsync = function(path11, content, overwrite, attr, callback) {
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(path11, function(exist) {
399
+ self.fs.exists(path12, function(exist) {
400
400
  if (exist && !overwrite) return callback(false);
401
- self.fs.stat(path11, function(err, 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(path11);
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(path11, attr || 438, function() {
418
+ self.fs.chmod(path12, attr || 438, function() {
419
419
  callback(true);
420
420
  });
421
421
  });
422
422
  });
423
423
  };
424
- self.fs.open(path11, "w", 438, function(err2, fd) {
424
+ self.fs.open(path12, "w", 438, function(err2, fd) {
425
425
  if (err2) {
426
- self.fs.chmod(path11, 438, function() {
427
- self.fs.open(path11, "w", 438, function(retryErr, fd2) {
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(path11) {
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 path12 = pth.join(dir, file);
452
- const stat = self.fs.statSync(path12);
453
- if (!pattern || pattern.test(path12)) {
454
- files.push(pth.normalize(path12) + (stat.isDirectory() ? self.sep : ""));
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(path12);
457
+ const realDir = self.fs.realpathSync(path13);
458
458
  if (!visited.has(realDir)) {
459
459
  visited.add(realDir);
460
- files = files.concat(findSync(path12, pattern, recursive, visited));
460
+ files = files.concat(findSync(path13, pattern, recursive, visited));
461
461
  }
462
462
  }
463
463
  });
464
464
  return files;
465
465
  }
466
- return findSync(path11, void 0, true, /* @__PURE__ */ new Set([self.fs.realpathSync(path11)]));
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(path11) {
543
- if (!path11) return "";
544
- const safeSuffix = pth.posix.normalize("/" + path11.split("\\").join("/"));
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(path11) {
548
- if (!path11) return "";
549
- const safeSuffix = pth.posix.normalize("/" + path11.split("\\").join("/"));
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 path11 = pth.normalize(pth.join(prefix, parts.slice(i, l).join(pth.sep)));
567
- if (path11 === prefix || path11.startsWith(prefix + pth.sep)) {
568
- return path11;
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(path11, { fs: fs9 }) {
615
- var _path = path11 || "", _obj = newAttr(), _stat = null;
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 && fs9.existsSync(_path)) {
627
- _stat = fs9.statSync(_path);
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 fs8 from "node:fs";
2754
- import path10 from "node:path";
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 path8 from "path";
4401
- import fs7 from "fs/promises";
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 parseAssistantWorkspaceAgentId(entryName) {
4410
- if (!entryName.startsWith(ASSISTANT_WORKSPACE_PREFIX)) return void 0;
4411
- const suffix = entryName.slice(ASSISTANT_WORKSPACE_PREFIX.length);
4412
- if (!ASSISTANT_WORKSPACE_ID_RE.test(suffix)) return void 0;
4413
- return `${ASSISTANT_AGENT_PREFIX}${suffix}`;
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 = path8.basename(userId);
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 path8.join(openclawHome(), "state", "openclaw.sqlite");
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 = path8.join(openclawHome(), "logs");
4467
- fs7.mkdir(logsDir, { recursive: true }).then(() => {
4468
- const logPath = path8.join(logsDir, "skill-logger.err");
4469
- fs7.appendFile(logPath, logLine).catch(() => {
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 rootPath = openclawHome();
4587
- let entries = [];
4893
+ const configPath = path9.join(openclawHome(), "openclaw.json");
4894
+ let rawConfig;
4588
4895
  try {
4589
- entries = await fs7.readdir(rootPath);
4896
+ rawConfig = await fs8.readFile(configPath, "utf-8");
4590
4897
  } catch (e) {
4591
- this.appendLogToFile("WARN", "AgentScan", "OpenClaw home is not readable; skipping this scan cycle", e);
4898
+ this.appendLogToFile("WARN", "AgentScan", "OpenClaw config is not readable; skipping this scan cycle", e);
4592
4899
  return;
4593
4900
  }
4594
- const newAgentIds = /* @__PURE__ */ new Set();
4595
- for (const entry of entries) {
4596
- const agentId = parseAssistantWorkspaceAgentId(entry);
4597
- if (agentId) newAgentIds.add(agentId);
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 = code ? path8.basename(code) : void 0;
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 = path8.join(targetDir, safeCode);
4796
- await fs7.rm(skillPath, { recursive: true, force: true });
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 fs7.stat(targetDir);
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 fs7.readdir(targetDir, { withFileTypes: true });
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 = path8.join(targetDir, e.name);
4814
- const skillMdPath = path8.join(skillDir, "SKILL.md");
5129
+ const skillDir = path9.join(targetDir, e.name);
5130
+ const skillMdPath = path9.join(skillDir, "SKILL.md");
4815
5131
  try {
4816
- const stat = await fs7.stat(skillMdPath);
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 = path8.join(skillDir, ".meta.json");
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 fs7.readFile(skillMdPath, "utf8");
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 fs7.readFile(metaPath, "utf8");
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 additionalTargetDirs = syncBuiltInTemplate ? [path8.join(openclawHome(), "workspace-xgjk-assistant-template", "skills")] : [];
4889
- const userSkillDir = path8.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "skills", safeCode);
4890
- try {
4891
- await fs7.stat(userSkillDir);
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 = path8.join(openclawHome(), `workspace-assistant-${pureId}`, ".user");
4939
- const expertTarget = path8.join(userSkillRoot, "experts", safeCode);
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 = path8.join(expertTarget, ".meta.json");
5254
+ const metaPath = path9.join(expertTarget, ".meta.json");
4942
5255
  try {
4943
- const raw = await fs7.readFile(metaPath, "utf-8");
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 fs7.mkdir(path8.dirname(expertTarget), { recursive: true });
4952
- const expertResult = await this.options.updater.installZipFromUrl(downloadUrl, expertTarget);
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 fs7.writeFile(metaPath, JSON.stringify(meta, null, 2));
4959
- const skillTargetRoot = path8.join(userSkillRoot, "skills");
4960
- await fs7.mkdir(skillTargetRoot, { recursive: true });
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
- if (!sk.code || !sk.downloadUrl) {
4965
- skillResults.push(`${sk.code || "unknown"}: \u7F3A\u5C11\u4E0B\u8F7D\u5730\u5740`);
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 skTarget = path8.join(skillTargetRoot, sk.code);
4970
- const result = await this.options.updater.installZipFromUrl(sk.downloadUrl, skTarget);
4971
- skillResults.push(`${sk.code}: ${result.success ? "\u6210\u529F" : "\u5931\u8D25 - " + result.message}`);
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(`${sk.code}: \u5931\u8D25 - ${e.message}`);
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 = path8.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "experts", safeCode);
4988
- await fs7.rm(expertPath, { recursive: true, force: true });
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 = path8.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "experts");
5316
+ const expertsDir = path9.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "experts");
4994
5317
  const list = [];
4995
5318
  try {
4996
- const stat = await fs7.stat(expertsDir);
5319
+ const stat = await fs8.stat(expertsDir);
4997
5320
  if (stat.isDirectory()) {
4998
- const entries = await fs7.readdir(expertsDir, { withFileTypes: true });
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 = path8.join(expertsDir, e.name, ".meta.json");
5324
+ const metaPath = path9.join(expertsDir, e.name, ".meta.json");
5002
5325
  try {
5003
- const raw = await fs7.readFile(metaPath, "utf-8");
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 path9 from "node:path";
5058
- import { randomUUID as randomUUID2 } from "node:crypto";
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 path9.basename(filePath) === "SKILL.md";
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: randomUUID2(),
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 = path9.dirname(filePath);
5247
- const skillName = this.configSync.resolveSkillName(rootDir) || path9.basename(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: randomUUID2(),
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: randomUUID2(),
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: randomUUID2(),
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 = path10.dirname(fileURLToPath2(import.meta.url));
5448
- const pkgPath = path10.join(dir, "..", "package.json");
5449
- const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
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);