@spzhongwin/skill-logger-plugin 1.0.17 → 1.0.19

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(path12, content, overwrite, attr) {
363
+ Utils.prototype.writeFileTo = function(path16, content, overwrite, attr) {
364
364
  const self = this;
365
- if (self.fs.existsSync(path12)) {
365
+ if (self.fs.existsSync(path16)) {
366
366
  if (!overwrite) return false;
367
- var stat = self.fs.statSync(path12);
367
+ var stat = self.fs.statSync(path16);
368
368
  if (stat.isDirectory()) {
369
369
  return false;
370
370
  }
371
371
  }
372
- var folder = pth.dirname(path12);
372
+ var folder = pth.dirname(path16);
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(path12, "w", 438);
378
+ fd = self.fs.openSync(path16, "w", 438);
379
379
  } catch (e) {
380
- self.fs.chmodSync(path12, 438);
381
- fd = self.fs.openSync(path12, "w", 438);
380
+ self.fs.chmodSync(path16, 438);
381
+ fd = self.fs.openSync(path16, "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(path12, attr || 438);
390
+ self.fs.chmodSync(path16, attr || 438);
391
391
  return true;
392
392
  };
393
- Utils.prototype.writeFileToAsync = function(path12, content, overwrite, attr, callback) {
393
+ Utils.prototype.writeFileToAsync = function(path16, 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(path12, function(exist) {
399
+ self.fs.exists(path16, function(exist) {
400
400
  if (exist && !overwrite) return callback(false);
401
- self.fs.stat(path12, function(err, stat) {
401
+ self.fs.stat(path16, function(err, stat) {
402
402
  if (exist && stat && stat.isDirectory()) {
403
403
  return callback(false);
404
404
  }
405
- var folder = pth.dirname(path12);
405
+ var folder = pth.dirname(path16);
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(path12, attr || 438, function() {
418
+ self.fs.chmod(path16, attr || 438, function() {
419
419
  callback(true);
420
420
  });
421
421
  });
422
422
  });
423
423
  };
424
- self.fs.open(path12, "w", 438, function(err2, fd) {
424
+ self.fs.open(path16, "w", 438, function(err2, fd) {
425
425
  if (err2) {
426
- self.fs.chmod(path12, 438, function() {
427
- self.fs.open(path12, "w", 438, function(retryErr, fd2) {
426
+ self.fs.chmod(path16, 438, function() {
427
+ self.fs.open(path16, "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(path12) {
442
+ Utils.prototype.findFiles = function(path16) {
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 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 : ""));
451
+ const path17 = pth.join(dir, file);
452
+ const stat = self.fs.statSync(path17);
453
+ if (!pattern || pattern.test(path17)) {
454
+ files.push(pth.normalize(path17) + (stat.isDirectory() ? self.sep : ""));
455
455
  }
456
456
  if (stat.isDirectory() && recursive) {
457
- const realDir = self.fs.realpathSync(path13);
457
+ const realDir = self.fs.realpathSync(path17);
458
458
  if (!visited.has(realDir)) {
459
459
  visited.add(realDir);
460
- files = files.concat(findSync(path13, pattern, recursive, visited));
460
+ files = files.concat(findSync(path17, pattern, recursive, visited));
461
461
  }
462
462
  }
463
463
  });
464
464
  return files;
465
465
  }
466
- return findSync(path12, void 0, true, /* @__PURE__ */ new Set([self.fs.realpathSync(path12)]));
466
+ return findSync(path16, void 0, true, /* @__PURE__ */ new Set([self.fs.realpathSync(path16)]));
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(path12) {
543
- if (!path12) return "";
544
- const safeSuffix = pth.posix.normalize("/" + path12.split("\\").join("/"));
542
+ Utils.canonical = function(path16) {
543
+ if (!path16) return "";
544
+ const safeSuffix = pth.posix.normalize("/" + path16.split("\\").join("/"));
545
545
  return pth.join(".", safeSuffix);
546
546
  };
547
- Utils.zipnamefix = function(path12) {
548
- if (!path12) return "";
549
- const safeSuffix = pth.posix.normalize("/" + path12.split("\\").join("/"));
547
+ Utils.zipnamefix = function(path16) {
548
+ if (!path16) return "";
549
+ const safeSuffix = pth.posix.normalize("/" + path16.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 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;
566
+ var path16 = pth.normalize(pth.join(prefix, parts.slice(i, l).join(pth.sep)));
567
+ if (path16 === prefix || path16.startsWith(prefix + pth.sep)) {
568
+ return path16;
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(path12, { fs: fs10 }) {
615
- var _path = path12 || "", _obj = newAttr(), _stat = null;
614
+ module.exports = function(path16, { fs: fs13 }) {
615
+ var _path = path16 || "", _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 && fs10.existsSync(_path)) {
627
- _stat = fs10.statSync(_path);
626
+ if (_path && fs13.existsSync(_path)) {
627
+ _stat = fs13.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 fs9 from "node:fs";
2754
- import path11 from "node:path";
2753
+ import fs12 from "node:fs";
2754
+ import path15 from "node:path";
2755
2755
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2756
2756
  import os4 from "node:os";
2757
2757
 
@@ -4639,9 +4639,151 @@ async function repairNestedExpertSkillLayouts(openclawRoot) {
4639
4639
 
4640
4640
  // src/ws-client.ts
4641
4641
  import WebSocket from "ws";
4642
- import path9 from "path";
4643
- import fs8 from "fs/promises";
4642
+ import path10 from "path";
4643
+ import fs9 from "fs/promises";
4644
4644
  import { DatabaseSync } from "node:sqlite";
4645
+
4646
+ // src/free-skill-directory.ts
4647
+ import fs8 from "node:fs/promises";
4648
+ import path9 from "node:path";
4649
+ var FREE_SKILL_DIRECTORY_NAME = ".xg-platform";
4650
+ var SKILL_FILE_NAME = "SKILL.md";
4651
+ function isRecord(value) {
4652
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4653
+ }
4654
+ function isWithin(parent, candidate) {
4655
+ const relative = path9.relative(parent, candidate);
4656
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path9.sep}`) && !path9.isAbsolute(relative);
4657
+ }
4658
+ function asNonEmptyString(value) {
4659
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
4660
+ }
4661
+ function parseScalar(value) {
4662
+ const trimmed = value.trim().replace(/\s+#.*$/, "").trim();
4663
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
4664
+ return trimmed.slice(1, -1).trim();
4665
+ }
4666
+ return trimmed;
4667
+ }
4668
+ function parseFrontmatter(content) {
4669
+ const lines = content.replace(/^\uFEFF/, "").split(/\r?\n/);
4670
+ if (lines[0]?.trim() !== "---") return {};
4671
+ const fields = {};
4672
+ for (const line of lines.slice(1)) {
4673
+ if (line.trim() === "---" || line.trim() === "...") break;
4674
+ const match2 = /^\s*([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*?)\s*$/.exec(line);
4675
+ if (match2) fields[match2[1]] = parseScalar(match2[2]);
4676
+ }
4677
+ return fields;
4678
+ }
4679
+ async function realPathIfRegularFile(filePath) {
4680
+ try {
4681
+ const resolved = await fs8.realpath(filePath);
4682
+ const stats = await fs8.stat(resolved);
4683
+ return stats.isFile() ? resolved : void 0;
4684
+ } catch {
4685
+ return void 0;
4686
+ }
4687
+ }
4688
+ async function readOptionalMetadata(skillPath, workspacePath) {
4689
+ const candidate = path9.resolve(skillPath, ".meta.json");
4690
+ if (!isWithin(skillPath, candidate) || !isWithin(workspacePath, candidate)) return {};
4691
+ const metadataPath = await realPathIfRegularFile(candidate);
4692
+ if (!metadataPath || !isWithin(skillPath, metadataPath) || !isWithin(workspacePath, metadataPath)) return {};
4693
+ try {
4694
+ const parsed = JSON.parse(await fs8.readFile(metadataPath, "utf8"));
4695
+ if (!isRecord(parsed)) return {};
4696
+ return parsed;
4697
+ } catch {
4698
+ return {};
4699
+ }
4700
+ }
4701
+ async function scanSkill(directoryPath, workspacePath, entryName) {
4702
+ const candidateSkillPath = path9.resolve(directoryPath, entryName);
4703
+ if (!isWithin(directoryPath, candidateSkillPath) || !isWithin(workspacePath, candidateSkillPath)) return void 0;
4704
+ const skillPath = await realPathIfRegularFile(candidateSkillPath);
4705
+ if (skillPath) return void 0;
4706
+ let resolvedSkillPath;
4707
+ try {
4708
+ resolvedSkillPath = await fs8.realpath(candidateSkillPath);
4709
+ const stats = await fs8.stat(resolvedSkillPath);
4710
+ if (!stats.isDirectory()) return void 0;
4711
+ } catch {
4712
+ return void 0;
4713
+ }
4714
+ if (!isWithin(directoryPath, resolvedSkillPath) || !isWithin(workspacePath, resolvedSkillPath)) return void 0;
4715
+ const candidateSkillFilePath = path9.resolve(resolvedSkillPath, SKILL_FILE_NAME);
4716
+ if (!isWithin(resolvedSkillPath, candidateSkillFilePath) || !isWithin(workspacePath, candidateSkillFilePath)) {
4717
+ return void 0;
4718
+ }
4719
+ const skillFilePath = await realPathIfRegularFile(candidateSkillFilePath);
4720
+ if (!skillFilePath || !isWithin(resolvedSkillPath, skillFilePath) || !isWithin(workspacePath, skillFilePath)) {
4721
+ return void 0;
4722
+ }
4723
+ let skillFileContent;
4724
+ try {
4725
+ skillFileContent = await fs8.readFile(skillFilePath, "utf8");
4726
+ } catch {
4727
+ return void 0;
4728
+ }
4729
+ const frontmatter = parseFrontmatter(skillFileContent);
4730
+ const meta = await readOptionalMetadata(resolvedSkillPath, workspacePath);
4731
+ const nestedMetadata = isRecord(meta.metadata) ? meta.metadata : {};
4732
+ const getValue = (...values) => {
4733
+ for (const value of values) {
4734
+ const result = asNonEmptyString(value);
4735
+ if (result) return result;
4736
+ }
4737
+ return void 0;
4738
+ };
4739
+ const code = entryName;
4740
+ return {
4741
+ code,
4742
+ name: getValue(meta.name, nestedMetadata.name, frontmatter.name) ?? code,
4743
+ description: getValue(meta.description, nestedMetadata.description, frontmatter.description) ?? "",
4744
+ version: getValue(meta.version, nestedMetadata.version, frontmatter.version) ?? "",
4745
+ directoryPath,
4746
+ skillPath: resolvedSkillPath,
4747
+ skillFilePath
4748
+ };
4749
+ }
4750
+ async function scanFreeSkillDirectory(workspace) {
4751
+ const resolvedWorkspace = path9.resolve(workspace);
4752
+ let workspacePath;
4753
+ try {
4754
+ workspacePath = await fs8.realpath(resolvedWorkspace);
4755
+ const stats = await fs8.stat(workspacePath);
4756
+ if (!stats.isDirectory()) return [];
4757
+ } catch {
4758
+ return [];
4759
+ }
4760
+ const candidateDirectoryPath = path9.resolve(workspacePath, FREE_SKILL_DIRECTORY_NAME);
4761
+ if (!isWithin(workspacePath, candidateDirectoryPath)) return [];
4762
+ let directoryPath;
4763
+ try {
4764
+ directoryPath = await fs8.realpath(candidateDirectoryPath);
4765
+ const stats = await fs8.stat(directoryPath);
4766
+ if (!stats.isDirectory()) return [];
4767
+ } catch {
4768
+ return [];
4769
+ }
4770
+ if (!isWithin(workspacePath, directoryPath)) return [];
4771
+ let entries;
4772
+ try {
4773
+ entries = await fs8.readdir(directoryPath, { withFileTypes: true });
4774
+ } catch {
4775
+ return [];
4776
+ }
4777
+ const skills = [];
4778
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
4779
+ if (!entry.isDirectory()) continue;
4780
+ const skill = await scanSkill(directoryPath, workspacePath, entry.name);
4781
+ if (skill) skills.push(skill);
4782
+ }
4783
+ return skills;
4784
+ }
4785
+
4786
+ // src/ws-client.ts
4645
4787
  var HEARTBEAT_INTERVAL_MS = 3e4;
4646
4788
  var HEARTBEAT_ACK_TIMEOUT_MS = 75e3;
4647
4789
  var AGENT_SCAN_INTERVAL_MS = 3 * 60 * 1e3;
@@ -4694,10 +4836,10 @@ function resolveSkillInstallTarget(config, userId) {
4694
4836
  if (typeof workspace !== "string" || !workspace.trim()) {
4695
4837
  throw new Error(`\u672C\u5730 Agent ${localAgentId} \u672A\u914D\u7F6E workspace`);
4696
4838
  }
4697
- return { skillsDir: path9.join(workspace, "skills"), localAgentId };
4839
+ return { skillsDir: path10.join(workspace, "skills"), localAgentId };
4698
4840
  }
4699
4841
  function normalizeAssistantUserId(userId) {
4700
- const safeUserId = path9.basename(userId);
4842
+ const safeUserId = path10.basename(userId);
4701
4843
  if (safeUserId !== userId) return void 0;
4702
4844
  const pureId = safeUserId.startsWith(ASSISTANT_AGENT_PREFIX) ? safeUserId.slice(ASSISTANT_AGENT_PREFIX.length) : safeUserId;
4703
4845
  if (!ASSISTANT_WORKSPACE_ID_RE.test(pureId)) return void 0;
@@ -4706,23 +4848,29 @@ function normalizeAssistantUserId(userId) {
4706
4848
  function normalizeCommandCode(code) {
4707
4849
  if (typeof code !== "string" || code.length === 0 || code === "." || code === "..") return void 0;
4708
4850
  if (code.includes("/") || code.includes("\\") || code.includes("\0")) return void 0;
4709
- if (path9.basename(code) !== code) return void 0;
4851
+ if (path10.basename(code) !== code) return void 0;
4710
4852
  return code;
4711
4853
  }
4712
4854
  function shouldSyncBuiltInTemplate(action, isBuiltIn) {
4713
4855
  return action === "UPDATE_SKILL" && isBuiltIn === true;
4714
4856
  }
4857
+ function resolveGatewaySkillTarget(home = openclawHome()) {
4858
+ return path10.join(home, "skills");
4859
+ }
4860
+ function isGatewaySkillCommand(action, installScope) {
4861
+ return installScope === "gateway" && ["INSTALL_SKILL", "UPDATE_SKILL", "UNINSTALL_SKILL"].includes(String(action || ""));
4862
+ }
4715
4863
  async function findInstalledExpertSkillsRoot(rootPath, pureId, code) {
4716
- const skillsRoot = path9.join(rootPath, `workspace-assistant-${pureId}`, ".user", "skills");
4864
+ const skillsRoot = path10.join(rootPath, `workspace-assistant-${pureId}`, ".user", "skills");
4717
4865
  try {
4718
- const stat = await fs8.stat(path9.join(skillsRoot, code));
4866
+ const stat = await fs9.stat(path10.join(skillsRoot, code));
4719
4867
  return stat.isDirectory() ? skillsRoot : void 0;
4720
4868
  } catch {
4721
4869
  return void 0;
4722
4870
  }
4723
4871
  }
4724
4872
  function defaultOpenclawSqlitePath() {
4725
- return path9.join(openclawHome(), "state", "openclaw.sqlite");
4873
+ return path10.join(openclawHome(), "state", "openclaw.sqlite");
4726
4874
  }
4727
4875
  function readCronJobsByAgentId(agentId, sqlitePath = defaultOpenclawSqlitePath(), onError) {
4728
4876
  let db;
@@ -4736,6 +4884,49 @@ function readCronJobsByAgentId(agentId, sqlitePath = defaultOpenclawSqlitePath()
4736
4884
  db?.close();
4737
4885
  }
4738
4886
  }
4887
+ function isWithinPath(parent, candidate) {
4888
+ const relative = path10.relative(parent, candidate);
4889
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path10.sep}`) && !path10.isAbsolute(relative);
4890
+ }
4891
+ function relativeWorkspacePath(workspace, target) {
4892
+ const relative = path10.relative(workspace, target);
4893
+ return isWithinPath(workspace, target) ? relative : "";
4894
+ }
4895
+ async function resolveRealWorkspace(workspace) {
4896
+ const resolved = path10.resolve(workspace);
4897
+ try {
4898
+ return await fs9.realpath(resolved);
4899
+ } catch {
4900
+ return resolved;
4901
+ }
4902
+ }
4903
+ async function resolveFreeSkillDirectoryPath(workspace) {
4904
+ const workspacePath = await resolveRealWorkspace(workspace);
4905
+ const candidate = path10.resolve(workspacePath, ".xg-platform");
4906
+ try {
4907
+ const directoryPath = await fs9.realpath(candidate);
4908
+ const stat = await fs9.stat(directoryPath);
4909
+ return stat.isDirectory() && isWithinPath(workspacePath, directoryPath) ? directoryPath : "";
4910
+ } catch {
4911
+ return "";
4912
+ }
4913
+ }
4914
+ function buildFreeSkillDirectoryResponse(gatewayId, agentId, workspacePath, directoryPath, skills) {
4915
+ return {
4916
+ gatewayId,
4917
+ agentId,
4918
+ directoryPath,
4919
+ skills: skills.map((skill) => ({
4920
+ code: skill.code,
4921
+ name: skill.name,
4922
+ description: skill.description,
4923
+ version: skill.version,
4924
+ workspaceRelativePath: relativeWorkspacePath(workspacePath, skill.skillPath),
4925
+ skillFilePath: relativeWorkspacePath(workspacePath, skill.skillFilePath),
4926
+ hostSkillFilePath: skill.skillFilePath
4927
+ }))
4928
+ };
4929
+ }
4739
4930
  var GatewayWsClient = class {
4740
4931
  ws = null;
4741
4932
  options;
@@ -4762,10 +4953,10 @@ var GatewayWsClient = class {
4762
4953
  }
4763
4954
  }
4764
4955
  logLine += "\n";
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(() => {
4956
+ const logsDir = path10.join(openclawHome(), "logs");
4957
+ fs9.mkdir(logsDir, { recursive: true }).then(() => {
4958
+ const logPath = path10.join(logsDir, "skill-logger.err");
4959
+ fs9.appendFile(logPath, logLine).catch(() => {
4769
4960
  });
4770
4961
  }).catch(() => {
4771
4962
  });
@@ -4780,16 +4971,25 @@ var GatewayWsClient = class {
4780
4971
  });
4781
4972
  };
4782
4973
  }
4783
- /** 普通 Skill 的所有读写操作共用这一个配置驱动的寻址入口。 */
4784
- async resolveRegularSkillTarget(userId) {
4785
- const configPath = path9.join(openclawHome(), "openclaw.json");
4786
- let config;
4974
+ async readOpenclawConfig() {
4975
+ const configPath = path10.join(openclawHome(), "openclaw.json");
4787
4976
  try {
4788
- config = JSON.parse(await fs8.readFile(configPath, "utf-8"));
4977
+ return JSON.parse(await fs9.readFile(configPath, "utf-8"));
4789
4978
  } catch (err) {
4790
4979
  throw new Error(`\u65E0\u6CD5\u8BFB\u53D6 openclaw.json: ${err?.message || String(err)}`);
4791
4980
  }
4792
- return resolveSkillInstallTarget(config, userId).skillsDir;
4981
+ }
4982
+ async resolveRegularSkillContext(userId) {
4983
+ const target = resolveSkillInstallTarget(await this.readOpenclawConfig(), userId);
4984
+ return { ...target, workspace: path10.dirname(target.skillsDir) };
4985
+ }
4986
+ /** 普通 Skill 的所有读写操作共用这一个配置驱动的寻址入口。 */
4987
+ async resolveRegularSkillTarget(userId) {
4988
+ return (await this.resolveRegularSkillContext(userId)).skillsDir;
4989
+ }
4990
+ async resolveFreeSkillRequestContext(userId) {
4991
+ const target = await this.resolveRegularSkillContext(userId);
4992
+ return { workspace: target.workspace, agentId: target.localAgentId };
4793
4993
  }
4794
4994
  constructor(options) {
4795
4995
  this.options = options;
@@ -4890,10 +5090,10 @@ var GatewayWsClient = class {
4890
5090
  async scanAndReportAgents(isInitialReport) {
4891
5091
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
4892
5092
  try {
4893
- const configPath = path9.join(openclawHome(), "openclaw.json");
5093
+ const configPath = path10.join(openclawHome(), "openclaw.json");
4894
5094
  let rawConfig;
4895
5095
  try {
4896
- rawConfig = await fs8.readFile(configPath, "utf-8");
5096
+ rawConfig = await fs9.readFile(configPath, "utf-8");
4897
5097
  } catch (e) {
4898
5098
  this.appendLogToFile("WARN", "AgentScan", "OpenClaw config is not readable; skipping this scan cycle", e);
4899
5099
  return;
@@ -5013,7 +5213,8 @@ var GatewayWsClient = class {
5013
5213
  gatewayId: this.options.gatewayId,
5014
5214
  agentIds: Array.from(this.currentAgentIds),
5015
5215
  clientTime: Date.now(),
5016
- supportsBatch: true
5216
+ supportsBatch: true,
5217
+ supportsGatewaySkillScope: true
5017
5218
  }, "Heartbeat");
5018
5219
  }
5019
5220
  sendClientHeartbeat() {
@@ -5042,7 +5243,7 @@ var GatewayWsClient = class {
5042
5243
  * 核心指令分发中心:完全跳过沙盒,基于 userId 直接进行底层物理文件操作
5043
5244
  */
5044
5245
  async handleMessage(msg) {
5045
- const { action, userId, code, url, force, version, replyId, isBuiltIn } = msg;
5246
+ const { action, userId, code, url, force, version, replyId, isBuiltIn, installScope } = msg;
5046
5247
  this.appendLogToFile("INFO", "Command", `Received WS message`, {
5047
5248
  action,
5048
5249
  userId,
@@ -5050,6 +5251,7 @@ var GatewayWsClient = class {
5050
5251
  version,
5051
5252
  replyId,
5052
5253
  isBuiltIn,
5254
+ installScope,
5053
5255
  hasDirectUrl: Boolean(url)
5054
5256
  });
5055
5257
  if (!action) {
@@ -5075,6 +5277,14 @@ var GatewayWsClient = class {
5075
5277
  }
5076
5278
  if (!userId) {
5077
5279
  this.appendLogToFile("WARN", "Command", `Message dropped: missing userId`, msg);
5280
+ if (action === "LIST_FREE_SKILLS") {
5281
+ this.reply(replyId, {
5282
+ success: false,
5283
+ error: "INVALID_REQUEST",
5284
+ message: "Missing userId parameter",
5285
+ action
5286
+ });
5287
+ }
5078
5288
  return;
5079
5289
  }
5080
5290
  const safeCode = normalizeCommandCode(code);
@@ -5082,15 +5292,16 @@ var GatewayWsClient = class {
5082
5292
  this.reply(replyId, { success: false, message: `Invalid code: ${String(code)}`, action });
5083
5293
  return;
5084
5294
  }
5085
- const pureId = normalizeAssistantUserId(userId);
5086
- if (!pureId) {
5295
+ const gatewaySkillCommand = isGatewaySkillCommand(action, installScope);
5296
+ const pureId = gatewaySkillCommand ? void 0 : normalizeAssistantUserId(userId);
5297
+ if (!gatewaySkillCommand && !pureId) {
5087
5298
  this.reply(replyId, { success: false, message: `Invalid userId: ${userId}`, action });
5088
5299
  return;
5089
5300
  }
5090
5301
  try {
5091
5302
  if (action === "INSTALL_SKILL") {
5092
5303
  if (!safeCode) throw new Error("Missing code parameter");
5093
- const targetDir = await this.resolveRegularSkillTarget(userId);
5304
+ const targetDir = gatewaySkillCommand ? resolveGatewaySkillTarget() : await this.resolveRegularSkillTarget(userId);
5094
5305
  console.log(`[skill-logger-plugin][WS] Executing INSTALL for user ${userId}, code: ${safeCode}`);
5095
5306
  this.appendLogToFile("INFO", "Command", `INSTALL_SKILL received`, { userId, code: safeCode, version });
5096
5307
  const result = await this.options.updater.manualInstall({
@@ -5101,109 +5312,132 @@ var GatewayWsClient = class {
5101
5312
  targetDir,
5102
5313
  trace: this.createInstallTrace({ action, replyId, userId, code: safeCode })
5103
5314
  });
5104
- this.reply(replyId, { success: result.success, message: result.message, action });
5315
+ this.reply(replyId, {
5316
+ success: result.success,
5317
+ message: result.message,
5318
+ action,
5319
+ data: { code: safeCode, version, installScope: gatewaySkillCommand ? "gateway" : "agent" }
5320
+ });
5105
5321
  } else if (action === "UNINSTALL_SKILL") {
5106
5322
  if (!safeCode) throw new Error("Missing code parameter");
5107
- const targetDir = await this.resolveRegularSkillTarget(userId);
5323
+ const targetDir = gatewaySkillCommand ? resolveGatewaySkillTarget() : await this.resolveRegularSkillTarget(userId);
5108
5324
  console.log(`[skill-logger-plugin][WS] Executing UNINSTALL for user ${userId}, code: ${safeCode}`);
5109
5325
  this.appendLogToFile("INFO", "Command", `UNINSTALL_SKILL received`, { userId, code: safeCode });
5110
- const skillPath = path9.join(targetDir, safeCode);
5111
- await fs8.rm(skillPath, { recursive: true, force: true });
5112
- this.reply(replyId, { success: true, message: `Skill ${safeCode} removed`, action });
5326
+ const skillPath = path10.join(targetDir, safeCode);
5327
+ await fs9.rm(skillPath, { recursive: true, force: true });
5328
+ this.reply(replyId, {
5329
+ success: true,
5330
+ message: `Skill ${safeCode} removed`,
5331
+ action,
5332
+ data: { code: safeCode, installScope: gatewaySkillCommand ? "gateway" : "agent", removed: true }
5333
+ });
5334
+ } else if (action === "LIST_FREE_SKILLS") {
5335
+ const context = await this.resolveFreeSkillRequestContext(userId);
5336
+ const workspacePath = await resolveRealWorkspace(context.workspace);
5337
+ const scannedSkills = await scanFreeSkillDirectory(context.workspace);
5338
+ const directoryPath = scannedSkills[0]?.directoryPath || await resolveFreeSkillDirectoryPath(context.workspace);
5339
+ const data = buildFreeSkillDirectoryResponse(
5340
+ this.options.gatewayId,
5341
+ context.agentId,
5342
+ workspacePath,
5343
+ directoryPath,
5344
+ scannedSkills
5345
+ );
5346
+ this.reply(replyId, { success: true, data, action });
5113
5347
  } else if (action === "LIST_SKILLS") {
5114
- const targetDir = await this.resolveRegularSkillTarget(userId);
5115
- let list = [];
5116
- let targetDirExists = false;
5117
- try {
5118
- const targetStat = await fs8.stat(targetDir);
5119
- targetDirExists = targetStat.isDirectory();
5120
- } catch (err) {
5121
- if (err?.code !== "ENOENT") throw err;
5122
- }
5123
- if (!targetDirExists) {
5124
- throw new Error(`Target skills directory does not exist: ${targetDir}`);
5125
- }
5126
- const entries = await fs8.readdir(targetDir, { withFileTypes: true });
5127
- const dirs = entries.filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith("."));
5128
- for (const e of dirs) {
5129
- const skillDir = path9.join(targetDir, e.name);
5130
- const skillMdPath = path9.join(skillDir, "SKILL.md");
5348
+ const regularTargetDir = await this.resolveRegularSkillTarget(userId);
5349
+ const skillsByCode = /* @__PURE__ */ new Map();
5350
+ const sources = [
5351
+ { dir: regularTargetDir, gatewayBuiltIn: false },
5352
+ { dir: resolveGatewaySkillTarget(), gatewayBuiltIn: true }
5353
+ ];
5354
+ for (const source of sources) {
5355
+ let entries;
5131
5356
  try {
5132
- const stat = await fs8.stat(skillMdPath);
5133
- if (!stat.isFile()) continue;
5357
+ entries = await fs9.readdir(source.dir, { withFileTypes: true });
5134
5358
  } catch (err) {
5135
- continue;
5359
+ if (err?.code === "ENOENT") continue;
5360
+ throw err;
5136
5361
  }
5137
- const metaPath = path9.join(skillDir, ".meta.json");
5138
- let isPlatform = false;
5139
- let isBuiltIn2 = e.isSymbolicLink();
5140
- let metaData = null;
5141
- let name = e.name;
5142
- let description = "";
5143
- let skillVersion = "";
5144
- try {
5145
- const mdContent = await fs8.readFile(skillMdPath, "utf8");
5146
- const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(mdContent)?.[1] ?? "";
5147
- const parsedName = /(^|\n)name:\s*(.+)/.exec(fm)?.[2]?.trim();
5148
- if (parsedName) name = parsedName;
5149
- const descMatch = /(^|\n)description:\s*(?:>\s*\n\s*)?(.*?)(?=\n[a-z]+:|\n---|$)/is.exec(fm);
5150
- if (descMatch && descMatch[2]) {
5151
- description = descMatch[2].replace(/\n\s+/g, " ").trim();
5362
+ const dirs = entries.filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith("."));
5363
+ for (const e of dirs) {
5364
+ const skillDir = path10.join(source.dir, e.name);
5365
+ const skillMdPath = path10.join(skillDir, "SKILL.md");
5366
+ try {
5367
+ const stat = await fs9.stat(skillMdPath);
5368
+ if (!stat.isFile()) continue;
5369
+ } catch {
5370
+ continue;
5152
5371
  }
5153
- } catch (err) {
5154
- }
5155
- try {
5156
- const metaContent = await fs8.readFile(metaPath, "utf8");
5157
- const parsed = JSON.parse(metaContent);
5158
- if (parsed) {
5159
- if (parsed.ownerId === "CMS" || parsed.ownerId === "CMS_COMPAT") isPlatform = true;
5160
- if (parsed.isBuiltIn === true || parsed.ownerId === "built-in") isBuiltIn2 = true;
5161
- metaData = parsed;
5372
+ const metaPath = path10.join(skillDir, ".meta.json");
5373
+ let isPlatform = source.gatewayBuiltIn;
5374
+ let isBuiltIn2 = source.gatewayBuiltIn || e.isSymbolicLink();
5375
+ let metaData = null;
5376
+ let name = e.name;
5377
+ let description = "";
5378
+ let skillVersion = "";
5379
+ try {
5380
+ const mdContent = await fs9.readFile(skillMdPath, "utf8");
5381
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(mdContent)?.[1] ?? "";
5382
+ const parsedName = /(^|\n)name:\s*(.+)/.exec(fm)?.[2]?.trim();
5383
+ if (parsedName) name = parsedName;
5384
+ const descMatch = /(^|\n)description:\s*(?:>\s*\n\s*)?(.*?)(?=\n[a-z]+:|\n---|$)/is.exec(fm);
5385
+ if (descMatch?.[2]) description = descMatch[2].replace(/\n\s+/g, " ").trim();
5386
+ } catch {
5387
+ }
5388
+ try {
5389
+ const parsed = JSON.parse(await fs9.readFile(metaPath, "utf8"));
5390
+ if (parsed) {
5391
+ if (parsed.ownerId === "CMS" || parsed.ownerId === "CMS_COMPAT") isPlatform = true;
5392
+ if (parsed.isBuiltIn === true || parsed.ownerId === "built-in") isBuiltIn2 = true;
5393
+ metaData = parsed;
5394
+ }
5395
+ } catch {
5396
+ }
5397
+ const resolvedVersion = await readSkillVersion(skillDir);
5398
+ if (resolvedVersion) skillVersion = resolvedVersion;
5399
+ if (isPlatform) {
5400
+ skillsByCode.set(e.name, {
5401
+ code: e.name,
5402
+ isPlatform: true,
5403
+ isBuiltIn: isBuiltIn2,
5404
+ version: skillVersion,
5405
+ name,
5406
+ description,
5407
+ publishedAt: metaData?.publishedAt
5408
+ });
5409
+ } else {
5410
+ skillsByCode.set(e.name, {
5411
+ code: e.name,
5412
+ isPlatform: false,
5413
+ isBuiltIn: isBuiltIn2,
5414
+ version: skillVersion,
5415
+ name,
5416
+ description
5417
+ });
5162
5418
  }
5163
- } catch (err) {
5164
- }
5165
- const resolvedVersion = await readSkillVersion(skillDir);
5166
- if (resolvedVersion) skillVersion = resolvedVersion;
5167
- if (isPlatform) {
5168
- list.push({
5169
- code: e.name,
5170
- isPlatform: true,
5171
- isBuiltIn: isBuiltIn2,
5172
- version: skillVersion,
5173
- name,
5174
- description,
5175
- publishedAt: metaData?.publishedAt
5176
- });
5177
- } else {
5178
- list.push({
5179
- code: e.name,
5180
- isPlatform: false,
5181
- isBuiltIn: isBuiltIn2,
5182
- version: skillVersion,
5183
- name,
5184
- description
5185
- });
5186
5419
  }
5187
5420
  }
5188
- this.reply(replyId, { success: true, data: list, action });
5421
+ this.reply(replyId, { success: true, data: [...skillsByCode.values()], action });
5189
5422
  } else if (action === "UPDATE_SKILL") {
5190
5423
  if (!safeCode) throw new Error("Missing code parameter");
5191
5424
  const delayMs = Math.random() * 5e3;
5192
- const syncBuiltInTemplate = shouldSyncBuiltInTemplate(action, isBuiltIn);
5425
+ const syncBuiltInTemplate = !gatewaySkillCommand && shouldSyncBuiltInTemplate(action, isBuiltIn);
5193
5426
  console.log(`[skill-logger-plugin][WS] Scheduled UPDATE for user ${userId}, code: ${safeCode} in ${Math.round(delayMs)}ms`);
5194
5427
  this.appendLogToFile("INFO", "Command", `Scheduled UPDATE_SKILL`, {
5195
5428
  userId,
5196
5429
  code: safeCode,
5197
5430
  version,
5198
5431
  isBuiltIn,
5432
+ installScope,
5199
5433
  syncBuiltInTemplate,
5200
5434
  delayMs: Math.round(delayMs)
5201
5435
  });
5202
5436
  setTimeout(async () => {
5203
5437
  try {
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);
5438
+ const targetDir = gatewaySkillCommand ? resolveGatewaySkillTarget() : await this.resolveRegularSkillTarget(userId);
5439
+ const additionalTargetDirs = syncBuiltInTemplate ? [path10.join(openclawHome(), "workspace-xgjk-assistant-template", "skills")] : [];
5440
+ const userSkillRoot = pureId ? await findInstalledExpertSkillsRoot(openclawHome(), pureId, safeCode) : void 0;
5207
5441
  if (userSkillRoot) additionalTargetDirs.push(userSkillRoot);
5208
5442
  const result = await this.options.updater.manualInstall({
5209
5443
  code: safeCode,
@@ -5230,7 +5464,12 @@ var GatewayWsClient = class {
5230
5464
  syncBuiltInTemplate
5231
5465
  });
5232
5466
  if (replyId) {
5233
- this.reply(replyId, { success: result.success, message: result.message, action });
5467
+ this.reply(replyId, {
5468
+ success: result.success,
5469
+ message: result.message,
5470
+ action,
5471
+ data: { code: safeCode, version, installScope: gatewaySkillCommand ? "gateway" : "agent" }
5472
+ });
5234
5473
  }
5235
5474
  } catch (e) {
5236
5475
  this.appendLogToFile("ERROR", "Command", `UPDATE_SKILL threw`, {
@@ -5248,12 +5487,12 @@ var GatewayWsClient = class {
5248
5487
  const { name, version: version2, downloadUrl, skills } = msg;
5249
5488
  console.log(`[skill-logger-plugin][WS] Executing INSTALL_EXPERT for user ${userId}, code: ${safeCode}`);
5250
5489
  this.appendLogToFile("INFO", "Command", `INSTALL_EXPERT received`, { userId, code: safeCode, version: version2, downloadUrl });
5251
- const userSkillRoot = path9.join(openclawHome(), `workspace-assistant-${pureId}`, ".user");
5252
- const expertTarget = path9.join(userSkillRoot, "experts", safeCode);
5490
+ const userSkillRoot = path10.join(openclawHome(), `workspace-assistant-${pureId}`, ".user");
5491
+ const expertTarget = path10.join(userSkillRoot, "experts", safeCode);
5253
5492
  let skipInstall = false;
5254
- const metaPath = path9.join(expertTarget, ".meta.json");
5493
+ const metaPath = path10.join(expertTarget, ".meta.json");
5255
5494
  try {
5256
- const raw = await fs8.readFile(metaPath, "utf-8");
5495
+ const raw = await fs9.readFile(metaPath, "utf-8");
5257
5496
  const existing = JSON.parse(raw);
5258
5497
  if (existing.version && existing.version === (version2 || "1.0.0")) {
5259
5498
  skipInstall = true;
@@ -5261,16 +5500,16 @@ var GatewayWsClient = class {
5261
5500
  } catch {
5262
5501
  }
5263
5502
  if (!skipInstall) {
5264
- await fs8.mkdir(path9.dirname(expertTarget), { recursive: true });
5503
+ await fs9.mkdir(path10.dirname(expertTarget), { recursive: true });
5265
5504
  const expertResult = await this.options.updater.installExpertZipFromUrl(downloadUrl, expertTarget);
5266
5505
  if (!expertResult.success) {
5267
5506
  throw new Error(`\u4E13\u5BB6\u5B89\u88C5\u5931\u8D25: ${expertResult.message}`);
5268
5507
  }
5269
5508
  }
5270
5509
  const meta = { code: safeCode, name, version: version2 || "1.0.0", installedAt: Date.now() };
5271
- await fs8.writeFile(metaPath, JSON.stringify(meta, null, 2));
5272
- const skillTargetRoot = path9.join(userSkillRoot, "skills");
5273
- await fs8.mkdir(skillTargetRoot, { recursive: true });
5510
+ await fs9.writeFile(metaPath, JSON.stringify(meta, null, 2));
5511
+ const skillTargetRoot = path10.join(userSkillRoot, "skills");
5512
+ await fs9.mkdir(skillTargetRoot, { recursive: true });
5274
5513
  const skillResults = [];
5275
5514
  if (Array.isArray(skills)) {
5276
5515
  for (const sk of skills) {
@@ -5307,23 +5546,23 @@ var GatewayWsClient = class {
5307
5546
  if (!safeCode) throw new Error("Missing code parameter");
5308
5547
  console.log(`[skill-logger-plugin][WS] Executing UNINSTALL_EXPERT for user ${userId}, code: ${safeCode}`);
5309
5548
  this.appendLogToFile("INFO", "Command", `UNINSTALL_EXPERT received`, { userId, code: safeCode });
5310
- const expertPath = path9.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "experts", safeCode);
5311
- await fs8.rm(expertPath, { recursive: true, force: true });
5549
+ const expertPath = path10.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "experts", safeCode);
5550
+ await fs9.rm(expertPath, { recursive: true, force: true });
5312
5551
  this.reply(replyId, { success: true, message: `\u4E13\u5BB6 ${safeCode} \u5DF2\u5378\u8F7D`, action });
5313
5552
  } else if (action === "LIST_EXPERTS") {
5314
5553
  console.log(`[skill-logger-plugin][WS] Executing LIST_EXPERTS for user ${userId}`);
5315
5554
  this.appendLogToFile("INFO", "Command", `LIST_EXPERTS received`, { userId });
5316
- const expertsDir = path9.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "experts");
5555
+ const expertsDir = path10.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "experts");
5317
5556
  const list = [];
5318
5557
  try {
5319
- const stat = await fs8.stat(expertsDir);
5558
+ const stat = await fs9.stat(expertsDir);
5320
5559
  if (stat.isDirectory()) {
5321
- const entries = await fs8.readdir(expertsDir, { withFileTypes: true });
5560
+ const entries = await fs9.readdir(expertsDir, { withFileTypes: true });
5322
5561
  for (const e of entries) {
5323
5562
  if (!e.isDirectory()) continue;
5324
- const metaPath = path9.join(expertsDir, e.name, ".meta.json");
5563
+ const metaPath = path10.join(expertsDir, e.name, ".meta.json");
5325
5564
  try {
5326
- const raw = await fs8.readFile(metaPath, "utf-8");
5565
+ const raw = await fs9.readFile(metaPath, "utf-8");
5327
5566
  const meta = JSON.parse(raw);
5328
5567
  list.push({
5329
5568
  code: meta.code || e.name,
@@ -5342,7 +5581,17 @@ var GatewayWsClient = class {
5342
5581
  } else {
5343
5582
  console.warn(`[skill-logger-plugin][WS] Unknown action: ${action}`);
5344
5583
  this.appendLogToFile("WARN", "Command", `Unknown action: ${action}`);
5345
- this.reply(replyId, { success: false, message: `Unknown action: ${action}`, action });
5584
+ if (action === "LIST_FREE_SKILLS") {
5585
+ this.reply(replyId, {
5586
+ success: false,
5587
+ unsupported: true,
5588
+ error: "UNSUPPORTED_ACTION",
5589
+ message: "LIST_FREE_SKILLS unsupported by this plugin",
5590
+ action
5591
+ });
5592
+ } else {
5593
+ this.reply(replyId, { success: false, message: `Unknown action: ${action}`, action });
5594
+ }
5346
5595
  }
5347
5596
  } catch (err) {
5348
5597
  this.appendLogToFile("ERROR", "Command", `Error executing action ${action}`, err);
@@ -5377,7 +5626,7 @@ var GatewayWsClient = class {
5377
5626
  };
5378
5627
 
5379
5628
  // src/hooks.ts
5380
- import path10 from "node:path";
5629
+ import path11 from "node:path";
5381
5630
  import { randomUUID as randomUUID3 } from "node:crypto";
5382
5631
  var PENDING_TTL_MS = 30 * 60 * 1e3;
5383
5632
  var PENDING_MAX = 5e3;
@@ -5386,7 +5635,7 @@ function toMySQLDateTime(d) {
5386
5635
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
5387
5636
  }
5388
5637
  function isSkillMdReadPath(filePath) {
5389
- return path10.basename(filePath) === "SKILL.md";
5638
+ return path11.basename(filePath) === "SKILL.md";
5390
5639
  }
5391
5640
  function extractAppKey(event, ctx) {
5392
5641
  try {
@@ -5566,8 +5815,8 @@ var Hooks = class {
5566
5815
  if (toolName === "read") {
5567
5816
  const filePath = params.path ?? params.file_path ?? "";
5568
5817
  if (!isSkillMdReadPath(filePath)) return;
5569
- const rootDir = path10.dirname(filePath);
5570
- const skillName = this.configSync.resolveSkillName(rootDir) || path10.basename(rootDir);
5818
+ const rootDir = path11.dirname(filePath);
5819
+ const skillName = this.configSync.resolveSkillName(rootDir) || path11.basename(rootDir);
5571
5820
  if (!skillName) return;
5572
5821
  this.debug(`Intercepted 'read' for SKILL.md. Attributed to skill: ${skillName}`);
5573
5822
  this.recordTrigger(skillName, "inline", "read", ctx, appKey);
@@ -5749,6 +5998,637 @@ var Hooks = class {
5749
5998
  }
5750
5999
  };
5751
6000
 
6001
+ // src/free-skill-tool.ts
6002
+ import path14 from "node:path";
6003
+
6004
+ // src/free-skill-writer.ts
6005
+ import crypto from "node:crypto";
6006
+ import fs11 from "node:fs/promises";
6007
+ import path13 from "node:path";
6008
+
6009
+ // src/free-skill-workspace.ts
6010
+ import fs10 from "node:fs/promises";
6011
+ import path12 from "node:path";
6012
+ var FREE_SKILL_DIRECTORY_NAME2 = ".xg-platform";
6013
+ var FreeSkillWorkspaceError = class extends Error {
6014
+ code;
6015
+ constructor(code, message) {
6016
+ super(message);
6017
+ this.code = code;
6018
+ this.name = "FreeSkillWorkspaceError";
6019
+ }
6020
+ };
6021
+ function isRecord2(value) {
6022
+ return typeof value === "object" && value !== null;
6023
+ }
6024
+ function nonEmptyString(value) {
6025
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
6026
+ }
6027
+ function isWithin2(parent, candidate) {
6028
+ const relative = path12.relative(parent, candidate);
6029
+ return relative === "" || !relative.startsWith(`..${path12.sep}`) && relative !== ".." && !path12.isAbsolute(relative);
6030
+ }
6031
+ function nestedRecord(value, key) {
6032
+ if (!isRecord2(value)) return void 0;
6033
+ const nested = value[key];
6034
+ return isRecord2(nested) ? nested : void 0;
6035
+ }
6036
+ function firstWorkspaceValue(context) {
6037
+ if (!isRecord2(context)) return void 0;
6038
+ const wrapped = nestedRecord(context, "ctx");
6039
+ const nestedContext = nestedRecord(context, "context");
6040
+ const candidates = [
6041
+ context.workspaceDir,
6042
+ context.workspace,
6043
+ wrapped?.workspaceDir,
6044
+ wrapped?.workspace,
6045
+ nestedContext?.workspaceDir,
6046
+ nestedContext?.workspace
6047
+ ];
6048
+ return candidates.map(nonEmptyString).find((value) => Boolean(value));
6049
+ }
6050
+ function firstAgentIdValue(context) {
6051
+ if (!isRecord2(context)) return void 0;
6052
+ const wrapped = nestedRecord(context, "ctx");
6053
+ const nestedContext = nestedRecord(context, "context");
6054
+ const agent = isRecord2(context.agent) ? context.agent.id : void 0;
6055
+ const wrappedAgent = isRecord2(wrapped?.agent) ? wrapped?.agent.id : void 0;
6056
+ const nestedContextAgent = isRecord2(nestedContext?.agent) ? nestedContext?.agent.id : void 0;
6057
+ const candidates = [
6058
+ context.agentId,
6059
+ agent,
6060
+ wrapped?.agentId,
6061
+ wrappedAgent,
6062
+ nestedContext?.agentId,
6063
+ nestedContextAgent
6064
+ ];
6065
+ return candidates.map(nonEmptyString).find((value) => Boolean(value));
6066
+ }
6067
+ async function resolveWorkspacePath(workspaceInput) {
6068
+ let workspacePath;
6069
+ try {
6070
+ workspacePath = await fs10.realpath(path12.resolve(workspaceInput));
6071
+ } catch {
6072
+ throw new FreeSkillWorkspaceError(
6073
+ "WORKSPACE_MISSING",
6074
+ `Agent workspace \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u8BBF\u95EE: ${workspaceInput}`
6075
+ );
6076
+ }
6077
+ try {
6078
+ const stat = await fs10.stat(workspacePath);
6079
+ if (!stat.isDirectory()) {
6080
+ throw new FreeSkillWorkspaceError(
6081
+ "WORKSPACE_NOT_DIRECTORY",
6082
+ `Agent workspace \u4E0D\u662F\u76EE\u5F55: ${workspaceInput}`
6083
+ );
6084
+ }
6085
+ } catch (error) {
6086
+ if (error instanceof FreeSkillWorkspaceError) throw error;
6087
+ throw new FreeSkillWorkspaceError(
6088
+ "WORKSPACE_MISSING",
6089
+ `Agent workspace \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u8BBF\u95EE: ${workspaceInput}`
6090
+ );
6091
+ }
6092
+ return workspacePath;
6093
+ }
6094
+ async function resolvePlatformDirectory(workspacePath) {
6095
+ const candidate = path12.resolve(workspacePath, FREE_SKILL_DIRECTORY_NAME2);
6096
+ if (!isWithin2(workspacePath, candidate)) {
6097
+ throw new FreeSkillWorkspaceError(
6098
+ "PLATFORM_DIRECTORY_ESCAPE",
6099
+ "`.xg-platform` \u4E0D\u5728 Agent workspace \u5185"
6100
+ );
6101
+ }
6102
+ let entry;
6103
+ try {
6104
+ entry = await fs10.lstat(candidate);
6105
+ } catch (error) {
6106
+ if (error?.code === "ENOENT") return candidate;
6107
+ throw new FreeSkillWorkspaceError(
6108
+ "PLATFORM_DIRECTORY_BROKEN_LINK",
6109
+ "\u65E0\u6CD5\u68C0\u67E5 Agent workspace \u4E0B\u7684 `.xg-platform`"
6110
+ );
6111
+ }
6112
+ let resolved = candidate;
6113
+ if (entry.isSymbolicLink()) {
6114
+ try {
6115
+ resolved = await fs10.realpath(candidate);
6116
+ } catch {
6117
+ throw new FreeSkillWorkspaceError(
6118
+ "PLATFORM_DIRECTORY_BROKEN_LINK",
6119
+ "`.xg-platform` \u662F\u5931\u6548\u7B26\u53F7\u94FE\u63A5"
6120
+ );
6121
+ }
6122
+ if (!isWithin2(workspacePath, resolved)) {
6123
+ throw new FreeSkillWorkspaceError(
6124
+ "PLATFORM_DIRECTORY_ESCAPE",
6125
+ "`.xg-platform` \u7B26\u53F7\u94FE\u63A5\u8D8A\u51FA Agent workspace"
6126
+ );
6127
+ }
6128
+ }
6129
+ try {
6130
+ const stat = await fs10.stat(resolved);
6131
+ if (!stat.isDirectory()) {
6132
+ throw new FreeSkillWorkspaceError(
6133
+ "PLATFORM_DIRECTORY_NOT_DIRECTORY",
6134
+ "Agent workspace \u4E0B\u7684 `.xg-platform` \u4E0D\u662F\u76EE\u5F55"
6135
+ );
6136
+ }
6137
+ } catch (error) {
6138
+ if (error instanceof FreeSkillWorkspaceError) throw error;
6139
+ throw new FreeSkillWorkspaceError(
6140
+ "PLATFORM_DIRECTORY_BROKEN_LINK",
6141
+ "\u65E0\u6CD5\u8BBF\u95EE Agent workspace \u4E0B\u7684 `.xg-platform`"
6142
+ );
6143
+ }
6144
+ return resolved;
6145
+ }
6146
+ async function resolveFreeSkillWorkspace(context, fallbackWorkspace) {
6147
+ const workspaceInput = firstWorkspaceValue(context) ?? nonEmptyString(fallbackWorkspace);
6148
+ if (!workspaceInput) {
6149
+ throw new FreeSkillWorkspaceError(
6150
+ "WORKSPACE_MISSING",
6151
+ "\u672A\u63D0\u4F9B\u5F53\u524D Agent workspace"
6152
+ );
6153
+ }
6154
+ const workspacePath = await resolveWorkspacePath(workspaceInput);
6155
+ const directoryPath = await resolvePlatformDirectory(workspacePath);
6156
+ return {
6157
+ agentId: firstAgentIdValue(context),
6158
+ workspacePath,
6159
+ directoryPath,
6160
+ workspaceRelativePath: FREE_SKILL_DIRECTORY_NAME2
6161
+ };
6162
+ }
6163
+
6164
+ // src/free-skill-writer.ts
6165
+ var FREE_SKILL_DIRECTORY_NAME3 = ".xg-platform";
6166
+ var SKILL_FILE_NAME2 = "SKILL.md";
6167
+ var FreeSkillWriterError = class extends Error {
6168
+ code;
6169
+ constructor(code, message, options) {
6170
+ super(message, options);
6171
+ this.code = code;
6172
+ this.name = "FreeSkillWriterError";
6173
+ }
6174
+ };
6175
+ var defaultFileSystem = {
6176
+ lstat: fs11.lstat,
6177
+ realpath: fs11.realpath,
6178
+ stat: fs11.stat,
6179
+ mkdir: fs11.mkdir,
6180
+ writeFile: fs11.writeFile,
6181
+ rename: fs11.rename,
6182
+ rm: fs11.rm
6183
+ };
6184
+ function isRecord3(value) {
6185
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6186
+ }
6187
+ function isWithin3(parent, candidate) {
6188
+ const relative = path13.relative(parent, candidate);
6189
+ return relative === "" || !relative.startsWith(`..${path13.sep}`) && relative !== ".." && !path13.isAbsolute(relative);
6190
+ }
6191
+ function asErrnoCode(error) {
6192
+ return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
6193
+ }
6194
+ function asMessage(error) {
6195
+ return error instanceof Error ? error.message : String(error);
6196
+ }
6197
+ function fail(code, message) {
6198
+ throw new FreeSkillWriterError(code, message);
6199
+ }
6200
+ function stripYamlComment(value) {
6201
+ let quote;
6202
+ for (let index = 0; index < value.length; index += 1) {
6203
+ const character = value[index];
6204
+ if (quote === '"' && character === "\\") {
6205
+ index += 1;
6206
+ continue;
6207
+ }
6208
+ if ((character === "'" || character === '"') && (!quote || quote === character)) {
6209
+ if (quote === "'" && character === "'" && value[index + 1] === "'") {
6210
+ index += 1;
6211
+ continue;
6212
+ }
6213
+ quote = quote ? void 0 : character;
6214
+ continue;
6215
+ }
6216
+ if (!quote && character === "#" && (index === 0 || /\s/.test(value[index - 1]))) {
6217
+ return value.slice(0, index).trimEnd();
6218
+ }
6219
+ }
6220
+ return value.trim();
6221
+ }
6222
+ function parseQuotedScalar(rawValue) {
6223
+ if (rawValue.startsWith('"')) {
6224
+ if (!rawValue.endsWith('"')) return void 0;
6225
+ try {
6226
+ const parsed = JSON.parse(rawValue);
6227
+ return typeof parsed === "string" ? parsed : void 0;
6228
+ } catch {
6229
+ return void 0;
6230
+ }
6231
+ }
6232
+ if (rawValue.startsWith("'")) {
6233
+ if (!rawValue.endsWith("'")) return void 0;
6234
+ const inner = rawValue.slice(1, -1);
6235
+ let value = "";
6236
+ for (let index = 0; index < inner.length; index += 1) {
6237
+ if (inner[index] === "'" && inner[index + 1] === "'") {
6238
+ value += "'";
6239
+ index += 1;
6240
+ } else if (inner[index] === "'") {
6241
+ return void 0;
6242
+ } else {
6243
+ value += inner[index];
6244
+ }
6245
+ }
6246
+ return value;
6247
+ }
6248
+ return void 0;
6249
+ }
6250
+ function hasBalancedCollection(value) {
6251
+ const opening = value[0];
6252
+ const closing = opening === "[" ? "]" : "}";
6253
+ if (opening !== "[" && opening !== "{" || !value.endsWith(closing)) return false;
6254
+ let depth = 0;
6255
+ let quote;
6256
+ for (let index = 0; index < value.length; index += 1) {
6257
+ const character = value[index];
6258
+ if (quote === '"' && character === "\\") {
6259
+ index += 1;
6260
+ continue;
6261
+ }
6262
+ if ((character === "'" || character === '"') && (!quote || quote === character)) {
6263
+ if (quote === "'" && character === "'" && value[index + 1] === "'") {
6264
+ index += 1;
6265
+ continue;
6266
+ }
6267
+ quote = quote ? void 0 : character;
6268
+ continue;
6269
+ }
6270
+ if (quote) continue;
6271
+ if (character === opening) depth += 1;
6272
+ if (character === closing) depth -= 1;
6273
+ if (depth < 0) return false;
6274
+ }
6275
+ return depth === 0 && !quote;
6276
+ }
6277
+ function parseInlineValue(rawValue) {
6278
+ const value = stripYamlComment(rawValue);
6279
+ if (value.startsWith('"') || value.startsWith("'")) {
6280
+ const parsed = parseQuotedScalar(value);
6281
+ if (parsed === void 0) fail("INVALID_FRONTMATTER", "SKILL.md frontmatter \u4E2D\u5B58\u5728\u672A\u95ED\u5408\u6216\u65E0\u6548\u5B57\u7B26\u4E32");
6282
+ return { kind: "scalar", value: parsed };
6283
+ }
6284
+ if (value.startsWith("[") || value.startsWith("{")) {
6285
+ if (!hasBalancedCollection(value)) {
6286
+ fail("INVALID_FRONTMATTER", "SKILL.md frontmatter \u4E2D\u5B58\u5728\u672A\u95ED\u5408\u96C6\u5408\u503C");
6287
+ }
6288
+ return { kind: "other" };
6289
+ }
6290
+ return { kind: "scalar", value };
6291
+ }
6292
+ function parseBlockValue(lines, startIndex, header) {
6293
+ const headerMatch = /^([|>])(?:[+-]|[1-9])?$/.exec(header);
6294
+ if (!headerMatch) fail("INVALID_FRONTMATTER", "SKILL.md frontmatter \u4E2D\u5B58\u5728\u65E0\u6548\u5757\u6807\u91CF");
6295
+ const blockLines = [];
6296
+ let index = startIndex;
6297
+ let contentIndent;
6298
+ while (index < lines.length) {
6299
+ const line = lines[index];
6300
+ if (line.trim() !== "" && !/^\s+/.test(line)) break;
6301
+ if (line.trim() === "") {
6302
+ blockLines.push("");
6303
+ index += 1;
6304
+ continue;
6305
+ }
6306
+ const indent = line.match(/^\s*/)?.[0].length ?? 0;
6307
+ contentIndent ??= indent;
6308
+ if (indent < contentIndent) break;
6309
+ blockLines.push(line.slice(contentIndent));
6310
+ index += 1;
6311
+ }
6312
+ if (headerMatch[1] === ">") {
6313
+ const folded = [];
6314
+ for (const line of blockLines) {
6315
+ if (line === "") {
6316
+ folded.push("\n");
6317
+ } else if (folded.length > 0 && folded[folded.length - 1] !== "\n") {
6318
+ folded.push(" ", line);
6319
+ } else {
6320
+ folded.push(line);
6321
+ }
6322
+ }
6323
+ return { value: folded.join(""), nextIndex: index };
6324
+ }
6325
+ return { value: blockLines.join("\n"), nextIndex: index };
6326
+ }
6327
+ function parseFrontmatter2(content) {
6328
+ const normalized = content.replace(/^\uFEFF/, "");
6329
+ const lines = normalized.split(/\r?\n/);
6330
+ if (lines[0]?.trim() !== "---") {
6331
+ fail("INVALID_FRONTMATTER", "SKILL.md \u5FC5\u987B\u4EE5 YAML frontmatter \u5F00\u59CB");
6332
+ }
6333
+ const fields = /* @__PURE__ */ new Map();
6334
+ let closingIndex = -1;
6335
+ let index = 1;
6336
+ while (index < lines.length) {
6337
+ const line = lines[index];
6338
+ const trimmed = line.trim();
6339
+ if (trimmed === "---" || trimmed === "...") {
6340
+ closingIndex = index;
6341
+ break;
6342
+ }
6343
+ if (trimmed === "" || trimmed.startsWith("#")) {
6344
+ index += 1;
6345
+ continue;
6346
+ }
6347
+ if (/^\s/.test(line) || line.includes(" ")) {
6348
+ fail("INVALID_FRONTMATTER", "SKILL.md frontmatter \u4E2D\u5B58\u5728\u65E0\u6548\u7F29\u8FDB");
6349
+ }
6350
+ const match2 = /^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*?)\s*$/.exec(line);
6351
+ if (!match2) fail("INVALID_FRONTMATTER", "SKILL.md frontmatter \u4E2D\u5B58\u5728\u65E0\u6548\u5B57\u6BB5");
6352
+ const [, key, rawValue] = match2;
6353
+ if (fields.has(key)) fail("INVALID_FRONTMATTER", `SKILL.md frontmatter \u5B57\u6BB5\u91CD\u590D: ${key}`);
6354
+ const value = stripYamlComment(rawValue);
6355
+ if (value === "|" || value === ">" || /^[|>][+-]$/.test(value)) {
6356
+ const block = parseBlockValue(lines, index + 1, value);
6357
+ fields.set(key, { kind: "scalar", value: block.value });
6358
+ index = block.nextIndex;
6359
+ continue;
6360
+ }
6361
+ fields.set(key, parseInlineValue(rawValue));
6362
+ index += 1;
6363
+ }
6364
+ if (closingIndex < 0) fail("INVALID_FRONTMATTER", "SKILL.md frontmatter \u7F3A\u5C11\u7ED3\u675F\u6807\u8BB0");
6365
+ const name = fields.get("name");
6366
+ if (!name || name.kind !== "scalar" || !name.value.trim()) {
6367
+ fail("MISSING_NAME", "SKILL.md frontmatter \u5FC5\u987B\u5305\u542B\u975E\u7A7A name");
6368
+ }
6369
+ const description = fields.get("description");
6370
+ if (!description || description.kind !== "scalar" || !description.value.trim()) {
6371
+ fail("MISSING_DESCRIPTION", "SKILL.md frontmatter \u5FC5\u987B\u5305\u542B\u975E\u7A7A description");
6372
+ }
6373
+ const body = lines.slice(closingIndex + 1).join("\n");
6374
+ if (!body.trim()) fail("EMPTY_BODY", "SKILL.md \u6B63\u6587\u4E0D\u80FD\u4E3A\u7A7A");
6375
+ return { name: name.value.trim(), description: description.value.trim(), body };
6376
+ }
6377
+ function validateFreeSkillContent(content) {
6378
+ if (typeof content !== "string") fail("INVALID_CONTENT", "SKILL.md \u5185\u5BB9\u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
6379
+ return parseFrontmatter2(content);
6380
+ }
6381
+ function validateSkillName(value) {
6382
+ if (typeof value !== "string" || value.length === 0 || value !== value.trim()) {
6383
+ fail("INVALID_SKILL_NAME", "Skill \u540D\u79F0\u5FC5\u987B\u662F\u975E\u7A7A\u7684\u5355\u7EA7\u76EE\u5F55\u540D");
6384
+ }
6385
+ if (value === "." || value === ".." || value.startsWith(".") || value.endsWith(".") || value.endsWith(" ") || path13.basename(value) !== value || path13.win32.basename(value) !== value || path13.isAbsolute(value) || /[\\/\0<>:"|?*\u0000-\u001f\u007f]/u.test(value)) {
6386
+ fail("INVALID_SKILL_NAME", `\u975E\u6CD5\u7684 Skill \u540D\u79F0: ${value}`);
6387
+ }
6388
+ return value;
6389
+ }
6390
+ function serializeMetadata(value) {
6391
+ if (value === void 0) return void 0;
6392
+ if (!isRecord3(value)) fail("INVALID_METADATA", ".meta.json metadata \u5FC5\u987B\u662F JSON \u5BF9\u8C61");
6393
+ try {
6394
+ const serialized = JSON.stringify(value, null, 2);
6395
+ if (serialized === void 0) fail("INVALID_METADATA", ".meta.json metadata \u65E0\u6CD5\u5E8F\u5217\u5316");
6396
+ return `${serialized}
6397
+ `;
6398
+ } catch (error) {
6399
+ fail("INVALID_METADATA", `.meta.json metadata \u65E0\u6CD5\u5E8F\u5217\u5316: ${asMessage(error)}`);
6400
+ }
6401
+ }
6402
+ function isResolvedWorkspace(value) {
6403
+ if (!isRecord3(value)) return false;
6404
+ return typeof value.workspacePath === "string" && typeof value.directoryPath === "string" && value.workspaceRelativePath === FREE_SKILL_DIRECTORY_NAME3;
6405
+ }
6406
+ async function ensurePlatformDirectory(workspace, io) {
6407
+ if (!path13.isAbsolute(workspace.workspacePath) || !path13.isAbsolute(workspace.directoryPath)) {
6408
+ fail("TARGET_ESCAPE", "Skill workspace \u8DEF\u5F84\u5FC5\u987B\u662F\u89E3\u6790\u5668\u8FD4\u56DE\u7684\u7EDD\u5BF9\u8DEF\u5F84");
6409
+ }
6410
+ let workspacePath;
6411
+ try {
6412
+ workspacePath = await io.realpath(path13.resolve(workspace.workspacePath));
6413
+ const workspaceStat = await io.stat(workspacePath);
6414
+ if (!workspaceStat.isDirectory()) fail("WORKSPACE_NOT_DIRECTORY", "Agent workspace \u4E0D\u662F\u76EE\u5F55");
6415
+ } catch (error) {
6416
+ if (error instanceof FreeSkillWriterError) throw error;
6417
+ fail("WORKSPACE_MISSING", `Agent workspace \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u8BBF\u95EE: ${asMessage(error)}`);
6418
+ }
6419
+ const directoryPath = path13.resolve(workspacePath, FREE_SKILL_DIRECTORY_NAME3);
6420
+ if (!isWithin3(workspacePath, directoryPath) || path13.resolve(workspace.directoryPath) !== directoryPath) {
6421
+ fail("TARGET_ESCAPE", "\u81EA\u7531 Skill \u76EE\u6807\u76EE\u5F55\u5FC5\u987B\u662F workspace/.xg-platform");
6422
+ }
6423
+ let entry;
6424
+ try {
6425
+ entry = await io.lstat(directoryPath);
6426
+ } catch (error) {
6427
+ if (asErrnoCode(error) !== "ENOENT") {
6428
+ fail("TARGET_UNAVAILABLE", `\u65E0\u6CD5\u68C0\u67E5\u81EA\u7531 Skill \u76EE\u6807\u76EE\u5F55: ${asMessage(error)}`);
6429
+ }
6430
+ try {
6431
+ await io.mkdir(directoryPath, { recursive: false });
6432
+ entry = await io.lstat(directoryPath);
6433
+ } catch (mkdirError) {
6434
+ if (asErrnoCode(mkdirError) !== "EEXIST") {
6435
+ fail("ATOMIC_WRITE_FAILED", `\u521B\u5EFA\u81EA\u7531 Skill \u76EE\u6807\u76EE\u5F55\u5931\u8D25: ${asMessage(mkdirError)}`);
6436
+ }
6437
+ try {
6438
+ entry = await io.lstat(directoryPath);
6439
+ } catch (statError) {
6440
+ fail("TARGET_UNAVAILABLE", `\u65E0\u6CD5\u68C0\u67E5\u81EA\u7531 Skill \u76EE\u6807\u76EE\u5F55: ${asMessage(statError)}`);
6441
+ }
6442
+ }
6443
+ }
6444
+ if (entry.isSymbolicLink()) fail("TARGET_SYMLINK", "\u62D2\u7EDD\u5199\u5165\u7B26\u53F7\u94FE\u63A5\u76EE\u6807\u76EE\u5F55");
6445
+ if (!entry.isDirectory()) fail("TARGET_NOT_DIRECTORY", "\u81EA\u7531 Skill \u76EE\u6807\u4E0D\u662F\u76EE\u5F55");
6446
+ let realDirectory;
6447
+ try {
6448
+ realDirectory = await io.realpath(directoryPath);
6449
+ } catch (error) {
6450
+ fail("TARGET_UNAVAILABLE", `\u65E0\u6CD5\u89E3\u6790\u81EA\u7531 Skill \u76EE\u6807\u76EE\u5F55: ${asMessage(error)}`);
6451
+ }
6452
+ if (realDirectory !== directoryPath || !isWithin3(workspacePath, realDirectory)) {
6453
+ fail("TARGET_ESCAPE", "\u81EA\u7531 Skill \u76EE\u6807\u76EE\u5F55\u8D8A\u51FA Agent workspace");
6454
+ }
6455
+ return { workspacePath, directoryPath };
6456
+ }
6457
+ async function resolveWriterWorkspace(input, suppliedWorkspace, io) {
6458
+ let workspace;
6459
+ const candidate = suppliedWorkspace ?? input.workspace;
6460
+ if (isResolvedWorkspace(candidate)) {
6461
+ workspace = candidate;
6462
+ } else {
6463
+ const context = input.context ?? candidate ?? input;
6464
+ try {
6465
+ workspace = await resolveFreeSkillWorkspace(context);
6466
+ } catch (error) {
6467
+ if (error instanceof FreeSkillWorkspaceError) throw error;
6468
+ fail("WORKSPACE_MISSING", `\u65E0\u6CD5\u89E3\u6790 Agent workspace: ${asMessage(error)}`);
6469
+ }
6470
+ }
6471
+ return ensurePlatformDirectory(workspace, io);
6472
+ }
6473
+ function makeTemporaryPath(directoryPath, skillName, suffix) {
6474
+ return path13.join(
6475
+ directoryPath,
6476
+ `.${skillName}.${suffix}-${process.pid}-${Date.now()}-${crypto.randomUUID()}`
6477
+ );
6478
+ }
6479
+ async function removePath(io, target) {
6480
+ await io.rm(target, { recursive: true, force: true }).catch(() => void 0);
6481
+ }
6482
+ async function targetAlreadyExists(targetPath, io) {
6483
+ try {
6484
+ const entry = await io.lstat(targetPath);
6485
+ return entry.isSymbolicLink() ? "symlink" : "exists";
6486
+ } catch (error) {
6487
+ if (asErrnoCode(error) === "ENOENT") return "missing";
6488
+ fail("TARGET_UNAVAILABLE", `\u65E0\u6CD5\u68C0\u67E5 Skill \u76EE\u6807\u76EE\u5F55: ${asMessage(error)}`);
6489
+ }
6490
+ }
6491
+ async function writeFreeSkill(input, workspace, dependencies) {
6492
+ const io = {
6493
+ ...defaultFileSystem,
6494
+ ...dependencies?.fileSystem
6495
+ };
6496
+ try {
6497
+ if (!isRecord3(input)) fail("INVALID_CONTENT", "Skill \u5199\u5165\u8BF7\u6C42\u5FC5\u987B\u662F\u5BF9\u8C61");
6498
+ const skillName = validateSkillName(input.skillName ?? input.name);
6499
+ const content = input.content ?? input.skillContent;
6500
+ validateFreeSkillContent(content);
6501
+ const metadata = serializeMetadata(input.metadata);
6502
+ const { directoryPath: platformDirectoryPath } = await resolveWriterWorkspace(input, workspace, io);
6503
+ const targetDirectoryPath = path13.resolve(platformDirectoryPath, skillName);
6504
+ if (!isWithin3(platformDirectoryPath, targetDirectoryPath)) {
6505
+ fail("TARGET_ESCAPE", "Skill \u76EE\u6807\u76EE\u5F55\u8D8A\u51FA workspace/.xg-platform");
6506
+ }
6507
+ const existing = await targetAlreadyExists(targetDirectoryPath, io);
6508
+ if (existing === "symlink") fail("TARGET_SYMLINK", "\u62D2\u7EDD\u5199\u5165\u7B26\u53F7\u94FE\u63A5 Skill \u76EE\u5F55");
6509
+ if (existing === "exists") fail("SKILL_EXISTS", `Skill \u5DF2\u5B58\u5728: ${skillName}`);
6510
+ let createdTargetDirectory = false;
6511
+ let skillTempPath;
6512
+ let metadataTempPath;
6513
+ const skillFilePath = path13.join(targetDirectoryPath, SKILL_FILE_NAME2);
6514
+ const metadataFilePath = metadata ? path13.join(targetDirectoryPath, ".meta.json") : void 0;
6515
+ try {
6516
+ try {
6517
+ await io.mkdir(targetDirectoryPath, { recursive: false });
6518
+ createdTargetDirectory = true;
6519
+ } catch (error) {
6520
+ if (asErrnoCode(error) === "EEXIST") {
6521
+ const raced = await targetAlreadyExists(targetDirectoryPath, io);
6522
+ if (raced === "symlink") fail("TARGET_SYMLINK", "\u62D2\u7EDD\u5199\u5165\u7B26\u53F7\u94FE\u63A5 Skill \u76EE\u5F55");
6523
+ fail("SKILL_EXISTS", `Skill \u5DF2\u5B58\u5728: ${skillName}`);
6524
+ }
6525
+ fail("ATOMIC_WRITE_FAILED", `\u521B\u5EFA Skill \u76EE\u5F55\u5931\u8D25: ${asMessage(error)}`);
6526
+ }
6527
+ skillTempPath = makeTemporaryPath(targetDirectoryPath, skillName, "skill-md-tmp");
6528
+ await io.writeFile(skillTempPath, content, {
6529
+ encoding: "utf8",
6530
+ flag: "wx",
6531
+ mode: 384
6532
+ });
6533
+ await io.rename(skillTempPath, skillFilePath);
6534
+ skillTempPath = void 0;
6535
+ if (metadata && metadataFilePath) {
6536
+ metadataTempPath = makeTemporaryPath(targetDirectoryPath, skillName, "metadata-tmp");
6537
+ await io.writeFile(metadataTempPath, metadata, {
6538
+ encoding: "utf8",
6539
+ flag: "wx",
6540
+ mode: 384
6541
+ });
6542
+ await io.rename(metadataTempPath, metadataFilePath);
6543
+ metadataTempPath = void 0;
6544
+ }
6545
+ } catch (error) {
6546
+ if (error instanceof FreeSkillWriterError && error.code === "SKILL_EXISTS") throw error;
6547
+ await Promise.all([
6548
+ skillTempPath ? removePath(io, skillTempPath) : Promise.resolve(),
6549
+ metadataTempPath ? removePath(io, metadataTempPath) : Promise.resolve()
6550
+ ]);
6551
+ if (createdTargetDirectory) await removePath(io, targetDirectoryPath);
6552
+ if (error instanceof FreeSkillWriterError) throw error;
6553
+ fail("ATOMIC_WRITE_FAILED", `\u81EA\u7531 Skill \u539F\u5B50\u5199\u5165\u5931\u8D25: ${asMessage(error)}`);
6554
+ }
6555
+ return {
6556
+ success: true,
6557
+ skillName,
6558
+ directoryPath: targetDirectoryPath,
6559
+ skillFilePath,
6560
+ ...metadataFilePath ? { metadataFilePath } : {}
6561
+ };
6562
+ } catch (error) {
6563
+ if (error instanceof FreeSkillWriterError) {
6564
+ return { success: false, code: error.code, message: error.message };
6565
+ }
6566
+ if (error instanceof FreeSkillWorkspaceError) {
6567
+ return { success: false, code: error.code, message: error.message };
6568
+ }
6569
+ return { success: false, code: "ATOMIC_WRITE_FAILED", message: `\u81EA\u7531 Skill \u5199\u5165\u5931\u8D25: ${asMessage(error)}` };
6570
+ }
6571
+ }
6572
+ async function writeFreeSkillOrThrow(input, workspace, dependencies) {
6573
+ const result = await writeFreeSkill(input, workspace, dependencies);
6574
+ if (!result.success) throw new FreeSkillWriterError(result.code, result.message);
6575
+ return result;
6576
+ }
6577
+
6578
+ // src/free-skill-tool.ts
6579
+ var FREE_SKILL_TOOL_NAME = "skill_logger_create_free_skill";
6580
+ var parameters = {
6581
+ type: "object",
6582
+ additionalProperties: false,
6583
+ properties: {
6584
+ skillName: { type: "string", description: "\u81EA\u7531 Skill \u7684\u4E00\u7EA7\u76EE\u5F55\u540D" },
6585
+ content: { type: "string", description: "\u5B8C\u6574\u7684 SKILL.md \u5185\u5BB9" },
6586
+ metadata: { type: "object", description: "\u53EF\u9009\u7684 JSON \u5143\u6570\u636E" }
6587
+ },
6588
+ required: ["skillName", "content"]
6589
+ };
6590
+ function hasOwn(value, key) {
6591
+ return Object.prototype.hasOwnProperty.call(value, key);
6592
+ }
6593
+ function asInput(params) {
6594
+ if (hasOwn(params, "target") || hasOwn(params, "path")) {
6595
+ throw new FreeSkillWriterError(
6596
+ "INVALID_CONTENT",
6597
+ "\u81EA\u7531 Skill \u5DE5\u5177\u4E0D\u63A5\u53D7 target/path\uFF0C\u76EE\u6807\u8DEF\u5F84\u7531\u5F53\u524D Agent workspace \u51B3\u5B9A"
6598
+ );
6599
+ }
6600
+ return {
6601
+ skillName: params.skillName ?? params.name,
6602
+ content: params.content ?? params.skillContent,
6603
+ metadata: params.metadata
6604
+ };
6605
+ }
6606
+ function createFreeSkillToolFactory(context) {
6607
+ return {
6608
+ name: FREE_SKILL_TOOL_NAME,
6609
+ label: "Create free Skill",
6610
+ description: "Create a validated Skill in the current Agent workspace under .xg-platform. The target path is runtime controlled.",
6611
+ parameters,
6612
+ async execute(_toolCallId, params) {
6613
+ const workspace = await resolveFreeSkillWorkspace(context);
6614
+ const result = await writeFreeSkillOrThrow(asInput(params), workspace);
6615
+ const workspaceRelativePath = path14.posix.join(".xg-platform", result.skillName);
6616
+ const details = {
6617
+ success: true,
6618
+ skillName: result.skillName,
6619
+ workspaceRelativePath,
6620
+ directoryPath: result.directoryPath,
6621
+ skillFilePath: result.skillFilePath,
6622
+ ...workspace.agentId ? { agentId: workspace.agentId } : {}
6623
+ };
6624
+ return {
6625
+ content: [{ type: "text", text: `\u81EA\u7531 Skill \u5DF2\u5199\u5165 ${workspaceRelativePath}` }],
6626
+ details
6627
+ };
6628
+ }
6629
+ };
6630
+ }
6631
+
5752
6632
  // src/index.ts
5753
6633
  var wsClient;
5754
6634
  var RECONCILE_INTERVAL_MS = 3 * 60 * 1e3;
@@ -5765,11 +6645,14 @@ var definition = {
5765
6645
  name: "Skill Logger",
5766
6646
  description: "\u8FFD\u8E2A openclaw skill \u5185\u529F\u80FD\u70B9\uFF08\u811A\u672C/\u547D\u4EE4/\u5DE5\u5177/HTTP\uFF09\u4F7F\u7528\u4E0E\u62A5\u9519\uFF0C\u843D\u672C\u5730\u5E76\u6279\u91CF\u4E0A\u62A5",
5767
6647
  register(api) {
6648
+ if (typeof api.registerTool === "function") {
6649
+ api.registerTool(createFreeSkillToolFactory, { name: FREE_SKILL_TOOL_NAME });
6650
+ }
5768
6651
  let pkgVersion = "unknown";
5769
6652
  try {
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"));
6653
+ const dir = path15.dirname(fileURLToPath2(import.meta.url));
6654
+ const pkgPath = path15.join(dir, "..", "package.json");
6655
+ const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
5773
6656
  if (pkg.version) pkgVersion = pkg.version;
5774
6657
  } catch {
5775
6658
  }