@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 +1048 -165
- package/openclaw.plugin.json +5 -0
- package/package.json +1 -1
- package/src/free-skill-directory.test.ts +109 -0
- package/src/free-skill-directory.ts +188 -0
- package/src/free-skill-tool.test.ts +68 -0
- package/src/free-skill-tool.ts +99 -0
- package/src/free-skill-workspace.test.ts +109 -0
- package/src/free-skill-workspace.ts +223 -0
- package/src/free-skill-writer.test.ts +105 -0
- package/src/free-skill-writer.ts +591 -0
- package/src/index.ts +5 -0
- package/src/ws-client.test.ts +162 -0
- package/src/ws-client.ts +227 -95
package/dist/index.js
CHANGED
|
@@ -360,25 +360,25 @@ var require_utils = __commonJS({
|
|
|
360
360
|
}
|
|
361
361
|
mkdirSync(folder);
|
|
362
362
|
};
|
|
363
|
-
Utils.prototype.writeFileTo = function(
|
|
363
|
+
Utils.prototype.writeFileTo = function(path16, content, overwrite, attr) {
|
|
364
364
|
const self = this;
|
|
365
|
-
if (self.fs.existsSync(
|
|
365
|
+
if (self.fs.existsSync(path16)) {
|
|
366
366
|
if (!overwrite) return false;
|
|
367
|
-
var stat = self.fs.statSync(
|
|
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(
|
|
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(
|
|
378
|
+
fd = self.fs.openSync(path16, "w", 438);
|
|
379
379
|
} catch (e) {
|
|
380
|
-
self.fs.chmodSync(
|
|
381
|
-
fd = self.fs.openSync(
|
|
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(
|
|
390
|
+
self.fs.chmodSync(path16, attr || 438);
|
|
391
391
|
return true;
|
|
392
392
|
};
|
|
393
|
-
Utils.prototype.writeFileToAsync = function(
|
|
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(
|
|
399
|
+
self.fs.exists(path16, function(exist) {
|
|
400
400
|
if (exist && !overwrite) return callback(false);
|
|
401
|
-
self.fs.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(
|
|
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(
|
|
418
|
+
self.fs.chmod(path16, attr || 438, function() {
|
|
419
419
|
callback(true);
|
|
420
420
|
});
|
|
421
421
|
});
|
|
422
422
|
});
|
|
423
423
|
};
|
|
424
|
-
self.fs.open(
|
|
424
|
+
self.fs.open(path16, "w", 438, function(err2, fd) {
|
|
425
425
|
if (err2) {
|
|
426
|
-
self.fs.chmod(
|
|
427
|
-
self.fs.open(
|
|
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(
|
|
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
|
|
452
|
-
const stat = self.fs.statSync(
|
|
453
|
-
if (!pattern || pattern.test(
|
|
454
|
-
files.push(pth.normalize(
|
|
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(
|
|
457
|
+
const realDir = self.fs.realpathSync(path17);
|
|
458
458
|
if (!visited.has(realDir)) {
|
|
459
459
|
visited.add(realDir);
|
|
460
|
-
files = files.concat(findSync(
|
|
460
|
+
files = files.concat(findSync(path17, pattern, recursive, visited));
|
|
461
461
|
}
|
|
462
462
|
}
|
|
463
463
|
});
|
|
464
464
|
return files;
|
|
465
465
|
}
|
|
466
|
-
return findSync(
|
|
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(
|
|
543
|
-
if (!
|
|
544
|
-
const safeSuffix = pth.posix.normalize("/" +
|
|
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(
|
|
548
|
-
if (!
|
|
549
|
-
const safeSuffix = pth.posix.normalize("/" +
|
|
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
|
|
567
|
-
if (
|
|
568
|
-
return
|
|
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(
|
|
615
|
-
var _path =
|
|
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 &&
|
|
627
|
-
_stat =
|
|
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
|
|
2754
|
-
import
|
|
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
|
|
4643
|
-
import
|
|
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:
|
|
4839
|
+
return { skillsDir: path10.join(workspace, "skills"), localAgentId };
|
|
4698
4840
|
}
|
|
4699
4841
|
function normalizeAssistantUserId(userId) {
|
|
4700
|
-
const safeUserId =
|
|
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 (
|
|
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 =
|
|
4864
|
+
const skillsRoot = path10.join(rootPath, `workspace-assistant-${pureId}`, ".user", "skills");
|
|
4717
4865
|
try {
|
|
4718
|
-
const stat = await
|
|
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
|
|
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 =
|
|
4766
|
-
|
|
4767
|
-
const logPath =
|
|
4768
|
-
|
|
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
|
-
|
|
4784
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
5093
|
+
const configPath = path10.join(openclawHome(), "openclaw.json");
|
|
4894
5094
|
let rawConfig;
|
|
4895
5095
|
try {
|
|
4896
|
-
rawConfig = await
|
|
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
|
|
5086
|
-
|
|
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, {
|
|
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 =
|
|
5111
|
-
await
|
|
5112
|
-
this.reply(replyId, {
|
|
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
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
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
|
-
|
|
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
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
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
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
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:
|
|
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 ? [
|
|
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, {
|
|
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 =
|
|
5252
|
-
const expertTarget =
|
|
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 =
|
|
5493
|
+
const metaPath = path10.join(expertTarget, ".meta.json");
|
|
5255
5494
|
try {
|
|
5256
|
-
const raw = await
|
|
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
|
|
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
|
|
5272
|
-
const skillTargetRoot =
|
|
5273
|
-
await
|
|
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 =
|
|
5311
|
-
await
|
|
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 =
|
|
5555
|
+
const expertsDir = path10.join(openclawHome(), `workspace-assistant-${pureId}`, ".user", "experts");
|
|
5317
5556
|
const list = [];
|
|
5318
5557
|
try {
|
|
5319
|
-
const stat = await
|
|
5558
|
+
const stat = await fs9.stat(expertsDir);
|
|
5320
5559
|
if (stat.isDirectory()) {
|
|
5321
|
-
const entries = await
|
|
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 =
|
|
5563
|
+
const metaPath = path10.join(expertsDir, e.name, ".meta.json");
|
|
5325
5564
|
try {
|
|
5326
|
-
const raw = await
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
5570
|
-
const skillName = this.configSync.resolveSkillName(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 =
|
|
5771
|
-
const pkgPath =
|
|
5772
|
-
const pkg = JSON.parse(
|
|
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
|
}
|