@agentforge/core 0.12.6 → 0.14.0
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.cjs +898 -28
- package/dist/index.d.cts +685 -2
- package/dist/index.d.ts +685 -2
- package/dist/index.js +881 -28
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ var ToolCategory = /* @__PURE__ */ ((ToolCategory2) => {
|
|
|
7
7
|
ToolCategory2["API"] = "api";
|
|
8
8
|
ToolCategory2["UTILITY"] = "utility";
|
|
9
9
|
ToolCategory2["CUSTOM"] = "custom";
|
|
10
|
+
ToolCategory2["SKILLS"] = "skills";
|
|
10
11
|
return ToolCategory2;
|
|
11
12
|
})(ToolCategory || {});
|
|
12
13
|
|
|
@@ -1405,7 +1406,7 @@ function createToolExecutor(config = {}) {
|
|
|
1405
1406
|
}
|
|
1406
1407
|
if (attempt < policy.maxAttempts) {
|
|
1407
1408
|
const delay = calculateBackoff(attempt, policy);
|
|
1408
|
-
await new Promise((
|
|
1409
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
1409
1410
|
}
|
|
1410
1411
|
}
|
|
1411
1412
|
}
|
|
@@ -1462,12 +1463,12 @@ function createToolExecutor(config = {}) {
|
|
|
1462
1463
|
}
|
|
1463
1464
|
async function execute(tool, input, options = {}) {
|
|
1464
1465
|
const priority = options.priority || priorityFn(tool);
|
|
1465
|
-
return new Promise((
|
|
1466
|
+
return new Promise((resolve3, reject) => {
|
|
1466
1467
|
queue.push({
|
|
1467
1468
|
tool,
|
|
1468
1469
|
input,
|
|
1469
1470
|
priority,
|
|
1470
|
-
resolve,
|
|
1471
|
+
resolve: resolve3,
|
|
1471
1472
|
reject,
|
|
1472
1473
|
timestamp: Date.now()
|
|
1473
1474
|
});
|
|
@@ -1767,7 +1768,7 @@ function retry(tool, options = {}) {
|
|
|
1767
1768
|
lastError = error;
|
|
1768
1769
|
if (attempt < maxAttempts) {
|
|
1769
1770
|
const waitTime = backoff === "exponential" ? delay * Math.pow(2, attempt - 1) : delay * attempt;
|
|
1770
|
-
await new Promise((
|
|
1771
|
+
await new Promise((resolve3) => setTimeout(resolve3, waitTime));
|
|
1771
1772
|
}
|
|
1772
1773
|
}
|
|
1773
1774
|
}
|
|
@@ -1828,7 +1829,7 @@ function createMockTool(config) {
|
|
|
1828
1829
|
const startTime = Date.now();
|
|
1829
1830
|
if (latency) {
|
|
1830
1831
|
const delay = typeof latency === "number" ? latency : Math.random() * (latency.max - latency.min) + latency.min;
|
|
1831
|
-
await new Promise((
|
|
1832
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
1832
1833
|
}
|
|
1833
1834
|
if (errorRate > 0 && Math.random() < errorRate) {
|
|
1834
1835
|
const error2 = new Error(`Random error from ${name}`);
|
|
@@ -1899,8 +1900,8 @@ function createToolSimulator(config) {
|
|
|
1899
1900
|
if (!latency) return 0;
|
|
1900
1901
|
const u1 = Math.random();
|
|
1901
1902
|
const u2 = Math.random();
|
|
1902
|
-
const
|
|
1903
|
-
return Math.max(0, latency.mean +
|
|
1903
|
+
const z4 = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
1904
|
+
return Math.max(0, latency.mean + z4 * latency.stddev);
|
|
1904
1905
|
}
|
|
1905
1906
|
return {
|
|
1906
1907
|
execute: async (toolName, input) => {
|
|
@@ -1911,7 +1912,7 @@ function createToolSimulator(config) {
|
|
|
1911
1912
|
const startTime = Date.now();
|
|
1912
1913
|
if (latency) {
|
|
1913
1914
|
const delay = generateLatency();
|
|
1914
|
-
await new Promise((
|
|
1915
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
1915
1916
|
}
|
|
1916
1917
|
if (errorRate > 0 && Math.random() < errorRate) {
|
|
1917
1918
|
const error = new Error(`Simulated error from ${toolName}`);
|
|
@@ -2269,7 +2270,7 @@ function calculateDelay(attempt, strategy, initialDelay, maxDelay) {
|
|
|
2269
2270
|
return Math.min(delay, maxDelay);
|
|
2270
2271
|
}
|
|
2271
2272
|
function sleep(ms) {
|
|
2272
|
-
return new Promise((
|
|
2273
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
2273
2274
|
}
|
|
2274
2275
|
function withRetry(node, options = {}) {
|
|
2275
2276
|
const {
|
|
@@ -2515,14 +2516,14 @@ var withLogging = (options) => {
|
|
|
2515
2516
|
onComplete,
|
|
2516
2517
|
onError
|
|
2517
2518
|
} = options;
|
|
2518
|
-
const
|
|
2519
|
+
const logger8 = providedLogger || createLogger(name, { level });
|
|
2519
2520
|
return (node) => {
|
|
2520
2521
|
return async (state) => {
|
|
2521
2522
|
const startTime = Date.now();
|
|
2522
2523
|
try {
|
|
2523
2524
|
if (logInput) {
|
|
2524
2525
|
const data = extractData ? extractData(state) : { state };
|
|
2525
|
-
|
|
2526
|
+
logger8.info("Node execution started", data);
|
|
2526
2527
|
}
|
|
2527
2528
|
if (onStart) {
|
|
2528
2529
|
onStart(state);
|
|
@@ -2532,9 +2533,9 @@ var withLogging = (options) => {
|
|
|
2532
2533
|
if (logOutput) {
|
|
2533
2534
|
const data = extractData ? extractData(result) : { result };
|
|
2534
2535
|
if (logDuration) {
|
|
2535
|
-
|
|
2536
|
+
logger8.info(`Node execution completed (${duration}ms)`, data);
|
|
2536
2537
|
} else {
|
|
2537
|
-
|
|
2538
|
+
logger8.info("Node execution completed", data);
|
|
2538
2539
|
}
|
|
2539
2540
|
}
|
|
2540
2541
|
if (onComplete) {
|
|
@@ -2545,7 +2546,7 @@ var withLogging = (options) => {
|
|
|
2545
2546
|
const duration = Date.now() - startTime;
|
|
2546
2547
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
2547
2548
|
if (logErrors) {
|
|
2548
|
-
|
|
2549
|
+
logger8.error(`Node execution failed (${duration}ms)`, {
|
|
2549
2550
|
error: err.message,
|
|
2550
2551
|
stack: err.stack
|
|
2551
2552
|
});
|
|
@@ -2581,7 +2582,7 @@ function withLogging2(options) {
|
|
|
2581
2582
|
function production(node, options) {
|
|
2582
2583
|
const {
|
|
2583
2584
|
nodeName,
|
|
2584
|
-
logger:
|
|
2585
|
+
logger: logger8,
|
|
2585
2586
|
enableMetrics = true,
|
|
2586
2587
|
enableTracing = true,
|
|
2587
2588
|
enableRetry = true,
|
|
@@ -2589,7 +2590,7 @@ function production(node, options) {
|
|
|
2589
2590
|
retryOptions = {},
|
|
2590
2591
|
errorOptions = {}
|
|
2591
2592
|
} = options;
|
|
2592
|
-
const actualLogger =
|
|
2593
|
+
const actualLogger = logger8 || createLogger(nodeName, { level: "info" /* INFO */ });
|
|
2593
2594
|
const middleware = [];
|
|
2594
2595
|
middleware.push(
|
|
2595
2596
|
withLogging2({
|
|
@@ -2653,9 +2654,9 @@ function development(node, options) {
|
|
|
2653
2654
|
const {
|
|
2654
2655
|
nodeName,
|
|
2655
2656
|
verbose = true,
|
|
2656
|
-
logger:
|
|
2657
|
+
logger: logger8
|
|
2657
2658
|
} = options;
|
|
2658
|
-
const actualLogger =
|
|
2659
|
+
const actualLogger = logger8 || createLogger(nodeName, { level: "debug" /* DEBUG */ });
|
|
2659
2660
|
return withLogging2({
|
|
2660
2661
|
logger: actualLogger,
|
|
2661
2662
|
name: nodeName,
|
|
@@ -2679,7 +2680,7 @@ function testing(node, options) {
|
|
|
2679
2680
|
invocations.push(state);
|
|
2680
2681
|
}
|
|
2681
2682
|
if (delay > 0) {
|
|
2682
|
-
await new Promise((
|
|
2683
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
2683
2684
|
}
|
|
2684
2685
|
if (simulateError) {
|
|
2685
2686
|
throw simulateError;
|
|
@@ -3138,12 +3139,12 @@ var ConcurrencyController = class {
|
|
|
3138
3139
|
}
|
|
3139
3140
|
throw new Error(`Queue is full (max size: ${this.maxQueueSize})`);
|
|
3140
3141
|
}
|
|
3141
|
-
return new Promise((
|
|
3142
|
+
return new Promise((resolve3, reject) => {
|
|
3142
3143
|
const task = {
|
|
3143
3144
|
state,
|
|
3144
3145
|
priority,
|
|
3145
3146
|
executor,
|
|
3146
|
-
resolve,
|
|
3147
|
+
resolve: resolve3,
|
|
3147
3148
|
reject,
|
|
3148
3149
|
timestamp: Date.now()
|
|
3149
3150
|
};
|
|
@@ -3608,7 +3609,7 @@ async function* throttle(stream, options) {
|
|
|
3608
3609
|
const now = Date.now();
|
|
3609
3610
|
const timeSinceLastEmit = now - lastEmit;
|
|
3610
3611
|
if (timeSinceLastEmit < interval) {
|
|
3611
|
-
await new Promise((
|
|
3612
|
+
await new Promise((resolve3) => setTimeout(resolve3, interval - timeSinceLastEmit));
|
|
3612
3613
|
}
|
|
3613
3614
|
lastEmit = Date.now();
|
|
3614
3615
|
yield item;
|
|
@@ -4123,17 +4124,17 @@ var ConnectionPool = class {
|
|
|
4123
4124
|
this.options.onAcquire?.(connection);
|
|
4124
4125
|
return connection;
|
|
4125
4126
|
}
|
|
4126
|
-
return new Promise((
|
|
4127
|
+
return new Promise((resolve3, reject) => {
|
|
4127
4128
|
const timeout2 = poolConfig.acquireTimeout || 3e4;
|
|
4128
4129
|
const timer = setTimeout(() => {
|
|
4129
|
-
const index = this.pending.findIndex((p) => p.resolve ===
|
|
4130
|
+
const index = this.pending.findIndex((p) => p.resolve === resolve3);
|
|
4130
4131
|
if (index !== -1) {
|
|
4131
4132
|
this.pending.splice(index, 1);
|
|
4132
4133
|
this.stats.pending--;
|
|
4133
4134
|
}
|
|
4134
4135
|
reject(new Error("Acquire timeout"));
|
|
4135
4136
|
}, timeout2);
|
|
4136
|
-
this.pending.push({ resolve, reject, timeout: timer });
|
|
4137
|
+
this.pending.push({ resolve: resolve3, reject, timeout: timer });
|
|
4137
4138
|
this.stats.pending++;
|
|
4138
4139
|
});
|
|
4139
4140
|
}
|
|
@@ -4217,7 +4218,7 @@ var ConnectionPool = class {
|
|
|
4217
4218
|
this.pending = [];
|
|
4218
4219
|
this.stats.pending = 0;
|
|
4219
4220
|
while (this.connections.some((c) => c.inUse)) {
|
|
4220
|
-
await new Promise((
|
|
4221
|
+
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
4221
4222
|
}
|
|
4222
4223
|
}
|
|
4223
4224
|
async clear() {
|
|
@@ -4544,10 +4545,10 @@ var BatchProcessor = class {
|
|
|
4544
4545
|
failedBatches: 0
|
|
4545
4546
|
};
|
|
4546
4547
|
add(input) {
|
|
4547
|
-
return new Promise((
|
|
4548
|
+
return new Promise((resolve3, reject) => {
|
|
4548
4549
|
this.pending.push({
|
|
4549
4550
|
input,
|
|
4550
|
-
resolve,
|
|
4551
|
+
resolve: resolve3,
|
|
4551
4552
|
reject,
|
|
4552
4553
|
addedAt: Date.now()
|
|
4553
4554
|
});
|
|
@@ -5307,6 +5308,841 @@ function loadPrompt(promptName, options = {}, promptsDir) {
|
|
|
5307
5308
|
);
|
|
5308
5309
|
}
|
|
5309
5310
|
}
|
|
5311
|
+
|
|
5312
|
+
// src/skills/types.ts
|
|
5313
|
+
var TrustPolicyReason = /* @__PURE__ */ ((TrustPolicyReason2) => {
|
|
5314
|
+
TrustPolicyReason2["NOT_SCRIPT"] = "not-script";
|
|
5315
|
+
TrustPolicyReason2["WORKSPACE_TRUST"] = "workspace-trust";
|
|
5316
|
+
TrustPolicyReason2["TRUSTED_ROOT"] = "trusted-root";
|
|
5317
|
+
TrustPolicyReason2["UNTRUSTED_SCRIPT_DENIED"] = "untrusted-script-denied";
|
|
5318
|
+
TrustPolicyReason2["UNTRUSTED_SCRIPT_ALLOWED"] = "untrusted-script-allowed-override";
|
|
5319
|
+
TrustPolicyReason2["UNKNOWN_TRUST_LEVEL"] = "unknown-trust-level";
|
|
5320
|
+
return TrustPolicyReason2;
|
|
5321
|
+
})(TrustPolicyReason || {});
|
|
5322
|
+
var SkillRegistryEvent = /* @__PURE__ */ ((SkillRegistryEvent2) => {
|
|
5323
|
+
SkillRegistryEvent2["SKILL_DISCOVERED"] = "skill:discovered";
|
|
5324
|
+
SkillRegistryEvent2["SKILL_WARNING"] = "skill:warning";
|
|
5325
|
+
SkillRegistryEvent2["SKILL_ACTIVATED"] = "skill:activated";
|
|
5326
|
+
SkillRegistryEvent2["SKILL_RESOURCE_LOADED"] = "skill:resource-loaded";
|
|
5327
|
+
SkillRegistryEvent2["TRUST_POLICY_DENIED"] = "trust:policy-denied";
|
|
5328
|
+
SkillRegistryEvent2["TRUST_POLICY_ALLOWED"] = "trust:policy-allowed";
|
|
5329
|
+
return SkillRegistryEvent2;
|
|
5330
|
+
})(SkillRegistryEvent || {});
|
|
5331
|
+
|
|
5332
|
+
// src/skills/parser.ts
|
|
5333
|
+
import matter from "gray-matter";
|
|
5334
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
5335
|
+
var SKILL_NAME_MAX_LENGTH = 64;
|
|
5336
|
+
var SKILL_DESCRIPTION_MAX_LENGTH = 1024;
|
|
5337
|
+
function validateSkillName(name) {
|
|
5338
|
+
const errors = [];
|
|
5339
|
+
if (name === void 0 || name === null) {
|
|
5340
|
+
errors.push({ field: "name", message: "name is required" });
|
|
5341
|
+
return errors;
|
|
5342
|
+
}
|
|
5343
|
+
if (typeof name !== "string") {
|
|
5344
|
+
errors.push({ field: "name", message: "name must be a string" });
|
|
5345
|
+
return errors;
|
|
5346
|
+
}
|
|
5347
|
+
if (name.length === 0) {
|
|
5348
|
+
errors.push({ field: "name", message: "name must not be empty" });
|
|
5349
|
+
return errors;
|
|
5350
|
+
}
|
|
5351
|
+
if (name.length > SKILL_NAME_MAX_LENGTH) {
|
|
5352
|
+
errors.push({
|
|
5353
|
+
field: "name",
|
|
5354
|
+
message: `name must be at most ${SKILL_NAME_MAX_LENGTH} characters (got ${name.length})`
|
|
5355
|
+
});
|
|
5356
|
+
return errors;
|
|
5357
|
+
}
|
|
5358
|
+
if (!SKILL_NAME_PATTERN.test(name)) {
|
|
5359
|
+
errors.push({
|
|
5360
|
+
field: "name",
|
|
5361
|
+
message: "name must be lowercase alphanumeric with hyphens, no leading/trailing/consecutive hyphens"
|
|
5362
|
+
});
|
|
5363
|
+
}
|
|
5364
|
+
if (name.includes("--")) {
|
|
5365
|
+
errors.push({
|
|
5366
|
+
field: "name",
|
|
5367
|
+
message: "name must not contain consecutive hyphens"
|
|
5368
|
+
});
|
|
5369
|
+
}
|
|
5370
|
+
return errors;
|
|
5371
|
+
}
|
|
5372
|
+
function validateSkillDescription(description) {
|
|
5373
|
+
const errors = [];
|
|
5374
|
+
if (description === void 0 || description === null) {
|
|
5375
|
+
errors.push({ field: "description", message: "description is required" });
|
|
5376
|
+
return errors;
|
|
5377
|
+
}
|
|
5378
|
+
if (typeof description !== "string") {
|
|
5379
|
+
errors.push({ field: "description", message: "description must be a string" });
|
|
5380
|
+
return errors;
|
|
5381
|
+
}
|
|
5382
|
+
if (description.trim().length === 0) {
|
|
5383
|
+
errors.push({ field: "description", message: "description must not be empty" });
|
|
5384
|
+
return errors;
|
|
5385
|
+
}
|
|
5386
|
+
if (description.length > SKILL_DESCRIPTION_MAX_LENGTH) {
|
|
5387
|
+
errors.push({
|
|
5388
|
+
field: "description",
|
|
5389
|
+
message: `description must be at most ${SKILL_DESCRIPTION_MAX_LENGTH} characters (got ${description.length})`
|
|
5390
|
+
});
|
|
5391
|
+
}
|
|
5392
|
+
return errors;
|
|
5393
|
+
}
|
|
5394
|
+
function validateSkillNameMatchesDir(name, dirName) {
|
|
5395
|
+
if (name !== dirName) {
|
|
5396
|
+
return [{
|
|
5397
|
+
field: "name",
|
|
5398
|
+
message: `name "${name}" must match parent directory name "${dirName}"`
|
|
5399
|
+
}];
|
|
5400
|
+
}
|
|
5401
|
+
return [];
|
|
5402
|
+
}
|
|
5403
|
+
function parseSkillContent(content, dirName) {
|
|
5404
|
+
let parsed;
|
|
5405
|
+
try {
|
|
5406
|
+
parsed = matter(content);
|
|
5407
|
+
} catch (err) {
|
|
5408
|
+
return {
|
|
5409
|
+
success: false,
|
|
5410
|
+
error: `Failed to parse frontmatter: ${err instanceof Error ? err.message : String(err)}`
|
|
5411
|
+
};
|
|
5412
|
+
}
|
|
5413
|
+
const data = parsed.data;
|
|
5414
|
+
const errors = [
|
|
5415
|
+
...validateSkillName(data.name),
|
|
5416
|
+
...validateSkillDescription(data.description)
|
|
5417
|
+
];
|
|
5418
|
+
if (typeof data.name === "string" && data.name.length > 0 && SKILL_NAME_PATTERN.test(data.name)) {
|
|
5419
|
+
errors.push(...validateSkillNameMatchesDir(data.name, dirName));
|
|
5420
|
+
}
|
|
5421
|
+
if (errors.length > 0) {
|
|
5422
|
+
return {
|
|
5423
|
+
success: false,
|
|
5424
|
+
error: errors.map((e) => `${e.field}: ${e.message}`).join("; ")
|
|
5425
|
+
};
|
|
5426
|
+
}
|
|
5427
|
+
const metadata = {
|
|
5428
|
+
name: data.name,
|
|
5429
|
+
description: data.description
|
|
5430
|
+
};
|
|
5431
|
+
if (data.license !== void 0) {
|
|
5432
|
+
metadata.license = String(data.license);
|
|
5433
|
+
}
|
|
5434
|
+
if (Array.isArray(data.compatibility)) {
|
|
5435
|
+
metadata.compatibility = data.compatibility.map(String);
|
|
5436
|
+
}
|
|
5437
|
+
if (data.metadata !== void 0 && typeof data.metadata === "object" && data.metadata !== null) {
|
|
5438
|
+
metadata.metadata = data.metadata;
|
|
5439
|
+
}
|
|
5440
|
+
if (Array.isArray(data["allowed-tools"])) {
|
|
5441
|
+
metadata.allowedTools = data["allowed-tools"].map(String);
|
|
5442
|
+
}
|
|
5443
|
+
return {
|
|
5444
|
+
success: true,
|
|
5445
|
+
metadata,
|
|
5446
|
+
body: parsed.content
|
|
5447
|
+
};
|
|
5448
|
+
}
|
|
5449
|
+
|
|
5450
|
+
// src/skills/scanner.ts
|
|
5451
|
+
import { existsSync, readdirSync, statSync, readFileSync as readFileSync2 } from "fs";
|
|
5452
|
+
import { resolve, basename } from "path";
|
|
5453
|
+
import { homedir } from "os";
|
|
5454
|
+
var logger5 = createLogger("agentforge:core:skills:scanner", { level: "info" /* INFO */ });
|
|
5455
|
+
function expandHome(p) {
|
|
5456
|
+
if (p.startsWith("~/") || p === "~") {
|
|
5457
|
+
return resolve(homedir(), p.slice(2));
|
|
5458
|
+
}
|
|
5459
|
+
return p;
|
|
5460
|
+
}
|
|
5461
|
+
function scanSkillRoot(rootPath) {
|
|
5462
|
+
const resolvedRoot = resolve(expandHome(rootPath));
|
|
5463
|
+
const candidates = [];
|
|
5464
|
+
if (!existsSync(resolvedRoot)) {
|
|
5465
|
+
logger5.debug("Skill root does not exist, skipping", { rootPath: resolvedRoot });
|
|
5466
|
+
return candidates;
|
|
5467
|
+
}
|
|
5468
|
+
let entries;
|
|
5469
|
+
try {
|
|
5470
|
+
entries = readdirSync(resolvedRoot);
|
|
5471
|
+
} catch (err) {
|
|
5472
|
+
logger5.warn("Failed to read skill root directory", {
|
|
5473
|
+
rootPath: resolvedRoot,
|
|
5474
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5475
|
+
});
|
|
5476
|
+
return candidates;
|
|
5477
|
+
}
|
|
5478
|
+
for (const entry of entries) {
|
|
5479
|
+
const entryPath = resolve(resolvedRoot, entry);
|
|
5480
|
+
let stat;
|
|
5481
|
+
try {
|
|
5482
|
+
stat = statSync(entryPath);
|
|
5483
|
+
} catch {
|
|
5484
|
+
continue;
|
|
5485
|
+
}
|
|
5486
|
+
if (!stat.isDirectory()) {
|
|
5487
|
+
continue;
|
|
5488
|
+
}
|
|
5489
|
+
const skillMdPath = resolve(entryPath, "SKILL.md");
|
|
5490
|
+
if (!existsSync(skillMdPath)) {
|
|
5491
|
+
continue;
|
|
5492
|
+
}
|
|
5493
|
+
try {
|
|
5494
|
+
const content = readFileSync2(skillMdPath, "utf-8");
|
|
5495
|
+
candidates.push({
|
|
5496
|
+
skillPath: entryPath,
|
|
5497
|
+
dirName: basename(entryPath),
|
|
5498
|
+
content,
|
|
5499
|
+
rootPath: resolvedRoot
|
|
5500
|
+
});
|
|
5501
|
+
} catch (err) {
|
|
5502
|
+
logger5.warn("Failed to read SKILL.md", {
|
|
5503
|
+
path: skillMdPath,
|
|
5504
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5505
|
+
});
|
|
5506
|
+
}
|
|
5507
|
+
}
|
|
5508
|
+
logger5.debug("Scanned skill root", {
|
|
5509
|
+
rootPath: resolvedRoot,
|
|
5510
|
+
candidatesFound: candidates.length
|
|
5511
|
+
});
|
|
5512
|
+
return candidates;
|
|
5513
|
+
}
|
|
5514
|
+
function scanAllSkillRoots(skillRoots) {
|
|
5515
|
+
const allCandidates = [];
|
|
5516
|
+
for (const root of skillRoots) {
|
|
5517
|
+
const candidates = scanSkillRoot(root);
|
|
5518
|
+
allCandidates.push(...candidates);
|
|
5519
|
+
}
|
|
5520
|
+
logger5.info("Skill discovery complete", {
|
|
5521
|
+
rootsScanned: skillRoots.length,
|
|
5522
|
+
totalCandidates: allCandidates.length
|
|
5523
|
+
});
|
|
5524
|
+
return allCandidates;
|
|
5525
|
+
}
|
|
5526
|
+
|
|
5527
|
+
// src/skills/activation.ts
|
|
5528
|
+
import { readFileSync as readFileSync3, realpathSync } from "fs";
|
|
5529
|
+
import { resolve as resolve2, relative, isAbsolute } from "path";
|
|
5530
|
+
import matter2 from "gray-matter";
|
|
5531
|
+
import { z as z3 } from "zod";
|
|
5532
|
+
|
|
5533
|
+
// src/skills/trust.ts
|
|
5534
|
+
var SCRIPT_PATH_PREFIX = "scripts/";
|
|
5535
|
+
var SCRIPT_PATH_EXACT = "scripts";
|
|
5536
|
+
function normalizeRootConfig(root) {
|
|
5537
|
+
if (typeof root === "string") {
|
|
5538
|
+
return { path: root, trust: "untrusted" };
|
|
5539
|
+
}
|
|
5540
|
+
return root;
|
|
5541
|
+
}
|
|
5542
|
+
function isScriptResource(resourcePath) {
|
|
5543
|
+
let normalized = resourcePath.trim().replace(/\\/g, "/");
|
|
5544
|
+
normalized = normalized.replace(/\/+/g, "/");
|
|
5545
|
+
while (normalized.startsWith("./")) {
|
|
5546
|
+
normalized = normalized.slice(2);
|
|
5547
|
+
}
|
|
5548
|
+
const lower = normalized.toLowerCase();
|
|
5549
|
+
return lower === SCRIPT_PATH_EXACT || lower.startsWith(SCRIPT_PATH_PREFIX);
|
|
5550
|
+
}
|
|
5551
|
+
function evaluateTrustPolicy(resourcePath, trustLevel, allowUntrustedScripts = false) {
|
|
5552
|
+
if (!isScriptResource(resourcePath)) {
|
|
5553
|
+
return {
|
|
5554
|
+
allowed: true,
|
|
5555
|
+
reason: "not-script" /* NOT_SCRIPT */,
|
|
5556
|
+
message: "Resource is not a script \u2014 no trust check required"
|
|
5557
|
+
};
|
|
5558
|
+
}
|
|
5559
|
+
switch (trustLevel) {
|
|
5560
|
+
case "workspace":
|
|
5561
|
+
return {
|
|
5562
|
+
allowed: true,
|
|
5563
|
+
reason: "workspace-trust" /* WORKSPACE_TRUST */,
|
|
5564
|
+
message: "Script allowed \u2014 skill root has workspace trust"
|
|
5565
|
+
};
|
|
5566
|
+
case "trusted":
|
|
5567
|
+
return {
|
|
5568
|
+
allowed: true,
|
|
5569
|
+
reason: "trusted-root" /* TRUSTED_ROOT */,
|
|
5570
|
+
message: "Script allowed \u2014 skill root is explicitly trusted"
|
|
5571
|
+
};
|
|
5572
|
+
case "untrusted":
|
|
5573
|
+
if (allowUntrustedScripts) {
|
|
5574
|
+
return {
|
|
5575
|
+
allowed: true,
|
|
5576
|
+
reason: "untrusted-script-allowed-override" /* UNTRUSTED_SCRIPT_ALLOWED */,
|
|
5577
|
+
message: "Script from untrusted root allowed via allowUntrustedScripts override"
|
|
5578
|
+
};
|
|
5579
|
+
}
|
|
5580
|
+
return {
|
|
5581
|
+
allowed: false,
|
|
5582
|
+
reason: "untrusted-script-denied" /* UNTRUSTED_SCRIPT_DENIED */,
|
|
5583
|
+
message: `Script access denied \u2014 skill root is untrusted. Scripts from untrusted roots are blocked by default for security. To allow, set 'allowUntrustedScripts: true' in SkillRegistryConfig or promote the skill root to 'trusted' or 'workspace' trust level.`
|
|
5584
|
+
};
|
|
5585
|
+
default:
|
|
5586
|
+
return {
|
|
5587
|
+
allowed: false,
|
|
5588
|
+
reason: "unknown-trust-level" /* UNKNOWN_TRUST_LEVEL */,
|
|
5589
|
+
message: `Script access denied \u2014 trust level "${trustLevel}" is unknown and is treated as untrusted for security.`
|
|
5590
|
+
};
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
|
|
5594
|
+
// src/skills/activation.ts
|
|
5595
|
+
var logger6 = createLogger("agentforge:core:skills:activation", { level: "info" /* INFO */ });
|
|
5596
|
+
var activateSkillSchema = z3.object({
|
|
5597
|
+
name: z3.string().describe('The name of the skill to activate (e.g., "code-review")')
|
|
5598
|
+
});
|
|
5599
|
+
var readSkillResourceSchema = z3.object({
|
|
5600
|
+
name: z3.string().describe("The name of the skill that owns the resource"),
|
|
5601
|
+
path: z3.string().describe('Relative path to the resource file within the skill directory (e.g., "references/GUIDE.md", "scripts/setup.sh")')
|
|
5602
|
+
});
|
|
5603
|
+
function resolveResourcePath(skillPath, resourcePath) {
|
|
5604
|
+
if (isAbsolute(resourcePath)) {
|
|
5605
|
+
return { success: false, error: "Absolute resource paths are not allowed" };
|
|
5606
|
+
}
|
|
5607
|
+
const segments = resourcePath.split(/[/\\]/);
|
|
5608
|
+
if (segments.some((seg) => seg === "..")) {
|
|
5609
|
+
return { success: false, error: "Path traversal is not allowed \u2014 resource paths must stay within the skill directory" };
|
|
5610
|
+
}
|
|
5611
|
+
const resolvedPath = resolve2(skillPath, resourcePath);
|
|
5612
|
+
const resolvedSkillPath = resolve2(skillPath);
|
|
5613
|
+
const rel = relative(resolvedSkillPath, resolvedPath);
|
|
5614
|
+
if (rel.startsWith("..") || resolve2(resolvedSkillPath, rel) !== resolvedPath) {
|
|
5615
|
+
return { success: false, error: "Path traversal is not allowed \u2014 resource paths must stay within the skill directory" };
|
|
5616
|
+
}
|
|
5617
|
+
try {
|
|
5618
|
+
const realSkillRoot = realpathSync(resolvedSkillPath);
|
|
5619
|
+
const realTarget = realpathSync(resolvedPath);
|
|
5620
|
+
const realRel = relative(realSkillRoot, realTarget);
|
|
5621
|
+
if (realRel.startsWith("..") || isAbsolute(realRel)) {
|
|
5622
|
+
return { success: false, error: "Symlink target escapes the skill directory \u2014 access denied" };
|
|
5623
|
+
}
|
|
5624
|
+
} catch {
|
|
5625
|
+
}
|
|
5626
|
+
return { success: true, resolvedPath };
|
|
5627
|
+
}
|
|
5628
|
+
function createActivateSkillTool(registry) {
|
|
5629
|
+
return new ToolBuilder().name("activate-skill").description(
|
|
5630
|
+
"Activate an Agent Skill by name, loading its full instructions. Returns the complete SKILL.md body content for the named skill. Use this when you see a relevant skill in <available_skills> and want to follow its instructions."
|
|
5631
|
+
).category("skills" /* SKILLS */).tags(["skill", "activation", "agent-skills"]).schema(activateSkillSchema).implement(async ({ name }) => {
|
|
5632
|
+
const skill = registry.get(name);
|
|
5633
|
+
if (!skill) {
|
|
5634
|
+
const availableNames = registry.getNames();
|
|
5635
|
+
const suggestion = availableNames.length > 0 ? ` Available skills: ${availableNames.join(", ")}` : " No skills are currently registered.";
|
|
5636
|
+
const errorMsg = `Skill "${name}" not found.${suggestion}`;
|
|
5637
|
+
logger6.warn("Skill activation failed \u2014 not found", { name, availableCount: availableNames.length });
|
|
5638
|
+
return errorMsg;
|
|
5639
|
+
}
|
|
5640
|
+
const skillMdPath = resolve2(skill.skillPath, "SKILL.md");
|
|
5641
|
+
try {
|
|
5642
|
+
const content = readFileSync3(skillMdPath, "utf-8");
|
|
5643
|
+
const body = extractBody(content);
|
|
5644
|
+
logger6.info("Skill activated", {
|
|
5645
|
+
name: skill.metadata.name,
|
|
5646
|
+
skillPath: skill.skillPath,
|
|
5647
|
+
bodyLength: body.length
|
|
5648
|
+
});
|
|
5649
|
+
registry.emitEvent("skill:activated" /* SKILL_ACTIVATED */, {
|
|
5650
|
+
name: skill.metadata.name,
|
|
5651
|
+
skillPath: skill.skillPath,
|
|
5652
|
+
bodyLength: body.length
|
|
5653
|
+
});
|
|
5654
|
+
return body;
|
|
5655
|
+
} catch (error) {
|
|
5656
|
+
const errorMsg = `Failed to read skill "${name}" instructions: ${error instanceof Error ? error.message : String(error)}`;
|
|
5657
|
+
logger6.error("Skill activation failed \u2014 read error", {
|
|
5658
|
+
name,
|
|
5659
|
+
skillPath: skill.skillPath,
|
|
5660
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5661
|
+
});
|
|
5662
|
+
return errorMsg;
|
|
5663
|
+
}
|
|
5664
|
+
}).build();
|
|
5665
|
+
}
|
|
5666
|
+
function createReadSkillResourceTool(registry) {
|
|
5667
|
+
return new ToolBuilder().name("read-skill-resource").description(
|
|
5668
|
+
"Read a resource file from an activated Agent Skill. Returns the content of a file within the skill directory (e.g., references/, scripts/, assets/). The path must be relative to the skill root and cannot traverse outside it."
|
|
5669
|
+
).category("skills" /* SKILLS */).tags(["skill", "resource", "agent-skills"]).schema(readSkillResourceSchema).implement(async ({ name, path: resourcePath }) => {
|
|
5670
|
+
const skill = registry.get(name);
|
|
5671
|
+
if (!skill) {
|
|
5672
|
+
const availableNames = registry.getNames();
|
|
5673
|
+
const suggestion = availableNames.length > 0 ? ` Available skills: ${availableNames.join(", ")}` : " No skills are currently registered.";
|
|
5674
|
+
const errorMsg = `Skill "${name}" not found.${suggestion}`;
|
|
5675
|
+
logger6.warn("Skill resource load failed \u2014 skill not found", { name, resourcePath });
|
|
5676
|
+
return errorMsg;
|
|
5677
|
+
}
|
|
5678
|
+
const pathResult = resolveResourcePath(skill.skillPath, resourcePath);
|
|
5679
|
+
if (!pathResult.success) {
|
|
5680
|
+
logger6.warn("Skill resource load blocked \u2014 path traversal", {
|
|
5681
|
+
name,
|
|
5682
|
+
resourcePath,
|
|
5683
|
+
error: pathResult.error
|
|
5684
|
+
});
|
|
5685
|
+
return pathResult.error;
|
|
5686
|
+
}
|
|
5687
|
+
const policyDecision = evaluateTrustPolicy(
|
|
5688
|
+
resourcePath,
|
|
5689
|
+
skill.trustLevel,
|
|
5690
|
+
registry.getAllowUntrustedScripts()
|
|
5691
|
+
);
|
|
5692
|
+
if (!policyDecision.allowed) {
|
|
5693
|
+
logger6.warn("Skill resource load blocked \u2014 trust policy", {
|
|
5694
|
+
name,
|
|
5695
|
+
resourcePath,
|
|
5696
|
+
trustLevel: skill.trustLevel,
|
|
5697
|
+
reason: policyDecision.reason,
|
|
5698
|
+
message: policyDecision.message
|
|
5699
|
+
});
|
|
5700
|
+
registry.emitEvent("trust:policy-denied" /* TRUST_POLICY_DENIED */, {
|
|
5701
|
+
name: skill.metadata.name,
|
|
5702
|
+
resourcePath,
|
|
5703
|
+
trustLevel: skill.trustLevel,
|
|
5704
|
+
reason: policyDecision.reason,
|
|
5705
|
+
message: policyDecision.message
|
|
5706
|
+
});
|
|
5707
|
+
return policyDecision.message;
|
|
5708
|
+
}
|
|
5709
|
+
if (policyDecision.reason !== "not-script" /* NOT_SCRIPT */) {
|
|
5710
|
+
logger6.info("Skill resource trust policy \u2014 allowed", {
|
|
5711
|
+
name,
|
|
5712
|
+
resourcePath,
|
|
5713
|
+
trustLevel: skill.trustLevel,
|
|
5714
|
+
reason: policyDecision.reason
|
|
5715
|
+
});
|
|
5716
|
+
registry.emitEvent("trust:policy-allowed" /* TRUST_POLICY_ALLOWED */, {
|
|
5717
|
+
name: skill.metadata.name,
|
|
5718
|
+
resourcePath,
|
|
5719
|
+
trustLevel: skill.trustLevel,
|
|
5720
|
+
reason: policyDecision.reason
|
|
5721
|
+
});
|
|
5722
|
+
}
|
|
5723
|
+
try {
|
|
5724
|
+
const content = readFileSync3(pathResult.resolvedPath, "utf-8");
|
|
5725
|
+
logger6.info("Skill resource loaded", {
|
|
5726
|
+
name: skill.metadata.name,
|
|
5727
|
+
resourcePath,
|
|
5728
|
+
resolvedPath: pathResult.resolvedPath,
|
|
5729
|
+
contentLength: content.length
|
|
5730
|
+
});
|
|
5731
|
+
registry.emitEvent("skill:resource-loaded" /* SKILL_RESOURCE_LOADED */, {
|
|
5732
|
+
name: skill.metadata.name,
|
|
5733
|
+
resourcePath,
|
|
5734
|
+
resolvedPath: pathResult.resolvedPath,
|
|
5735
|
+
contentLength: content.length
|
|
5736
|
+
});
|
|
5737
|
+
return content;
|
|
5738
|
+
} catch (error) {
|
|
5739
|
+
const errorMsg = `Failed to read resource "${resourcePath}" from skill "${name}": ${error instanceof Error ? error.message : String(error)}`;
|
|
5740
|
+
logger6.warn("Skill resource load failed \u2014 file not found or unreadable", {
|
|
5741
|
+
name,
|
|
5742
|
+
resourcePath,
|
|
5743
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5744
|
+
});
|
|
5745
|
+
return errorMsg;
|
|
5746
|
+
}
|
|
5747
|
+
}).build();
|
|
5748
|
+
}
|
|
5749
|
+
function createSkillActivationTools(registry) {
|
|
5750
|
+
return [
|
|
5751
|
+
createActivateSkillTool(registry),
|
|
5752
|
+
createReadSkillResourceTool(registry)
|
|
5753
|
+
];
|
|
5754
|
+
}
|
|
5755
|
+
function extractBody(content) {
|
|
5756
|
+
return matter2(content).content.trim();
|
|
5757
|
+
}
|
|
5758
|
+
|
|
5759
|
+
// src/skills/registry.ts
|
|
5760
|
+
import { resolve as resolvePath } from "path";
|
|
5761
|
+
var logger7 = createLogger("agentforge:core:skills:registry", { level: "info" /* INFO */ });
|
|
5762
|
+
var SkillRegistry = class {
|
|
5763
|
+
skills = /* @__PURE__ */ new Map();
|
|
5764
|
+
eventHandlers = /* @__PURE__ */ new Map();
|
|
5765
|
+
config;
|
|
5766
|
+
scanErrors = [];
|
|
5767
|
+
/** Maps resolved root paths → trust levels for skill trust assignment */
|
|
5768
|
+
rootTrustMap = /* @__PURE__ */ new Map();
|
|
5769
|
+
/**
|
|
5770
|
+
* Create a SkillRegistry and immediately scan configured roots for skills.
|
|
5771
|
+
*
|
|
5772
|
+
* @param config - Registry configuration with skill root paths
|
|
5773
|
+
*
|
|
5774
|
+
* @example
|
|
5775
|
+
* ```ts
|
|
5776
|
+
* const registry = new SkillRegistry({
|
|
5777
|
+
* skillRoots: ['.agentskills', '~/.agentskills', './project-skills'],
|
|
5778
|
+
* });
|
|
5779
|
+
* console.log(`Discovered ${registry.size()} skills`);
|
|
5780
|
+
* ```
|
|
5781
|
+
*/
|
|
5782
|
+
constructor(config) {
|
|
5783
|
+
this.config = config;
|
|
5784
|
+
this.discover();
|
|
5785
|
+
}
|
|
5786
|
+
/**
|
|
5787
|
+
* Scan all configured roots and populate the registry.
|
|
5788
|
+
*
|
|
5789
|
+
* Called automatically during construction. Can be called again
|
|
5790
|
+
* to re-scan (clears existing skills first).
|
|
5791
|
+
*/
|
|
5792
|
+
discover() {
|
|
5793
|
+
this.skills.clear();
|
|
5794
|
+
this.scanErrors = [];
|
|
5795
|
+
this.rootTrustMap.clear();
|
|
5796
|
+
const normalizedRoots = this.config.skillRoots.map(normalizeRootConfig);
|
|
5797
|
+
const plainPaths = normalizedRoots.map((r) => r.path);
|
|
5798
|
+
for (const root of normalizedRoots) {
|
|
5799
|
+
const resolvedPath = resolvePath(expandHome(root.path));
|
|
5800
|
+
this.rootTrustMap.set(resolvedPath, root.trust);
|
|
5801
|
+
}
|
|
5802
|
+
const candidates = scanAllSkillRoots(plainPaths);
|
|
5803
|
+
let successCount = 0;
|
|
5804
|
+
let warningCount = 0;
|
|
5805
|
+
for (const candidate of candidates) {
|
|
5806
|
+
const result = parseSkillContent(candidate.content, candidate.dirName);
|
|
5807
|
+
if (!result.success) {
|
|
5808
|
+
warningCount++;
|
|
5809
|
+
this.scanErrors.push({
|
|
5810
|
+
path: candidate.skillPath,
|
|
5811
|
+
error: result.error || "Unknown parse error"
|
|
5812
|
+
});
|
|
5813
|
+
this.emit("skill:warning" /* SKILL_WARNING */, {
|
|
5814
|
+
skillPath: candidate.skillPath,
|
|
5815
|
+
rootPath: candidate.rootPath,
|
|
5816
|
+
error: result.error
|
|
5817
|
+
});
|
|
5818
|
+
logger7.warn("Skipping invalid skill", {
|
|
5819
|
+
skillPath: candidate.skillPath,
|
|
5820
|
+
error: result.error
|
|
5821
|
+
});
|
|
5822
|
+
continue;
|
|
5823
|
+
}
|
|
5824
|
+
const skill = {
|
|
5825
|
+
metadata: result.metadata,
|
|
5826
|
+
skillPath: candidate.skillPath,
|
|
5827
|
+
rootPath: candidate.rootPath,
|
|
5828
|
+
trustLevel: this.rootTrustMap.get(candidate.rootPath) ?? "untrusted"
|
|
5829
|
+
};
|
|
5830
|
+
if (this.skills.has(skill.metadata.name)) {
|
|
5831
|
+
const existing = this.skills.get(skill.metadata.name);
|
|
5832
|
+
warningCount++;
|
|
5833
|
+
const warningMsg = `Duplicate skill name "${skill.metadata.name}" from "${candidate.rootPath}" \u2014 keeping version from "${existing.rootPath}" (first root takes precedence)`;
|
|
5834
|
+
this.scanErrors.push({
|
|
5835
|
+
path: candidate.skillPath,
|
|
5836
|
+
error: warningMsg
|
|
5837
|
+
});
|
|
5838
|
+
this.emit("skill:warning" /* SKILL_WARNING */, {
|
|
5839
|
+
skillPath: candidate.skillPath,
|
|
5840
|
+
rootPath: candidate.rootPath,
|
|
5841
|
+
duplicateOf: existing.skillPath,
|
|
5842
|
+
error: warningMsg
|
|
5843
|
+
});
|
|
5844
|
+
logger7.warn("Duplicate skill name, keeping first", {
|
|
5845
|
+
name: skill.metadata.name,
|
|
5846
|
+
kept: existing.skillPath,
|
|
5847
|
+
skipped: candidate.skillPath
|
|
5848
|
+
});
|
|
5849
|
+
continue;
|
|
5850
|
+
}
|
|
5851
|
+
this.skills.set(skill.metadata.name, skill);
|
|
5852
|
+
successCount++;
|
|
5853
|
+
this.emit("skill:discovered" /* SKILL_DISCOVERED */, skill);
|
|
5854
|
+
logger7.debug("Skill discovered", {
|
|
5855
|
+
name: skill.metadata.name,
|
|
5856
|
+
description: skill.metadata.description.slice(0, 80),
|
|
5857
|
+
skillPath: skill.skillPath
|
|
5858
|
+
});
|
|
5859
|
+
}
|
|
5860
|
+
logger7.info("Skill registry populated", {
|
|
5861
|
+
rootsScanned: this.config.skillRoots.length,
|
|
5862
|
+
skillsDiscovered: successCount,
|
|
5863
|
+
warnings: warningCount
|
|
5864
|
+
});
|
|
5865
|
+
}
|
|
5866
|
+
// ─── Query API (parallel to ToolRegistry) ──────────────────────────────
|
|
5867
|
+
/**
|
|
5868
|
+
* Get a skill by name.
|
|
5869
|
+
*
|
|
5870
|
+
* @param name - The skill name
|
|
5871
|
+
* @returns The skill, or undefined if not found
|
|
5872
|
+
*
|
|
5873
|
+
* @example
|
|
5874
|
+
* ```ts
|
|
5875
|
+
* const skill = registry.get('code-review');
|
|
5876
|
+
* if (skill) {
|
|
5877
|
+
* console.log(skill.metadata.description);
|
|
5878
|
+
* }
|
|
5879
|
+
* ```
|
|
5880
|
+
*/
|
|
5881
|
+
get(name) {
|
|
5882
|
+
return this.skills.get(name);
|
|
5883
|
+
}
|
|
5884
|
+
/**
|
|
5885
|
+
* Get all discovered skills.
|
|
5886
|
+
*
|
|
5887
|
+
* @returns Array of all skills
|
|
5888
|
+
*
|
|
5889
|
+
* @example
|
|
5890
|
+
* ```ts
|
|
5891
|
+
* const allSkills = registry.getAll();
|
|
5892
|
+
* console.log(`Total skills: ${allSkills.length}`);
|
|
5893
|
+
* ```
|
|
5894
|
+
*/
|
|
5895
|
+
getAll() {
|
|
5896
|
+
return Array.from(this.skills.values());
|
|
5897
|
+
}
|
|
5898
|
+
/**
|
|
5899
|
+
* Check if a skill exists in the registry.
|
|
5900
|
+
*
|
|
5901
|
+
* @param name - The skill name
|
|
5902
|
+
* @returns True if the skill exists
|
|
5903
|
+
*
|
|
5904
|
+
* @example
|
|
5905
|
+
* ```ts
|
|
5906
|
+
* if (registry.has('code-review')) {
|
|
5907
|
+
* console.log('Skill available!');
|
|
5908
|
+
* }
|
|
5909
|
+
* ```
|
|
5910
|
+
*/
|
|
5911
|
+
has(name) {
|
|
5912
|
+
return this.skills.has(name);
|
|
5913
|
+
}
|
|
5914
|
+
/**
|
|
5915
|
+
* Get the number of discovered skills.
|
|
5916
|
+
*
|
|
5917
|
+
* @returns Number of skills in the registry
|
|
5918
|
+
*
|
|
5919
|
+
* @example
|
|
5920
|
+
* ```ts
|
|
5921
|
+
* console.log(`Registry has ${registry.size()} skills`);
|
|
5922
|
+
* ```
|
|
5923
|
+
*/
|
|
5924
|
+
size() {
|
|
5925
|
+
return this.skills.size;
|
|
5926
|
+
}
|
|
5927
|
+
/**
|
|
5928
|
+
* Get all skill names.
|
|
5929
|
+
*
|
|
5930
|
+
* @returns Array of skill names
|
|
5931
|
+
*/
|
|
5932
|
+
getNames() {
|
|
5933
|
+
return Array.from(this.skills.keys());
|
|
5934
|
+
}
|
|
5935
|
+
/**
|
|
5936
|
+
* Get errors/warnings from the last scan.
|
|
5937
|
+
*
|
|
5938
|
+
* Useful for diagnostics and observability.
|
|
5939
|
+
*
|
|
5940
|
+
* @returns Array of scan errors with paths
|
|
5941
|
+
*/
|
|
5942
|
+
getScanErrors() {
|
|
5943
|
+
return this.scanErrors;
|
|
5944
|
+
}
|
|
5945
|
+
/**
|
|
5946
|
+
* Check whether untrusted script access is allowed via config override.
|
|
5947
|
+
*
|
|
5948
|
+
* Used by activation tools to pass the override flag to trust policy checks.
|
|
5949
|
+
*
|
|
5950
|
+
* @returns True if `allowUntrustedScripts` is set in config
|
|
5951
|
+
*/
|
|
5952
|
+
getAllowUntrustedScripts() {
|
|
5953
|
+
return this.config.allowUntrustedScripts ?? false;
|
|
5954
|
+
}
|
|
5955
|
+
/**
|
|
5956
|
+
* Get the `allowed-tools` list for a skill.
|
|
5957
|
+
*
|
|
5958
|
+
* Returns the `allowedTools` array from the skill's frontmatter metadata,
|
|
5959
|
+
* enabling agents to filter their tool set based on what the skill expects.
|
|
5960
|
+
*
|
|
5961
|
+
* @param name - The skill name
|
|
5962
|
+
* @returns Array of allowed tool names, or undefined if skill not found or field not set
|
|
5963
|
+
*
|
|
5964
|
+
* @example
|
|
5965
|
+
* ```ts
|
|
5966
|
+
* const allowed = registry.getAllowedTools('code-review');
|
|
5967
|
+
* if (allowed) {
|
|
5968
|
+
* const filteredTools = allTools.filter(t => allowed.includes(t.name));
|
|
5969
|
+
* }
|
|
5970
|
+
* ```
|
|
5971
|
+
*/
|
|
5972
|
+
getAllowedTools(name) {
|
|
5973
|
+
const skill = this.skills.get(name);
|
|
5974
|
+
return skill?.metadata.allowedTools;
|
|
5975
|
+
}
|
|
5976
|
+
// ─── Prompt Generation ─────────────────────────────────────────────────
|
|
5977
|
+
/**
|
|
5978
|
+
* Generate an `<available_skills>` XML block for system prompt injection.
|
|
5979
|
+
*
|
|
5980
|
+
* Returns an empty string when:
|
|
5981
|
+
* - `config.enabled` is `false` (default) — agents operate with unmodified prompts
|
|
5982
|
+
* - No skills match the filter criteria
|
|
5983
|
+
*
|
|
5984
|
+
* The output composes naturally with `toolRegistry.generatePrompt()` —
|
|
5985
|
+
* simply concatenate both into the system prompt.
|
|
5986
|
+
*
|
|
5987
|
+
* @param options - Optional filtering (subset of skill names)
|
|
5988
|
+
* @returns XML string or empty string
|
|
5989
|
+
*
|
|
5990
|
+
* @example
|
|
5991
|
+
* ```ts
|
|
5992
|
+
* // All skills
|
|
5993
|
+
* const xml = registry.generatePrompt();
|
|
5994
|
+
*
|
|
5995
|
+
* // Subset for a focused agent
|
|
5996
|
+
* const xml = registry.generatePrompt({ skills: ['code-review', 'testing'] });
|
|
5997
|
+
*
|
|
5998
|
+
* // Compose with tool prompt
|
|
5999
|
+
* const systemPrompt = [
|
|
6000
|
+
* toolRegistry.generatePrompt(),
|
|
6001
|
+
* skillRegistry.generatePrompt(),
|
|
6002
|
+
* ].filter(Boolean).join('\n\n');
|
|
6003
|
+
* ```
|
|
6004
|
+
*/
|
|
6005
|
+
generatePrompt(options) {
|
|
6006
|
+
if (!this.config.enabled) {
|
|
6007
|
+
logger7.debug("Skill prompt generation skipped (disabled)", {
|
|
6008
|
+
enabled: this.config.enabled ?? false
|
|
6009
|
+
});
|
|
6010
|
+
return "";
|
|
6011
|
+
}
|
|
6012
|
+
let skills = this.getAll();
|
|
6013
|
+
if (options?.skills && options.skills.length > 0) {
|
|
6014
|
+
const requested = new Set(options.skills);
|
|
6015
|
+
skills = skills.filter((s) => requested.has(s.metadata.name));
|
|
6016
|
+
}
|
|
6017
|
+
if (this.config.maxDiscoveredSkills !== void 0 && this.config.maxDiscoveredSkills >= 0) {
|
|
6018
|
+
skills = skills.slice(0, this.config.maxDiscoveredSkills);
|
|
6019
|
+
}
|
|
6020
|
+
if (skills.length === 0) {
|
|
6021
|
+
logger7.debug("Skill prompt generation produced empty result", {
|
|
6022
|
+
totalDiscovered: this.size(),
|
|
6023
|
+
filterApplied: !!(options?.skills && options.skills.length > 0),
|
|
6024
|
+
maxCap: this.config.maxDiscoveredSkills
|
|
6025
|
+
});
|
|
6026
|
+
return "";
|
|
6027
|
+
}
|
|
6028
|
+
const skillEntries = skills.map((skill) => {
|
|
6029
|
+
const lines = [
|
|
6030
|
+
" <skill>",
|
|
6031
|
+
` <name>${escapeXml(skill.metadata.name)}</name>`,
|
|
6032
|
+
` <description>${escapeXml(skill.metadata.description)}</description>`,
|
|
6033
|
+
` <location>${escapeXml(skill.skillPath)}</location>`,
|
|
6034
|
+
" </skill>"
|
|
6035
|
+
];
|
|
6036
|
+
return lines.join("\n");
|
|
6037
|
+
});
|
|
6038
|
+
const xml = `<available_skills>
|
|
6039
|
+
${skillEntries.join("\n")}
|
|
6040
|
+
</available_skills>`;
|
|
6041
|
+
const estimatedTokens = Math.ceil(xml.length / 4);
|
|
6042
|
+
logger7.info("Skill prompt generated", {
|
|
6043
|
+
skillCount: skills.length,
|
|
6044
|
+
totalDiscovered: this.size(),
|
|
6045
|
+
filterApplied: !!(options?.skills && options.skills.length > 0),
|
|
6046
|
+
maxCap: this.config.maxDiscoveredSkills,
|
|
6047
|
+
estimatedTokens,
|
|
6048
|
+
xmlLength: xml.length
|
|
6049
|
+
});
|
|
6050
|
+
return xml;
|
|
6051
|
+
}
|
|
6052
|
+
// ─── Event System ──────────────────────────────────────────────────────
|
|
6053
|
+
/**
|
|
6054
|
+
* Register an event handler.
|
|
6055
|
+
*
|
|
6056
|
+
* @param event - The event to listen for
|
|
6057
|
+
* @param handler - The handler function
|
|
6058
|
+
*
|
|
6059
|
+
* @example
|
|
6060
|
+
* ```ts
|
|
6061
|
+
* registry.on(SkillRegistryEvent.SKILL_DISCOVERED, (skill) => {
|
|
6062
|
+
* console.log('Found skill:', skill.metadata.name);
|
|
6063
|
+
* });
|
|
6064
|
+
* ```
|
|
6065
|
+
*/
|
|
6066
|
+
on(event, handler) {
|
|
6067
|
+
if (!this.eventHandlers.has(event)) {
|
|
6068
|
+
this.eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
6069
|
+
}
|
|
6070
|
+
this.eventHandlers.get(event).add(handler);
|
|
6071
|
+
}
|
|
6072
|
+
/**
|
|
6073
|
+
* Unregister an event handler.
|
|
6074
|
+
*
|
|
6075
|
+
* @param event - The event to stop listening for
|
|
6076
|
+
* @param handler - The handler function to remove
|
|
6077
|
+
*/
|
|
6078
|
+
off(event, handler) {
|
|
6079
|
+
const handlers = this.eventHandlers.get(event);
|
|
6080
|
+
if (handlers) {
|
|
6081
|
+
handlers.delete(handler);
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
/**
|
|
6085
|
+
* Emit an event to all registered handlers.
|
|
6086
|
+
*
|
|
6087
|
+
* @param event - The event to emit
|
|
6088
|
+
* @param data - The event data
|
|
6089
|
+
* @private
|
|
6090
|
+
*/
|
|
6091
|
+
emit(event, data) {
|
|
6092
|
+
const handlers = this.eventHandlers.get(event);
|
|
6093
|
+
if (handlers) {
|
|
6094
|
+
handlers.forEach((handler) => {
|
|
6095
|
+
try {
|
|
6096
|
+
handler(data);
|
|
6097
|
+
} catch (error) {
|
|
6098
|
+
logger7.error("Skill event handler error", {
|
|
6099
|
+
event,
|
|
6100
|
+
error: error instanceof Error ? error.message : String(error),
|
|
6101
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
6102
|
+
});
|
|
6103
|
+
}
|
|
6104
|
+
});
|
|
6105
|
+
}
|
|
6106
|
+
}
|
|
6107
|
+
/**
|
|
6108
|
+
* Emit an event (public API for activation tools).
|
|
6109
|
+
*
|
|
6110
|
+
* Used by skill activation tools to emit `skill:activated` and
|
|
6111
|
+
* `skill:resource-loaded` events through the registry's event system.
|
|
6112
|
+
*
|
|
6113
|
+
* @param event - The event to emit
|
|
6114
|
+
* @param data - The event data
|
|
6115
|
+
*/
|
|
6116
|
+
emitEvent(event, data) {
|
|
6117
|
+
this.emit(event, data);
|
|
6118
|
+
}
|
|
6119
|
+
// ─── Tool Integration ────────────────────────────────────────────────
|
|
6120
|
+
/**
|
|
6121
|
+
* Create activation tools pre-wired to this registry instance.
|
|
6122
|
+
*
|
|
6123
|
+
* Returns `activate-skill` and `read-skill-resource` tools that
|
|
6124
|
+
* agents can use to load skill instructions and resources on demand.
|
|
6125
|
+
*
|
|
6126
|
+
* @returns Array of [activate-skill, read-skill-resource] tools
|
|
6127
|
+
*
|
|
6128
|
+
* @example
|
|
6129
|
+
* ```ts
|
|
6130
|
+
* const agent = createReActAgent({
|
|
6131
|
+
* model: llm,
|
|
6132
|
+
* tools: [
|
|
6133
|
+
* ...toolRegistry.toLangChainTools(),
|
|
6134
|
+
* ...skillRegistry.toActivationTools(),
|
|
6135
|
+
* ],
|
|
6136
|
+
* });
|
|
6137
|
+
* ```
|
|
6138
|
+
*/
|
|
6139
|
+
toActivationTools() {
|
|
6140
|
+
return createSkillActivationTools(this);
|
|
6141
|
+
}
|
|
6142
|
+
};
|
|
6143
|
+
function escapeXml(str) {
|
|
6144
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6145
|
+
}
|
|
5310
6146
|
export {
|
|
5311
6147
|
AgentError,
|
|
5312
6148
|
AlertManager,
|
|
@@ -5325,6 +6161,8 @@ export {
|
|
|
5325
6161
|
MissingDescriptionError,
|
|
5326
6162
|
Profiler,
|
|
5327
6163
|
RegistryEvent,
|
|
6164
|
+
SkillRegistry,
|
|
6165
|
+
SkillRegistryEvent,
|
|
5328
6166
|
TimeoutError,
|
|
5329
6167
|
ToolBuilder,
|
|
5330
6168
|
ToolCategory,
|
|
@@ -5334,6 +6172,7 @@ export {
|
|
|
5334
6172
|
ToolNameSchema,
|
|
5335
6173
|
ToolRegistry,
|
|
5336
6174
|
ToolRelationsSchema,
|
|
6175
|
+
TrustPolicyReason,
|
|
5337
6176
|
batch,
|
|
5338
6177
|
broadcast,
|
|
5339
6178
|
cache,
|
|
@@ -5347,6 +6186,7 @@ export {
|
|
|
5347
6186
|
composeWithOptions,
|
|
5348
6187
|
conditional,
|
|
5349
6188
|
configureLangSmith,
|
|
6189
|
+
createActivateSkillTool,
|
|
5350
6190
|
createAlertManager,
|
|
5351
6191
|
createApprovalRequiredInterrupt,
|
|
5352
6192
|
createAuditLogger,
|
|
@@ -5375,11 +6215,13 @@ export {
|
|
|
5375
6215
|
createParallelWorkflow,
|
|
5376
6216
|
createProfiler,
|
|
5377
6217
|
createProgressTracker,
|
|
6218
|
+
createReadSkillResourceTool,
|
|
5378
6219
|
createSSEFormatter,
|
|
5379
6220
|
createSequentialWorkflow,
|
|
5380
6221
|
createSharedCache,
|
|
5381
6222
|
createSharedConcurrencyController,
|
|
5382
6223
|
createSharedRateLimiter,
|
|
6224
|
+
createSkillActivationTools,
|
|
5383
6225
|
createSqliteCheckpointer,
|
|
5384
6226
|
createStateAnnotation,
|
|
5385
6227
|
createSubgraph,
|
|
@@ -5390,6 +6232,8 @@ export {
|
|
|
5390
6232
|
createToolUnsafe,
|
|
5391
6233
|
createWebSocketHandler,
|
|
5392
6234
|
development,
|
|
6235
|
+
evaluateTrustPolicy,
|
|
6236
|
+
expandHome,
|
|
5393
6237
|
filter,
|
|
5394
6238
|
formatAgentResumedEvent,
|
|
5395
6239
|
formatAgentWaitingEvent,
|
|
@@ -5409,20 +6253,26 @@ export {
|
|
|
5409
6253
|
isCustomInterrupt,
|
|
5410
6254
|
isHumanRequestInterrupt,
|
|
5411
6255
|
isMemoryCheckpointer,
|
|
6256
|
+
isScriptResource,
|
|
5412
6257
|
isTracingEnabled,
|
|
5413
6258
|
loadPrompt,
|
|
5414
6259
|
map,
|
|
5415
6260
|
merge,
|
|
5416
6261
|
mergeState,
|
|
6262
|
+
normalizeRootConfig,
|
|
5417
6263
|
parallel,
|
|
5418
6264
|
parseSSEEvent,
|
|
6265
|
+
parseSkillContent,
|
|
5419
6266
|
presets,
|
|
5420
6267
|
production,
|
|
5421
6268
|
reduce,
|
|
5422
6269
|
renderTemplate,
|
|
6270
|
+
resolveResourcePath,
|
|
5423
6271
|
retry,
|
|
5424
6272
|
safeValidateSchemaDescriptions,
|
|
5425
6273
|
sanitizeValue,
|
|
6274
|
+
scanAllSkillRoots,
|
|
6275
|
+
scanSkillRoot,
|
|
5426
6276
|
sendMessage,
|
|
5427
6277
|
sequential,
|
|
5428
6278
|
sequentialBuilder,
|
|
@@ -5434,6 +6284,9 @@ export {
|
|
|
5434
6284
|
toLangChainTools,
|
|
5435
6285
|
toolBuilder,
|
|
5436
6286
|
validateSchemaDescriptions,
|
|
6287
|
+
validateSkillDescription,
|
|
6288
|
+
validateSkillName,
|
|
6289
|
+
validateSkillNameMatchesDir,
|
|
5437
6290
|
validateState,
|
|
5438
6291
|
validateTool,
|
|
5439
6292
|
validateToolMetadata,
|