@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.cjs
CHANGED
|
@@ -47,6 +47,8 @@ __export(index_exports, {
|
|
|
47
47
|
MissingDescriptionError: () => MissingDescriptionError,
|
|
48
48
|
Profiler: () => Profiler,
|
|
49
49
|
RegistryEvent: () => RegistryEvent,
|
|
50
|
+
SkillRegistry: () => SkillRegistry,
|
|
51
|
+
SkillRegistryEvent: () => SkillRegistryEvent,
|
|
50
52
|
TimeoutError: () => TimeoutError,
|
|
51
53
|
ToolBuilder: () => ToolBuilder,
|
|
52
54
|
ToolCategory: () => ToolCategory,
|
|
@@ -56,6 +58,7 @@ __export(index_exports, {
|
|
|
56
58
|
ToolNameSchema: () => ToolNameSchema,
|
|
57
59
|
ToolRegistry: () => ToolRegistry,
|
|
58
60
|
ToolRelationsSchema: () => ToolRelationsSchema,
|
|
61
|
+
TrustPolicyReason: () => TrustPolicyReason,
|
|
59
62
|
batch: () => batch,
|
|
60
63
|
broadcast: () => broadcast,
|
|
61
64
|
cache: () => cache,
|
|
@@ -69,6 +72,7 @@ __export(index_exports, {
|
|
|
69
72
|
composeWithOptions: () => composeWithOptions,
|
|
70
73
|
conditional: () => conditional,
|
|
71
74
|
configureLangSmith: () => configureLangSmith,
|
|
75
|
+
createActivateSkillTool: () => createActivateSkillTool,
|
|
72
76
|
createAlertManager: () => createAlertManager,
|
|
73
77
|
createApprovalRequiredInterrupt: () => createApprovalRequiredInterrupt,
|
|
74
78
|
createAuditLogger: () => createAuditLogger,
|
|
@@ -97,11 +101,13 @@ __export(index_exports, {
|
|
|
97
101
|
createParallelWorkflow: () => createParallelWorkflow,
|
|
98
102
|
createProfiler: () => createProfiler,
|
|
99
103
|
createProgressTracker: () => createProgressTracker,
|
|
104
|
+
createReadSkillResourceTool: () => createReadSkillResourceTool,
|
|
100
105
|
createSSEFormatter: () => createSSEFormatter,
|
|
101
106
|
createSequentialWorkflow: () => createSequentialWorkflow,
|
|
102
107
|
createSharedCache: () => createSharedCache,
|
|
103
108
|
createSharedConcurrencyController: () => createSharedConcurrencyController,
|
|
104
109
|
createSharedRateLimiter: () => createSharedRateLimiter,
|
|
110
|
+
createSkillActivationTools: () => createSkillActivationTools,
|
|
105
111
|
createSqliteCheckpointer: () => createSqliteCheckpointer,
|
|
106
112
|
createStateAnnotation: () => createStateAnnotation,
|
|
107
113
|
createSubgraph: () => createSubgraph,
|
|
@@ -112,6 +118,8 @@ __export(index_exports, {
|
|
|
112
118
|
createToolUnsafe: () => createToolUnsafe,
|
|
113
119
|
createWebSocketHandler: () => createWebSocketHandler,
|
|
114
120
|
development: () => development,
|
|
121
|
+
evaluateTrustPolicy: () => evaluateTrustPolicy,
|
|
122
|
+
expandHome: () => expandHome,
|
|
115
123
|
filter: () => filter,
|
|
116
124
|
formatAgentResumedEvent: () => formatAgentResumedEvent,
|
|
117
125
|
formatAgentWaitingEvent: () => formatAgentWaitingEvent,
|
|
@@ -131,20 +139,26 @@ __export(index_exports, {
|
|
|
131
139
|
isCustomInterrupt: () => isCustomInterrupt,
|
|
132
140
|
isHumanRequestInterrupt: () => isHumanRequestInterrupt,
|
|
133
141
|
isMemoryCheckpointer: () => isMemoryCheckpointer,
|
|
142
|
+
isScriptResource: () => isScriptResource,
|
|
134
143
|
isTracingEnabled: () => isTracingEnabled,
|
|
135
144
|
loadPrompt: () => loadPrompt,
|
|
136
145
|
map: () => map,
|
|
137
146
|
merge: () => merge,
|
|
138
147
|
mergeState: () => mergeState,
|
|
148
|
+
normalizeRootConfig: () => normalizeRootConfig,
|
|
139
149
|
parallel: () => parallel,
|
|
140
150
|
parseSSEEvent: () => parseSSEEvent,
|
|
151
|
+
parseSkillContent: () => parseSkillContent,
|
|
141
152
|
presets: () => presets,
|
|
142
153
|
production: () => production,
|
|
143
154
|
reduce: () => reduce,
|
|
144
155
|
renderTemplate: () => renderTemplate,
|
|
156
|
+
resolveResourcePath: () => resolveResourcePath,
|
|
145
157
|
retry: () => retry,
|
|
146
158
|
safeValidateSchemaDescriptions: () => safeValidateSchemaDescriptions,
|
|
147
159
|
sanitizeValue: () => sanitizeValue,
|
|
160
|
+
scanAllSkillRoots: () => scanAllSkillRoots,
|
|
161
|
+
scanSkillRoot: () => scanSkillRoot,
|
|
148
162
|
sendMessage: () => sendMessage,
|
|
149
163
|
sequential: () => sequential,
|
|
150
164
|
sequentialBuilder: () => sequentialBuilder,
|
|
@@ -156,6 +170,9 @@ __export(index_exports, {
|
|
|
156
170
|
toLangChainTools: () => toLangChainTools,
|
|
157
171
|
toolBuilder: () => toolBuilder,
|
|
158
172
|
validateSchemaDescriptions: () => validateSchemaDescriptions,
|
|
173
|
+
validateSkillDescription: () => validateSkillDescription,
|
|
174
|
+
validateSkillName: () => validateSkillName,
|
|
175
|
+
validateSkillNameMatchesDir: () => validateSkillNameMatchesDir,
|
|
159
176
|
validateState: () => validateState,
|
|
160
177
|
validateTool: () => validateTool,
|
|
161
178
|
validateToolMetadata: () => validateToolMetadata,
|
|
@@ -182,6 +199,7 @@ var ToolCategory = /* @__PURE__ */ ((ToolCategory2) => {
|
|
|
182
199
|
ToolCategory2["API"] = "api";
|
|
183
200
|
ToolCategory2["UTILITY"] = "utility";
|
|
184
201
|
ToolCategory2["CUSTOM"] = "custom";
|
|
202
|
+
ToolCategory2["SKILLS"] = "skills";
|
|
185
203
|
return ToolCategory2;
|
|
186
204
|
})(ToolCategory || {});
|
|
187
205
|
|
|
@@ -1580,7 +1598,7 @@ function createToolExecutor(config = {}) {
|
|
|
1580
1598
|
}
|
|
1581
1599
|
if (attempt < policy.maxAttempts) {
|
|
1582
1600
|
const delay = calculateBackoff(attempt, policy);
|
|
1583
|
-
await new Promise((
|
|
1601
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
1584
1602
|
}
|
|
1585
1603
|
}
|
|
1586
1604
|
}
|
|
@@ -1637,12 +1655,12 @@ function createToolExecutor(config = {}) {
|
|
|
1637
1655
|
}
|
|
1638
1656
|
async function execute(tool, input, options = {}) {
|
|
1639
1657
|
const priority = options.priority || priorityFn(tool);
|
|
1640
|
-
return new Promise((
|
|
1658
|
+
return new Promise((resolve3, reject) => {
|
|
1641
1659
|
queue.push({
|
|
1642
1660
|
tool,
|
|
1643
1661
|
input,
|
|
1644
1662
|
priority,
|
|
1645
|
-
resolve,
|
|
1663
|
+
resolve: resolve3,
|
|
1646
1664
|
reject,
|
|
1647
1665
|
timestamp: Date.now()
|
|
1648
1666
|
});
|
|
@@ -1942,7 +1960,7 @@ function retry(tool, options = {}) {
|
|
|
1942
1960
|
lastError = error;
|
|
1943
1961
|
if (attempt < maxAttempts) {
|
|
1944
1962
|
const waitTime = backoff === "exponential" ? delay * Math.pow(2, attempt - 1) : delay * attempt;
|
|
1945
|
-
await new Promise((
|
|
1963
|
+
await new Promise((resolve3) => setTimeout(resolve3, waitTime));
|
|
1946
1964
|
}
|
|
1947
1965
|
}
|
|
1948
1966
|
}
|
|
@@ -2003,7 +2021,7 @@ function createMockTool(config) {
|
|
|
2003
2021
|
const startTime = Date.now();
|
|
2004
2022
|
if (latency) {
|
|
2005
2023
|
const delay = typeof latency === "number" ? latency : Math.random() * (latency.max - latency.min) + latency.min;
|
|
2006
|
-
await new Promise((
|
|
2024
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
2007
2025
|
}
|
|
2008
2026
|
if (errorRate > 0 && Math.random() < errorRate) {
|
|
2009
2027
|
const error2 = new Error(`Random error from ${name}`);
|
|
@@ -2074,8 +2092,8 @@ function createToolSimulator(config) {
|
|
|
2074
2092
|
if (!latency) return 0;
|
|
2075
2093
|
const u1 = Math.random();
|
|
2076
2094
|
const u2 = Math.random();
|
|
2077
|
-
const
|
|
2078
|
-
return Math.max(0, latency.mean +
|
|
2095
|
+
const z4 = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
2096
|
+
return Math.max(0, latency.mean + z4 * latency.stddev);
|
|
2079
2097
|
}
|
|
2080
2098
|
return {
|
|
2081
2099
|
execute: async (toolName, input) => {
|
|
@@ -2086,7 +2104,7 @@ function createToolSimulator(config) {
|
|
|
2086
2104
|
const startTime = Date.now();
|
|
2087
2105
|
if (latency) {
|
|
2088
2106
|
const delay = generateLatency();
|
|
2089
|
-
await new Promise((
|
|
2107
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
2090
2108
|
}
|
|
2091
2109
|
if (errorRate > 0 && Math.random() < errorRate) {
|
|
2092
2110
|
const error = new Error(`Simulated error from ${toolName}`);
|
|
@@ -2444,7 +2462,7 @@ function calculateDelay(attempt, strategy, initialDelay, maxDelay) {
|
|
|
2444
2462
|
return Math.min(delay, maxDelay);
|
|
2445
2463
|
}
|
|
2446
2464
|
function sleep(ms) {
|
|
2447
|
-
return new Promise((
|
|
2465
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
2448
2466
|
}
|
|
2449
2467
|
function withRetry(node, options = {}) {
|
|
2450
2468
|
const {
|
|
@@ -2690,14 +2708,14 @@ var withLogging = (options) => {
|
|
|
2690
2708
|
onComplete,
|
|
2691
2709
|
onError
|
|
2692
2710
|
} = options;
|
|
2693
|
-
const
|
|
2711
|
+
const logger8 = providedLogger || createLogger(name, { level });
|
|
2694
2712
|
return (node) => {
|
|
2695
2713
|
return async (state) => {
|
|
2696
2714
|
const startTime = Date.now();
|
|
2697
2715
|
try {
|
|
2698
2716
|
if (logInput) {
|
|
2699
2717
|
const data = extractData ? extractData(state) : { state };
|
|
2700
|
-
|
|
2718
|
+
logger8.info("Node execution started", data);
|
|
2701
2719
|
}
|
|
2702
2720
|
if (onStart) {
|
|
2703
2721
|
onStart(state);
|
|
@@ -2707,9 +2725,9 @@ var withLogging = (options) => {
|
|
|
2707
2725
|
if (logOutput) {
|
|
2708
2726
|
const data = extractData ? extractData(result) : { result };
|
|
2709
2727
|
if (logDuration) {
|
|
2710
|
-
|
|
2728
|
+
logger8.info(`Node execution completed (${duration}ms)`, data);
|
|
2711
2729
|
} else {
|
|
2712
|
-
|
|
2730
|
+
logger8.info("Node execution completed", data);
|
|
2713
2731
|
}
|
|
2714
2732
|
}
|
|
2715
2733
|
if (onComplete) {
|
|
@@ -2720,7 +2738,7 @@ var withLogging = (options) => {
|
|
|
2720
2738
|
const duration = Date.now() - startTime;
|
|
2721
2739
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
2722
2740
|
if (logErrors) {
|
|
2723
|
-
|
|
2741
|
+
logger8.error(`Node execution failed (${duration}ms)`, {
|
|
2724
2742
|
error: err.message,
|
|
2725
2743
|
stack: err.stack
|
|
2726
2744
|
});
|
|
@@ -2756,7 +2774,7 @@ function withLogging2(options) {
|
|
|
2756
2774
|
function production(node, options) {
|
|
2757
2775
|
const {
|
|
2758
2776
|
nodeName,
|
|
2759
|
-
logger:
|
|
2777
|
+
logger: logger8,
|
|
2760
2778
|
enableMetrics = true,
|
|
2761
2779
|
enableTracing = true,
|
|
2762
2780
|
enableRetry = true,
|
|
@@ -2764,7 +2782,7 @@ function production(node, options) {
|
|
|
2764
2782
|
retryOptions = {},
|
|
2765
2783
|
errorOptions = {}
|
|
2766
2784
|
} = options;
|
|
2767
|
-
const actualLogger =
|
|
2785
|
+
const actualLogger = logger8 || createLogger(nodeName, { level: "info" /* INFO */ });
|
|
2768
2786
|
const middleware = [];
|
|
2769
2787
|
middleware.push(
|
|
2770
2788
|
withLogging2({
|
|
@@ -2828,9 +2846,9 @@ function development(node, options) {
|
|
|
2828
2846
|
const {
|
|
2829
2847
|
nodeName,
|
|
2830
2848
|
verbose = true,
|
|
2831
|
-
logger:
|
|
2849
|
+
logger: logger8
|
|
2832
2850
|
} = options;
|
|
2833
|
-
const actualLogger =
|
|
2851
|
+
const actualLogger = logger8 || createLogger(nodeName, { level: "debug" /* DEBUG */ });
|
|
2834
2852
|
return withLogging2({
|
|
2835
2853
|
logger: actualLogger,
|
|
2836
2854
|
name: nodeName,
|
|
@@ -2854,7 +2872,7 @@ function testing(node, options) {
|
|
|
2854
2872
|
invocations.push(state);
|
|
2855
2873
|
}
|
|
2856
2874
|
if (delay > 0) {
|
|
2857
|
-
await new Promise((
|
|
2875
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
2858
2876
|
}
|
|
2859
2877
|
if (simulateError) {
|
|
2860
2878
|
throw simulateError;
|
|
@@ -3313,12 +3331,12 @@ var ConcurrencyController = class {
|
|
|
3313
3331
|
}
|
|
3314
3332
|
throw new Error(`Queue is full (max size: ${this.maxQueueSize})`);
|
|
3315
3333
|
}
|
|
3316
|
-
return new Promise((
|
|
3334
|
+
return new Promise((resolve3, reject) => {
|
|
3317
3335
|
const task = {
|
|
3318
3336
|
state,
|
|
3319
3337
|
priority,
|
|
3320
3338
|
executor,
|
|
3321
|
-
resolve,
|
|
3339
|
+
resolve: resolve3,
|
|
3322
3340
|
reject,
|
|
3323
3341
|
timestamp: Date.now()
|
|
3324
3342
|
};
|
|
@@ -3783,7 +3801,7 @@ async function* throttle(stream, options) {
|
|
|
3783
3801
|
const now = Date.now();
|
|
3784
3802
|
const timeSinceLastEmit = now - lastEmit;
|
|
3785
3803
|
if (timeSinceLastEmit < interval) {
|
|
3786
|
-
await new Promise((
|
|
3804
|
+
await new Promise((resolve3) => setTimeout(resolve3, interval - timeSinceLastEmit));
|
|
3787
3805
|
}
|
|
3788
3806
|
lastEmit = Date.now();
|
|
3789
3807
|
yield item;
|
|
@@ -4298,17 +4316,17 @@ var ConnectionPool = class {
|
|
|
4298
4316
|
this.options.onAcquire?.(connection);
|
|
4299
4317
|
return connection;
|
|
4300
4318
|
}
|
|
4301
|
-
return new Promise((
|
|
4319
|
+
return new Promise((resolve3, reject) => {
|
|
4302
4320
|
const timeout2 = poolConfig.acquireTimeout || 3e4;
|
|
4303
4321
|
const timer = setTimeout(() => {
|
|
4304
|
-
const index = this.pending.findIndex((p) => p.resolve ===
|
|
4322
|
+
const index = this.pending.findIndex((p) => p.resolve === resolve3);
|
|
4305
4323
|
if (index !== -1) {
|
|
4306
4324
|
this.pending.splice(index, 1);
|
|
4307
4325
|
this.stats.pending--;
|
|
4308
4326
|
}
|
|
4309
4327
|
reject(new Error("Acquire timeout"));
|
|
4310
4328
|
}, timeout2);
|
|
4311
|
-
this.pending.push({ resolve, reject, timeout: timer });
|
|
4329
|
+
this.pending.push({ resolve: resolve3, reject, timeout: timer });
|
|
4312
4330
|
this.stats.pending++;
|
|
4313
4331
|
});
|
|
4314
4332
|
}
|
|
@@ -4392,7 +4410,7 @@ var ConnectionPool = class {
|
|
|
4392
4410
|
this.pending = [];
|
|
4393
4411
|
this.stats.pending = 0;
|
|
4394
4412
|
while (this.connections.some((c) => c.inUse)) {
|
|
4395
|
-
await new Promise((
|
|
4413
|
+
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
4396
4414
|
}
|
|
4397
4415
|
}
|
|
4398
4416
|
async clear() {
|
|
@@ -4719,10 +4737,10 @@ var BatchProcessor = class {
|
|
|
4719
4737
|
failedBatches: 0
|
|
4720
4738
|
};
|
|
4721
4739
|
add(input) {
|
|
4722
|
-
return new Promise((
|
|
4740
|
+
return new Promise((resolve3, reject) => {
|
|
4723
4741
|
this.pending.push({
|
|
4724
4742
|
input,
|
|
4725
|
-
resolve,
|
|
4743
|
+
resolve: resolve3,
|
|
4726
4744
|
reject,
|
|
4727
4745
|
addedAt: Date.now()
|
|
4728
4746
|
});
|
|
@@ -5482,6 +5500,841 @@ function loadPrompt(promptName, options = {}, promptsDir) {
|
|
|
5482
5500
|
);
|
|
5483
5501
|
}
|
|
5484
5502
|
}
|
|
5503
|
+
|
|
5504
|
+
// src/skills/types.ts
|
|
5505
|
+
var TrustPolicyReason = /* @__PURE__ */ ((TrustPolicyReason2) => {
|
|
5506
|
+
TrustPolicyReason2["NOT_SCRIPT"] = "not-script";
|
|
5507
|
+
TrustPolicyReason2["WORKSPACE_TRUST"] = "workspace-trust";
|
|
5508
|
+
TrustPolicyReason2["TRUSTED_ROOT"] = "trusted-root";
|
|
5509
|
+
TrustPolicyReason2["UNTRUSTED_SCRIPT_DENIED"] = "untrusted-script-denied";
|
|
5510
|
+
TrustPolicyReason2["UNTRUSTED_SCRIPT_ALLOWED"] = "untrusted-script-allowed-override";
|
|
5511
|
+
TrustPolicyReason2["UNKNOWN_TRUST_LEVEL"] = "unknown-trust-level";
|
|
5512
|
+
return TrustPolicyReason2;
|
|
5513
|
+
})(TrustPolicyReason || {});
|
|
5514
|
+
var SkillRegistryEvent = /* @__PURE__ */ ((SkillRegistryEvent2) => {
|
|
5515
|
+
SkillRegistryEvent2["SKILL_DISCOVERED"] = "skill:discovered";
|
|
5516
|
+
SkillRegistryEvent2["SKILL_WARNING"] = "skill:warning";
|
|
5517
|
+
SkillRegistryEvent2["SKILL_ACTIVATED"] = "skill:activated";
|
|
5518
|
+
SkillRegistryEvent2["SKILL_RESOURCE_LOADED"] = "skill:resource-loaded";
|
|
5519
|
+
SkillRegistryEvent2["TRUST_POLICY_DENIED"] = "trust:policy-denied";
|
|
5520
|
+
SkillRegistryEvent2["TRUST_POLICY_ALLOWED"] = "trust:policy-allowed";
|
|
5521
|
+
return SkillRegistryEvent2;
|
|
5522
|
+
})(SkillRegistryEvent || {});
|
|
5523
|
+
|
|
5524
|
+
// src/skills/parser.ts
|
|
5525
|
+
var import_gray_matter = __toESM(require("gray-matter"), 1);
|
|
5526
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
5527
|
+
var SKILL_NAME_MAX_LENGTH = 64;
|
|
5528
|
+
var SKILL_DESCRIPTION_MAX_LENGTH = 1024;
|
|
5529
|
+
function validateSkillName(name) {
|
|
5530
|
+
const errors = [];
|
|
5531
|
+
if (name === void 0 || name === null) {
|
|
5532
|
+
errors.push({ field: "name", message: "name is required" });
|
|
5533
|
+
return errors;
|
|
5534
|
+
}
|
|
5535
|
+
if (typeof name !== "string") {
|
|
5536
|
+
errors.push({ field: "name", message: "name must be a string" });
|
|
5537
|
+
return errors;
|
|
5538
|
+
}
|
|
5539
|
+
if (name.length === 0) {
|
|
5540
|
+
errors.push({ field: "name", message: "name must not be empty" });
|
|
5541
|
+
return errors;
|
|
5542
|
+
}
|
|
5543
|
+
if (name.length > SKILL_NAME_MAX_LENGTH) {
|
|
5544
|
+
errors.push({
|
|
5545
|
+
field: "name",
|
|
5546
|
+
message: `name must be at most ${SKILL_NAME_MAX_LENGTH} characters (got ${name.length})`
|
|
5547
|
+
});
|
|
5548
|
+
return errors;
|
|
5549
|
+
}
|
|
5550
|
+
if (!SKILL_NAME_PATTERN.test(name)) {
|
|
5551
|
+
errors.push({
|
|
5552
|
+
field: "name",
|
|
5553
|
+
message: "name must be lowercase alphanumeric with hyphens, no leading/trailing/consecutive hyphens"
|
|
5554
|
+
});
|
|
5555
|
+
}
|
|
5556
|
+
if (name.includes("--")) {
|
|
5557
|
+
errors.push({
|
|
5558
|
+
field: "name",
|
|
5559
|
+
message: "name must not contain consecutive hyphens"
|
|
5560
|
+
});
|
|
5561
|
+
}
|
|
5562
|
+
return errors;
|
|
5563
|
+
}
|
|
5564
|
+
function validateSkillDescription(description) {
|
|
5565
|
+
const errors = [];
|
|
5566
|
+
if (description === void 0 || description === null) {
|
|
5567
|
+
errors.push({ field: "description", message: "description is required" });
|
|
5568
|
+
return errors;
|
|
5569
|
+
}
|
|
5570
|
+
if (typeof description !== "string") {
|
|
5571
|
+
errors.push({ field: "description", message: "description must be a string" });
|
|
5572
|
+
return errors;
|
|
5573
|
+
}
|
|
5574
|
+
if (description.trim().length === 0) {
|
|
5575
|
+
errors.push({ field: "description", message: "description must not be empty" });
|
|
5576
|
+
return errors;
|
|
5577
|
+
}
|
|
5578
|
+
if (description.length > SKILL_DESCRIPTION_MAX_LENGTH) {
|
|
5579
|
+
errors.push({
|
|
5580
|
+
field: "description",
|
|
5581
|
+
message: `description must be at most ${SKILL_DESCRIPTION_MAX_LENGTH} characters (got ${description.length})`
|
|
5582
|
+
});
|
|
5583
|
+
}
|
|
5584
|
+
return errors;
|
|
5585
|
+
}
|
|
5586
|
+
function validateSkillNameMatchesDir(name, dirName) {
|
|
5587
|
+
if (name !== dirName) {
|
|
5588
|
+
return [{
|
|
5589
|
+
field: "name",
|
|
5590
|
+
message: `name "${name}" must match parent directory name "${dirName}"`
|
|
5591
|
+
}];
|
|
5592
|
+
}
|
|
5593
|
+
return [];
|
|
5594
|
+
}
|
|
5595
|
+
function parseSkillContent(content, dirName) {
|
|
5596
|
+
let parsed;
|
|
5597
|
+
try {
|
|
5598
|
+
parsed = (0, import_gray_matter.default)(content);
|
|
5599
|
+
} catch (err) {
|
|
5600
|
+
return {
|
|
5601
|
+
success: false,
|
|
5602
|
+
error: `Failed to parse frontmatter: ${err instanceof Error ? err.message : String(err)}`
|
|
5603
|
+
};
|
|
5604
|
+
}
|
|
5605
|
+
const data = parsed.data;
|
|
5606
|
+
const errors = [
|
|
5607
|
+
...validateSkillName(data.name),
|
|
5608
|
+
...validateSkillDescription(data.description)
|
|
5609
|
+
];
|
|
5610
|
+
if (typeof data.name === "string" && data.name.length > 0 && SKILL_NAME_PATTERN.test(data.name)) {
|
|
5611
|
+
errors.push(...validateSkillNameMatchesDir(data.name, dirName));
|
|
5612
|
+
}
|
|
5613
|
+
if (errors.length > 0) {
|
|
5614
|
+
return {
|
|
5615
|
+
success: false,
|
|
5616
|
+
error: errors.map((e) => `${e.field}: ${e.message}`).join("; ")
|
|
5617
|
+
};
|
|
5618
|
+
}
|
|
5619
|
+
const metadata = {
|
|
5620
|
+
name: data.name,
|
|
5621
|
+
description: data.description
|
|
5622
|
+
};
|
|
5623
|
+
if (data.license !== void 0) {
|
|
5624
|
+
metadata.license = String(data.license);
|
|
5625
|
+
}
|
|
5626
|
+
if (Array.isArray(data.compatibility)) {
|
|
5627
|
+
metadata.compatibility = data.compatibility.map(String);
|
|
5628
|
+
}
|
|
5629
|
+
if (data.metadata !== void 0 && typeof data.metadata === "object" && data.metadata !== null) {
|
|
5630
|
+
metadata.metadata = data.metadata;
|
|
5631
|
+
}
|
|
5632
|
+
if (Array.isArray(data["allowed-tools"])) {
|
|
5633
|
+
metadata.allowedTools = data["allowed-tools"].map(String);
|
|
5634
|
+
}
|
|
5635
|
+
return {
|
|
5636
|
+
success: true,
|
|
5637
|
+
metadata,
|
|
5638
|
+
body: parsed.content
|
|
5639
|
+
};
|
|
5640
|
+
}
|
|
5641
|
+
|
|
5642
|
+
// src/skills/scanner.ts
|
|
5643
|
+
var import_node_fs = require("fs");
|
|
5644
|
+
var import_node_path = require("path");
|
|
5645
|
+
var import_node_os = require("os");
|
|
5646
|
+
var logger5 = createLogger("agentforge:core:skills:scanner", { level: "info" /* INFO */ });
|
|
5647
|
+
function expandHome(p) {
|
|
5648
|
+
if (p.startsWith("~/") || p === "~") {
|
|
5649
|
+
return (0, import_node_path.resolve)((0, import_node_os.homedir)(), p.slice(2));
|
|
5650
|
+
}
|
|
5651
|
+
return p;
|
|
5652
|
+
}
|
|
5653
|
+
function scanSkillRoot(rootPath) {
|
|
5654
|
+
const resolvedRoot = (0, import_node_path.resolve)(expandHome(rootPath));
|
|
5655
|
+
const candidates = [];
|
|
5656
|
+
if (!(0, import_node_fs.existsSync)(resolvedRoot)) {
|
|
5657
|
+
logger5.debug("Skill root does not exist, skipping", { rootPath: resolvedRoot });
|
|
5658
|
+
return candidates;
|
|
5659
|
+
}
|
|
5660
|
+
let entries;
|
|
5661
|
+
try {
|
|
5662
|
+
entries = (0, import_node_fs.readdirSync)(resolvedRoot);
|
|
5663
|
+
} catch (err) {
|
|
5664
|
+
logger5.warn("Failed to read skill root directory", {
|
|
5665
|
+
rootPath: resolvedRoot,
|
|
5666
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5667
|
+
});
|
|
5668
|
+
return candidates;
|
|
5669
|
+
}
|
|
5670
|
+
for (const entry of entries) {
|
|
5671
|
+
const entryPath = (0, import_node_path.resolve)(resolvedRoot, entry);
|
|
5672
|
+
let stat;
|
|
5673
|
+
try {
|
|
5674
|
+
stat = (0, import_node_fs.statSync)(entryPath);
|
|
5675
|
+
} catch {
|
|
5676
|
+
continue;
|
|
5677
|
+
}
|
|
5678
|
+
if (!stat.isDirectory()) {
|
|
5679
|
+
continue;
|
|
5680
|
+
}
|
|
5681
|
+
const skillMdPath = (0, import_node_path.resolve)(entryPath, "SKILL.md");
|
|
5682
|
+
if (!(0, import_node_fs.existsSync)(skillMdPath)) {
|
|
5683
|
+
continue;
|
|
5684
|
+
}
|
|
5685
|
+
try {
|
|
5686
|
+
const content = (0, import_node_fs.readFileSync)(skillMdPath, "utf-8");
|
|
5687
|
+
candidates.push({
|
|
5688
|
+
skillPath: entryPath,
|
|
5689
|
+
dirName: (0, import_node_path.basename)(entryPath),
|
|
5690
|
+
content,
|
|
5691
|
+
rootPath: resolvedRoot
|
|
5692
|
+
});
|
|
5693
|
+
} catch (err) {
|
|
5694
|
+
logger5.warn("Failed to read SKILL.md", {
|
|
5695
|
+
path: skillMdPath,
|
|
5696
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5697
|
+
});
|
|
5698
|
+
}
|
|
5699
|
+
}
|
|
5700
|
+
logger5.debug("Scanned skill root", {
|
|
5701
|
+
rootPath: resolvedRoot,
|
|
5702
|
+
candidatesFound: candidates.length
|
|
5703
|
+
});
|
|
5704
|
+
return candidates;
|
|
5705
|
+
}
|
|
5706
|
+
function scanAllSkillRoots(skillRoots) {
|
|
5707
|
+
const allCandidates = [];
|
|
5708
|
+
for (const root of skillRoots) {
|
|
5709
|
+
const candidates = scanSkillRoot(root);
|
|
5710
|
+
allCandidates.push(...candidates);
|
|
5711
|
+
}
|
|
5712
|
+
logger5.info("Skill discovery complete", {
|
|
5713
|
+
rootsScanned: skillRoots.length,
|
|
5714
|
+
totalCandidates: allCandidates.length
|
|
5715
|
+
});
|
|
5716
|
+
return allCandidates;
|
|
5717
|
+
}
|
|
5718
|
+
|
|
5719
|
+
// src/skills/activation.ts
|
|
5720
|
+
var import_node_fs2 = require("fs");
|
|
5721
|
+
var import_node_path2 = require("path");
|
|
5722
|
+
var import_gray_matter2 = __toESM(require("gray-matter"), 1);
|
|
5723
|
+
var import_zod3 = require("zod");
|
|
5724
|
+
|
|
5725
|
+
// src/skills/trust.ts
|
|
5726
|
+
var SCRIPT_PATH_PREFIX = "scripts/";
|
|
5727
|
+
var SCRIPT_PATH_EXACT = "scripts";
|
|
5728
|
+
function normalizeRootConfig(root) {
|
|
5729
|
+
if (typeof root === "string") {
|
|
5730
|
+
return { path: root, trust: "untrusted" };
|
|
5731
|
+
}
|
|
5732
|
+
return root;
|
|
5733
|
+
}
|
|
5734
|
+
function isScriptResource(resourcePath) {
|
|
5735
|
+
let normalized = resourcePath.trim().replace(/\\/g, "/");
|
|
5736
|
+
normalized = normalized.replace(/\/+/g, "/");
|
|
5737
|
+
while (normalized.startsWith("./")) {
|
|
5738
|
+
normalized = normalized.slice(2);
|
|
5739
|
+
}
|
|
5740
|
+
const lower = normalized.toLowerCase();
|
|
5741
|
+
return lower === SCRIPT_PATH_EXACT || lower.startsWith(SCRIPT_PATH_PREFIX);
|
|
5742
|
+
}
|
|
5743
|
+
function evaluateTrustPolicy(resourcePath, trustLevel, allowUntrustedScripts = false) {
|
|
5744
|
+
if (!isScriptResource(resourcePath)) {
|
|
5745
|
+
return {
|
|
5746
|
+
allowed: true,
|
|
5747
|
+
reason: "not-script" /* NOT_SCRIPT */,
|
|
5748
|
+
message: "Resource is not a script \u2014 no trust check required"
|
|
5749
|
+
};
|
|
5750
|
+
}
|
|
5751
|
+
switch (trustLevel) {
|
|
5752
|
+
case "workspace":
|
|
5753
|
+
return {
|
|
5754
|
+
allowed: true,
|
|
5755
|
+
reason: "workspace-trust" /* WORKSPACE_TRUST */,
|
|
5756
|
+
message: "Script allowed \u2014 skill root has workspace trust"
|
|
5757
|
+
};
|
|
5758
|
+
case "trusted":
|
|
5759
|
+
return {
|
|
5760
|
+
allowed: true,
|
|
5761
|
+
reason: "trusted-root" /* TRUSTED_ROOT */,
|
|
5762
|
+
message: "Script allowed \u2014 skill root is explicitly trusted"
|
|
5763
|
+
};
|
|
5764
|
+
case "untrusted":
|
|
5765
|
+
if (allowUntrustedScripts) {
|
|
5766
|
+
return {
|
|
5767
|
+
allowed: true,
|
|
5768
|
+
reason: "untrusted-script-allowed-override" /* UNTRUSTED_SCRIPT_ALLOWED */,
|
|
5769
|
+
message: "Script from untrusted root allowed via allowUntrustedScripts override"
|
|
5770
|
+
};
|
|
5771
|
+
}
|
|
5772
|
+
return {
|
|
5773
|
+
allowed: false,
|
|
5774
|
+
reason: "untrusted-script-denied" /* UNTRUSTED_SCRIPT_DENIED */,
|
|
5775
|
+
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.`
|
|
5776
|
+
};
|
|
5777
|
+
default:
|
|
5778
|
+
return {
|
|
5779
|
+
allowed: false,
|
|
5780
|
+
reason: "unknown-trust-level" /* UNKNOWN_TRUST_LEVEL */,
|
|
5781
|
+
message: `Script access denied \u2014 trust level "${trustLevel}" is unknown and is treated as untrusted for security.`
|
|
5782
|
+
};
|
|
5783
|
+
}
|
|
5784
|
+
}
|
|
5785
|
+
|
|
5786
|
+
// src/skills/activation.ts
|
|
5787
|
+
var logger6 = createLogger("agentforge:core:skills:activation", { level: "info" /* INFO */ });
|
|
5788
|
+
var activateSkillSchema = import_zod3.z.object({
|
|
5789
|
+
name: import_zod3.z.string().describe('The name of the skill to activate (e.g., "code-review")')
|
|
5790
|
+
});
|
|
5791
|
+
var readSkillResourceSchema = import_zod3.z.object({
|
|
5792
|
+
name: import_zod3.z.string().describe("The name of the skill that owns the resource"),
|
|
5793
|
+
path: import_zod3.z.string().describe('Relative path to the resource file within the skill directory (e.g., "references/GUIDE.md", "scripts/setup.sh")')
|
|
5794
|
+
});
|
|
5795
|
+
function resolveResourcePath(skillPath, resourcePath) {
|
|
5796
|
+
if ((0, import_node_path2.isAbsolute)(resourcePath)) {
|
|
5797
|
+
return { success: false, error: "Absolute resource paths are not allowed" };
|
|
5798
|
+
}
|
|
5799
|
+
const segments = resourcePath.split(/[/\\]/);
|
|
5800
|
+
if (segments.some((seg) => seg === "..")) {
|
|
5801
|
+
return { success: false, error: "Path traversal is not allowed \u2014 resource paths must stay within the skill directory" };
|
|
5802
|
+
}
|
|
5803
|
+
const resolvedPath = (0, import_node_path2.resolve)(skillPath, resourcePath);
|
|
5804
|
+
const resolvedSkillPath = (0, import_node_path2.resolve)(skillPath);
|
|
5805
|
+
const rel = (0, import_node_path2.relative)(resolvedSkillPath, resolvedPath);
|
|
5806
|
+
if (rel.startsWith("..") || (0, import_node_path2.resolve)(resolvedSkillPath, rel) !== resolvedPath) {
|
|
5807
|
+
return { success: false, error: "Path traversal is not allowed \u2014 resource paths must stay within the skill directory" };
|
|
5808
|
+
}
|
|
5809
|
+
try {
|
|
5810
|
+
const realSkillRoot = (0, import_node_fs2.realpathSync)(resolvedSkillPath);
|
|
5811
|
+
const realTarget = (0, import_node_fs2.realpathSync)(resolvedPath);
|
|
5812
|
+
const realRel = (0, import_node_path2.relative)(realSkillRoot, realTarget);
|
|
5813
|
+
if (realRel.startsWith("..") || (0, import_node_path2.isAbsolute)(realRel)) {
|
|
5814
|
+
return { success: false, error: "Symlink target escapes the skill directory \u2014 access denied" };
|
|
5815
|
+
}
|
|
5816
|
+
} catch {
|
|
5817
|
+
}
|
|
5818
|
+
return { success: true, resolvedPath };
|
|
5819
|
+
}
|
|
5820
|
+
function createActivateSkillTool(registry) {
|
|
5821
|
+
return new ToolBuilder().name("activate-skill").description(
|
|
5822
|
+
"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."
|
|
5823
|
+
).category("skills" /* SKILLS */).tags(["skill", "activation", "agent-skills"]).schema(activateSkillSchema).implement(async ({ name }) => {
|
|
5824
|
+
const skill = registry.get(name);
|
|
5825
|
+
if (!skill) {
|
|
5826
|
+
const availableNames = registry.getNames();
|
|
5827
|
+
const suggestion = availableNames.length > 0 ? ` Available skills: ${availableNames.join(", ")}` : " No skills are currently registered.";
|
|
5828
|
+
const errorMsg = `Skill "${name}" not found.${suggestion}`;
|
|
5829
|
+
logger6.warn("Skill activation failed \u2014 not found", { name, availableCount: availableNames.length });
|
|
5830
|
+
return errorMsg;
|
|
5831
|
+
}
|
|
5832
|
+
const skillMdPath = (0, import_node_path2.resolve)(skill.skillPath, "SKILL.md");
|
|
5833
|
+
try {
|
|
5834
|
+
const content = (0, import_node_fs2.readFileSync)(skillMdPath, "utf-8");
|
|
5835
|
+
const body = extractBody(content);
|
|
5836
|
+
logger6.info("Skill activated", {
|
|
5837
|
+
name: skill.metadata.name,
|
|
5838
|
+
skillPath: skill.skillPath,
|
|
5839
|
+
bodyLength: body.length
|
|
5840
|
+
});
|
|
5841
|
+
registry.emitEvent("skill:activated" /* SKILL_ACTIVATED */, {
|
|
5842
|
+
name: skill.metadata.name,
|
|
5843
|
+
skillPath: skill.skillPath,
|
|
5844
|
+
bodyLength: body.length
|
|
5845
|
+
});
|
|
5846
|
+
return body;
|
|
5847
|
+
} catch (error) {
|
|
5848
|
+
const errorMsg = `Failed to read skill "${name}" instructions: ${error instanceof Error ? error.message : String(error)}`;
|
|
5849
|
+
logger6.error("Skill activation failed \u2014 read error", {
|
|
5850
|
+
name,
|
|
5851
|
+
skillPath: skill.skillPath,
|
|
5852
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5853
|
+
});
|
|
5854
|
+
return errorMsg;
|
|
5855
|
+
}
|
|
5856
|
+
}).build();
|
|
5857
|
+
}
|
|
5858
|
+
function createReadSkillResourceTool(registry) {
|
|
5859
|
+
return new ToolBuilder().name("read-skill-resource").description(
|
|
5860
|
+
"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."
|
|
5861
|
+
).category("skills" /* SKILLS */).tags(["skill", "resource", "agent-skills"]).schema(readSkillResourceSchema).implement(async ({ name, path: resourcePath }) => {
|
|
5862
|
+
const skill = registry.get(name);
|
|
5863
|
+
if (!skill) {
|
|
5864
|
+
const availableNames = registry.getNames();
|
|
5865
|
+
const suggestion = availableNames.length > 0 ? ` Available skills: ${availableNames.join(", ")}` : " No skills are currently registered.";
|
|
5866
|
+
const errorMsg = `Skill "${name}" not found.${suggestion}`;
|
|
5867
|
+
logger6.warn("Skill resource load failed \u2014 skill not found", { name, resourcePath });
|
|
5868
|
+
return errorMsg;
|
|
5869
|
+
}
|
|
5870
|
+
const pathResult = resolveResourcePath(skill.skillPath, resourcePath);
|
|
5871
|
+
if (!pathResult.success) {
|
|
5872
|
+
logger6.warn("Skill resource load blocked \u2014 path traversal", {
|
|
5873
|
+
name,
|
|
5874
|
+
resourcePath,
|
|
5875
|
+
error: pathResult.error
|
|
5876
|
+
});
|
|
5877
|
+
return pathResult.error;
|
|
5878
|
+
}
|
|
5879
|
+
const policyDecision = evaluateTrustPolicy(
|
|
5880
|
+
resourcePath,
|
|
5881
|
+
skill.trustLevel,
|
|
5882
|
+
registry.getAllowUntrustedScripts()
|
|
5883
|
+
);
|
|
5884
|
+
if (!policyDecision.allowed) {
|
|
5885
|
+
logger6.warn("Skill resource load blocked \u2014 trust policy", {
|
|
5886
|
+
name,
|
|
5887
|
+
resourcePath,
|
|
5888
|
+
trustLevel: skill.trustLevel,
|
|
5889
|
+
reason: policyDecision.reason,
|
|
5890
|
+
message: policyDecision.message
|
|
5891
|
+
});
|
|
5892
|
+
registry.emitEvent("trust:policy-denied" /* TRUST_POLICY_DENIED */, {
|
|
5893
|
+
name: skill.metadata.name,
|
|
5894
|
+
resourcePath,
|
|
5895
|
+
trustLevel: skill.trustLevel,
|
|
5896
|
+
reason: policyDecision.reason,
|
|
5897
|
+
message: policyDecision.message
|
|
5898
|
+
});
|
|
5899
|
+
return policyDecision.message;
|
|
5900
|
+
}
|
|
5901
|
+
if (policyDecision.reason !== "not-script" /* NOT_SCRIPT */) {
|
|
5902
|
+
logger6.info("Skill resource trust policy \u2014 allowed", {
|
|
5903
|
+
name,
|
|
5904
|
+
resourcePath,
|
|
5905
|
+
trustLevel: skill.trustLevel,
|
|
5906
|
+
reason: policyDecision.reason
|
|
5907
|
+
});
|
|
5908
|
+
registry.emitEvent("trust:policy-allowed" /* TRUST_POLICY_ALLOWED */, {
|
|
5909
|
+
name: skill.metadata.name,
|
|
5910
|
+
resourcePath,
|
|
5911
|
+
trustLevel: skill.trustLevel,
|
|
5912
|
+
reason: policyDecision.reason
|
|
5913
|
+
});
|
|
5914
|
+
}
|
|
5915
|
+
try {
|
|
5916
|
+
const content = (0, import_node_fs2.readFileSync)(pathResult.resolvedPath, "utf-8");
|
|
5917
|
+
logger6.info("Skill resource loaded", {
|
|
5918
|
+
name: skill.metadata.name,
|
|
5919
|
+
resourcePath,
|
|
5920
|
+
resolvedPath: pathResult.resolvedPath,
|
|
5921
|
+
contentLength: content.length
|
|
5922
|
+
});
|
|
5923
|
+
registry.emitEvent("skill:resource-loaded" /* SKILL_RESOURCE_LOADED */, {
|
|
5924
|
+
name: skill.metadata.name,
|
|
5925
|
+
resourcePath,
|
|
5926
|
+
resolvedPath: pathResult.resolvedPath,
|
|
5927
|
+
contentLength: content.length
|
|
5928
|
+
});
|
|
5929
|
+
return content;
|
|
5930
|
+
} catch (error) {
|
|
5931
|
+
const errorMsg = `Failed to read resource "${resourcePath}" from skill "${name}": ${error instanceof Error ? error.message : String(error)}`;
|
|
5932
|
+
logger6.warn("Skill resource load failed \u2014 file not found or unreadable", {
|
|
5933
|
+
name,
|
|
5934
|
+
resourcePath,
|
|
5935
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5936
|
+
});
|
|
5937
|
+
return errorMsg;
|
|
5938
|
+
}
|
|
5939
|
+
}).build();
|
|
5940
|
+
}
|
|
5941
|
+
function createSkillActivationTools(registry) {
|
|
5942
|
+
return [
|
|
5943
|
+
createActivateSkillTool(registry),
|
|
5944
|
+
createReadSkillResourceTool(registry)
|
|
5945
|
+
];
|
|
5946
|
+
}
|
|
5947
|
+
function extractBody(content) {
|
|
5948
|
+
return (0, import_gray_matter2.default)(content).content.trim();
|
|
5949
|
+
}
|
|
5950
|
+
|
|
5951
|
+
// src/skills/registry.ts
|
|
5952
|
+
var import_node_path3 = require("path");
|
|
5953
|
+
var logger7 = createLogger("agentforge:core:skills:registry", { level: "info" /* INFO */ });
|
|
5954
|
+
var SkillRegistry = class {
|
|
5955
|
+
skills = /* @__PURE__ */ new Map();
|
|
5956
|
+
eventHandlers = /* @__PURE__ */ new Map();
|
|
5957
|
+
config;
|
|
5958
|
+
scanErrors = [];
|
|
5959
|
+
/** Maps resolved root paths → trust levels for skill trust assignment */
|
|
5960
|
+
rootTrustMap = /* @__PURE__ */ new Map();
|
|
5961
|
+
/**
|
|
5962
|
+
* Create a SkillRegistry and immediately scan configured roots for skills.
|
|
5963
|
+
*
|
|
5964
|
+
* @param config - Registry configuration with skill root paths
|
|
5965
|
+
*
|
|
5966
|
+
* @example
|
|
5967
|
+
* ```ts
|
|
5968
|
+
* const registry = new SkillRegistry({
|
|
5969
|
+
* skillRoots: ['.agentskills', '~/.agentskills', './project-skills'],
|
|
5970
|
+
* });
|
|
5971
|
+
* console.log(`Discovered ${registry.size()} skills`);
|
|
5972
|
+
* ```
|
|
5973
|
+
*/
|
|
5974
|
+
constructor(config) {
|
|
5975
|
+
this.config = config;
|
|
5976
|
+
this.discover();
|
|
5977
|
+
}
|
|
5978
|
+
/**
|
|
5979
|
+
* Scan all configured roots and populate the registry.
|
|
5980
|
+
*
|
|
5981
|
+
* Called automatically during construction. Can be called again
|
|
5982
|
+
* to re-scan (clears existing skills first).
|
|
5983
|
+
*/
|
|
5984
|
+
discover() {
|
|
5985
|
+
this.skills.clear();
|
|
5986
|
+
this.scanErrors = [];
|
|
5987
|
+
this.rootTrustMap.clear();
|
|
5988
|
+
const normalizedRoots = this.config.skillRoots.map(normalizeRootConfig);
|
|
5989
|
+
const plainPaths = normalizedRoots.map((r) => r.path);
|
|
5990
|
+
for (const root of normalizedRoots) {
|
|
5991
|
+
const resolvedPath = (0, import_node_path3.resolve)(expandHome(root.path));
|
|
5992
|
+
this.rootTrustMap.set(resolvedPath, root.trust);
|
|
5993
|
+
}
|
|
5994
|
+
const candidates = scanAllSkillRoots(plainPaths);
|
|
5995
|
+
let successCount = 0;
|
|
5996
|
+
let warningCount = 0;
|
|
5997
|
+
for (const candidate of candidates) {
|
|
5998
|
+
const result = parseSkillContent(candidate.content, candidate.dirName);
|
|
5999
|
+
if (!result.success) {
|
|
6000
|
+
warningCount++;
|
|
6001
|
+
this.scanErrors.push({
|
|
6002
|
+
path: candidate.skillPath,
|
|
6003
|
+
error: result.error || "Unknown parse error"
|
|
6004
|
+
});
|
|
6005
|
+
this.emit("skill:warning" /* SKILL_WARNING */, {
|
|
6006
|
+
skillPath: candidate.skillPath,
|
|
6007
|
+
rootPath: candidate.rootPath,
|
|
6008
|
+
error: result.error
|
|
6009
|
+
});
|
|
6010
|
+
logger7.warn("Skipping invalid skill", {
|
|
6011
|
+
skillPath: candidate.skillPath,
|
|
6012
|
+
error: result.error
|
|
6013
|
+
});
|
|
6014
|
+
continue;
|
|
6015
|
+
}
|
|
6016
|
+
const skill = {
|
|
6017
|
+
metadata: result.metadata,
|
|
6018
|
+
skillPath: candidate.skillPath,
|
|
6019
|
+
rootPath: candidate.rootPath,
|
|
6020
|
+
trustLevel: this.rootTrustMap.get(candidate.rootPath) ?? "untrusted"
|
|
6021
|
+
};
|
|
6022
|
+
if (this.skills.has(skill.metadata.name)) {
|
|
6023
|
+
const existing = this.skills.get(skill.metadata.name);
|
|
6024
|
+
warningCount++;
|
|
6025
|
+
const warningMsg = `Duplicate skill name "${skill.metadata.name}" from "${candidate.rootPath}" \u2014 keeping version from "${existing.rootPath}" (first root takes precedence)`;
|
|
6026
|
+
this.scanErrors.push({
|
|
6027
|
+
path: candidate.skillPath,
|
|
6028
|
+
error: warningMsg
|
|
6029
|
+
});
|
|
6030
|
+
this.emit("skill:warning" /* SKILL_WARNING */, {
|
|
6031
|
+
skillPath: candidate.skillPath,
|
|
6032
|
+
rootPath: candidate.rootPath,
|
|
6033
|
+
duplicateOf: existing.skillPath,
|
|
6034
|
+
error: warningMsg
|
|
6035
|
+
});
|
|
6036
|
+
logger7.warn("Duplicate skill name, keeping first", {
|
|
6037
|
+
name: skill.metadata.name,
|
|
6038
|
+
kept: existing.skillPath,
|
|
6039
|
+
skipped: candidate.skillPath
|
|
6040
|
+
});
|
|
6041
|
+
continue;
|
|
6042
|
+
}
|
|
6043
|
+
this.skills.set(skill.metadata.name, skill);
|
|
6044
|
+
successCount++;
|
|
6045
|
+
this.emit("skill:discovered" /* SKILL_DISCOVERED */, skill);
|
|
6046
|
+
logger7.debug("Skill discovered", {
|
|
6047
|
+
name: skill.metadata.name,
|
|
6048
|
+
description: skill.metadata.description.slice(0, 80),
|
|
6049
|
+
skillPath: skill.skillPath
|
|
6050
|
+
});
|
|
6051
|
+
}
|
|
6052
|
+
logger7.info("Skill registry populated", {
|
|
6053
|
+
rootsScanned: this.config.skillRoots.length,
|
|
6054
|
+
skillsDiscovered: successCount,
|
|
6055
|
+
warnings: warningCount
|
|
6056
|
+
});
|
|
6057
|
+
}
|
|
6058
|
+
// ─── Query API (parallel to ToolRegistry) ──────────────────────────────
|
|
6059
|
+
/**
|
|
6060
|
+
* Get a skill by name.
|
|
6061
|
+
*
|
|
6062
|
+
* @param name - The skill name
|
|
6063
|
+
* @returns The skill, or undefined if not found
|
|
6064
|
+
*
|
|
6065
|
+
* @example
|
|
6066
|
+
* ```ts
|
|
6067
|
+
* const skill = registry.get('code-review');
|
|
6068
|
+
* if (skill) {
|
|
6069
|
+
* console.log(skill.metadata.description);
|
|
6070
|
+
* }
|
|
6071
|
+
* ```
|
|
6072
|
+
*/
|
|
6073
|
+
get(name) {
|
|
6074
|
+
return this.skills.get(name);
|
|
6075
|
+
}
|
|
6076
|
+
/**
|
|
6077
|
+
* Get all discovered skills.
|
|
6078
|
+
*
|
|
6079
|
+
* @returns Array of all skills
|
|
6080
|
+
*
|
|
6081
|
+
* @example
|
|
6082
|
+
* ```ts
|
|
6083
|
+
* const allSkills = registry.getAll();
|
|
6084
|
+
* console.log(`Total skills: ${allSkills.length}`);
|
|
6085
|
+
* ```
|
|
6086
|
+
*/
|
|
6087
|
+
getAll() {
|
|
6088
|
+
return Array.from(this.skills.values());
|
|
6089
|
+
}
|
|
6090
|
+
/**
|
|
6091
|
+
* Check if a skill exists in the registry.
|
|
6092
|
+
*
|
|
6093
|
+
* @param name - The skill name
|
|
6094
|
+
* @returns True if the skill exists
|
|
6095
|
+
*
|
|
6096
|
+
* @example
|
|
6097
|
+
* ```ts
|
|
6098
|
+
* if (registry.has('code-review')) {
|
|
6099
|
+
* console.log('Skill available!');
|
|
6100
|
+
* }
|
|
6101
|
+
* ```
|
|
6102
|
+
*/
|
|
6103
|
+
has(name) {
|
|
6104
|
+
return this.skills.has(name);
|
|
6105
|
+
}
|
|
6106
|
+
/**
|
|
6107
|
+
* Get the number of discovered skills.
|
|
6108
|
+
*
|
|
6109
|
+
* @returns Number of skills in the registry
|
|
6110
|
+
*
|
|
6111
|
+
* @example
|
|
6112
|
+
* ```ts
|
|
6113
|
+
* console.log(`Registry has ${registry.size()} skills`);
|
|
6114
|
+
* ```
|
|
6115
|
+
*/
|
|
6116
|
+
size() {
|
|
6117
|
+
return this.skills.size;
|
|
6118
|
+
}
|
|
6119
|
+
/**
|
|
6120
|
+
* Get all skill names.
|
|
6121
|
+
*
|
|
6122
|
+
* @returns Array of skill names
|
|
6123
|
+
*/
|
|
6124
|
+
getNames() {
|
|
6125
|
+
return Array.from(this.skills.keys());
|
|
6126
|
+
}
|
|
6127
|
+
/**
|
|
6128
|
+
* Get errors/warnings from the last scan.
|
|
6129
|
+
*
|
|
6130
|
+
* Useful for diagnostics and observability.
|
|
6131
|
+
*
|
|
6132
|
+
* @returns Array of scan errors with paths
|
|
6133
|
+
*/
|
|
6134
|
+
getScanErrors() {
|
|
6135
|
+
return this.scanErrors;
|
|
6136
|
+
}
|
|
6137
|
+
/**
|
|
6138
|
+
* Check whether untrusted script access is allowed via config override.
|
|
6139
|
+
*
|
|
6140
|
+
* Used by activation tools to pass the override flag to trust policy checks.
|
|
6141
|
+
*
|
|
6142
|
+
* @returns True if `allowUntrustedScripts` is set in config
|
|
6143
|
+
*/
|
|
6144
|
+
getAllowUntrustedScripts() {
|
|
6145
|
+
return this.config.allowUntrustedScripts ?? false;
|
|
6146
|
+
}
|
|
6147
|
+
/**
|
|
6148
|
+
* Get the `allowed-tools` list for a skill.
|
|
6149
|
+
*
|
|
6150
|
+
* Returns the `allowedTools` array from the skill's frontmatter metadata,
|
|
6151
|
+
* enabling agents to filter their tool set based on what the skill expects.
|
|
6152
|
+
*
|
|
6153
|
+
* @param name - The skill name
|
|
6154
|
+
* @returns Array of allowed tool names, or undefined if skill not found or field not set
|
|
6155
|
+
*
|
|
6156
|
+
* @example
|
|
6157
|
+
* ```ts
|
|
6158
|
+
* const allowed = registry.getAllowedTools('code-review');
|
|
6159
|
+
* if (allowed) {
|
|
6160
|
+
* const filteredTools = allTools.filter(t => allowed.includes(t.name));
|
|
6161
|
+
* }
|
|
6162
|
+
* ```
|
|
6163
|
+
*/
|
|
6164
|
+
getAllowedTools(name) {
|
|
6165
|
+
const skill = this.skills.get(name);
|
|
6166
|
+
return skill?.metadata.allowedTools;
|
|
6167
|
+
}
|
|
6168
|
+
// ─── Prompt Generation ─────────────────────────────────────────────────
|
|
6169
|
+
/**
|
|
6170
|
+
* Generate an `<available_skills>` XML block for system prompt injection.
|
|
6171
|
+
*
|
|
6172
|
+
* Returns an empty string when:
|
|
6173
|
+
* - `config.enabled` is `false` (default) — agents operate with unmodified prompts
|
|
6174
|
+
* - No skills match the filter criteria
|
|
6175
|
+
*
|
|
6176
|
+
* The output composes naturally with `toolRegistry.generatePrompt()` —
|
|
6177
|
+
* simply concatenate both into the system prompt.
|
|
6178
|
+
*
|
|
6179
|
+
* @param options - Optional filtering (subset of skill names)
|
|
6180
|
+
* @returns XML string or empty string
|
|
6181
|
+
*
|
|
6182
|
+
* @example
|
|
6183
|
+
* ```ts
|
|
6184
|
+
* // All skills
|
|
6185
|
+
* const xml = registry.generatePrompt();
|
|
6186
|
+
*
|
|
6187
|
+
* // Subset for a focused agent
|
|
6188
|
+
* const xml = registry.generatePrompt({ skills: ['code-review', 'testing'] });
|
|
6189
|
+
*
|
|
6190
|
+
* // Compose with tool prompt
|
|
6191
|
+
* const systemPrompt = [
|
|
6192
|
+
* toolRegistry.generatePrompt(),
|
|
6193
|
+
* skillRegistry.generatePrompt(),
|
|
6194
|
+
* ].filter(Boolean).join('\n\n');
|
|
6195
|
+
* ```
|
|
6196
|
+
*/
|
|
6197
|
+
generatePrompt(options) {
|
|
6198
|
+
if (!this.config.enabled) {
|
|
6199
|
+
logger7.debug("Skill prompt generation skipped (disabled)", {
|
|
6200
|
+
enabled: this.config.enabled ?? false
|
|
6201
|
+
});
|
|
6202
|
+
return "";
|
|
6203
|
+
}
|
|
6204
|
+
let skills = this.getAll();
|
|
6205
|
+
if (options?.skills && options.skills.length > 0) {
|
|
6206
|
+
const requested = new Set(options.skills);
|
|
6207
|
+
skills = skills.filter((s) => requested.has(s.metadata.name));
|
|
6208
|
+
}
|
|
6209
|
+
if (this.config.maxDiscoveredSkills !== void 0 && this.config.maxDiscoveredSkills >= 0) {
|
|
6210
|
+
skills = skills.slice(0, this.config.maxDiscoveredSkills);
|
|
6211
|
+
}
|
|
6212
|
+
if (skills.length === 0) {
|
|
6213
|
+
logger7.debug("Skill prompt generation produced empty result", {
|
|
6214
|
+
totalDiscovered: this.size(),
|
|
6215
|
+
filterApplied: !!(options?.skills && options.skills.length > 0),
|
|
6216
|
+
maxCap: this.config.maxDiscoveredSkills
|
|
6217
|
+
});
|
|
6218
|
+
return "";
|
|
6219
|
+
}
|
|
6220
|
+
const skillEntries = skills.map((skill) => {
|
|
6221
|
+
const lines = [
|
|
6222
|
+
" <skill>",
|
|
6223
|
+
` <name>${escapeXml(skill.metadata.name)}</name>`,
|
|
6224
|
+
` <description>${escapeXml(skill.metadata.description)}</description>`,
|
|
6225
|
+
` <location>${escapeXml(skill.skillPath)}</location>`,
|
|
6226
|
+
" </skill>"
|
|
6227
|
+
];
|
|
6228
|
+
return lines.join("\n");
|
|
6229
|
+
});
|
|
6230
|
+
const xml = `<available_skills>
|
|
6231
|
+
${skillEntries.join("\n")}
|
|
6232
|
+
</available_skills>`;
|
|
6233
|
+
const estimatedTokens = Math.ceil(xml.length / 4);
|
|
6234
|
+
logger7.info("Skill prompt generated", {
|
|
6235
|
+
skillCount: skills.length,
|
|
6236
|
+
totalDiscovered: this.size(),
|
|
6237
|
+
filterApplied: !!(options?.skills && options.skills.length > 0),
|
|
6238
|
+
maxCap: this.config.maxDiscoveredSkills,
|
|
6239
|
+
estimatedTokens,
|
|
6240
|
+
xmlLength: xml.length
|
|
6241
|
+
});
|
|
6242
|
+
return xml;
|
|
6243
|
+
}
|
|
6244
|
+
// ─── Event System ──────────────────────────────────────────────────────
|
|
6245
|
+
/**
|
|
6246
|
+
* Register an event handler.
|
|
6247
|
+
*
|
|
6248
|
+
* @param event - The event to listen for
|
|
6249
|
+
* @param handler - The handler function
|
|
6250
|
+
*
|
|
6251
|
+
* @example
|
|
6252
|
+
* ```ts
|
|
6253
|
+
* registry.on(SkillRegistryEvent.SKILL_DISCOVERED, (skill) => {
|
|
6254
|
+
* console.log('Found skill:', skill.metadata.name);
|
|
6255
|
+
* });
|
|
6256
|
+
* ```
|
|
6257
|
+
*/
|
|
6258
|
+
on(event, handler) {
|
|
6259
|
+
if (!this.eventHandlers.has(event)) {
|
|
6260
|
+
this.eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
6261
|
+
}
|
|
6262
|
+
this.eventHandlers.get(event).add(handler);
|
|
6263
|
+
}
|
|
6264
|
+
/**
|
|
6265
|
+
* Unregister an event handler.
|
|
6266
|
+
*
|
|
6267
|
+
* @param event - The event to stop listening for
|
|
6268
|
+
* @param handler - The handler function to remove
|
|
6269
|
+
*/
|
|
6270
|
+
off(event, handler) {
|
|
6271
|
+
const handlers = this.eventHandlers.get(event);
|
|
6272
|
+
if (handlers) {
|
|
6273
|
+
handlers.delete(handler);
|
|
6274
|
+
}
|
|
6275
|
+
}
|
|
6276
|
+
/**
|
|
6277
|
+
* Emit an event to all registered handlers.
|
|
6278
|
+
*
|
|
6279
|
+
* @param event - The event to emit
|
|
6280
|
+
* @param data - The event data
|
|
6281
|
+
* @private
|
|
6282
|
+
*/
|
|
6283
|
+
emit(event, data) {
|
|
6284
|
+
const handlers = this.eventHandlers.get(event);
|
|
6285
|
+
if (handlers) {
|
|
6286
|
+
handlers.forEach((handler) => {
|
|
6287
|
+
try {
|
|
6288
|
+
handler(data);
|
|
6289
|
+
} catch (error) {
|
|
6290
|
+
logger7.error("Skill event handler error", {
|
|
6291
|
+
event,
|
|
6292
|
+
error: error instanceof Error ? error.message : String(error),
|
|
6293
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
6294
|
+
});
|
|
6295
|
+
}
|
|
6296
|
+
});
|
|
6297
|
+
}
|
|
6298
|
+
}
|
|
6299
|
+
/**
|
|
6300
|
+
* Emit an event (public API for activation tools).
|
|
6301
|
+
*
|
|
6302
|
+
* Used by skill activation tools to emit `skill:activated` and
|
|
6303
|
+
* `skill:resource-loaded` events through the registry's event system.
|
|
6304
|
+
*
|
|
6305
|
+
* @param event - The event to emit
|
|
6306
|
+
* @param data - The event data
|
|
6307
|
+
*/
|
|
6308
|
+
emitEvent(event, data) {
|
|
6309
|
+
this.emit(event, data);
|
|
6310
|
+
}
|
|
6311
|
+
// ─── Tool Integration ────────────────────────────────────────────────
|
|
6312
|
+
/**
|
|
6313
|
+
* Create activation tools pre-wired to this registry instance.
|
|
6314
|
+
*
|
|
6315
|
+
* Returns `activate-skill` and `read-skill-resource` tools that
|
|
6316
|
+
* agents can use to load skill instructions and resources on demand.
|
|
6317
|
+
*
|
|
6318
|
+
* @returns Array of [activate-skill, read-skill-resource] tools
|
|
6319
|
+
*
|
|
6320
|
+
* @example
|
|
6321
|
+
* ```ts
|
|
6322
|
+
* const agent = createReActAgent({
|
|
6323
|
+
* model: llm,
|
|
6324
|
+
* tools: [
|
|
6325
|
+
* ...toolRegistry.toLangChainTools(),
|
|
6326
|
+
* ...skillRegistry.toActivationTools(),
|
|
6327
|
+
* ],
|
|
6328
|
+
* });
|
|
6329
|
+
* ```
|
|
6330
|
+
*/
|
|
6331
|
+
toActivationTools() {
|
|
6332
|
+
return createSkillActivationTools(this);
|
|
6333
|
+
}
|
|
6334
|
+
};
|
|
6335
|
+
function escapeXml(str) {
|
|
6336
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6337
|
+
}
|
|
5485
6338
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5486
6339
|
0 && (module.exports = {
|
|
5487
6340
|
AgentError,
|
|
@@ -5501,6 +6354,8 @@ function loadPrompt(promptName, options = {}, promptsDir) {
|
|
|
5501
6354
|
MissingDescriptionError,
|
|
5502
6355
|
Profiler,
|
|
5503
6356
|
RegistryEvent,
|
|
6357
|
+
SkillRegistry,
|
|
6358
|
+
SkillRegistryEvent,
|
|
5504
6359
|
TimeoutError,
|
|
5505
6360
|
ToolBuilder,
|
|
5506
6361
|
ToolCategory,
|
|
@@ -5510,6 +6365,7 @@ function loadPrompt(promptName, options = {}, promptsDir) {
|
|
|
5510
6365
|
ToolNameSchema,
|
|
5511
6366
|
ToolRegistry,
|
|
5512
6367
|
ToolRelationsSchema,
|
|
6368
|
+
TrustPolicyReason,
|
|
5513
6369
|
batch,
|
|
5514
6370
|
broadcast,
|
|
5515
6371
|
cache,
|
|
@@ -5523,6 +6379,7 @@ function loadPrompt(promptName, options = {}, promptsDir) {
|
|
|
5523
6379
|
composeWithOptions,
|
|
5524
6380
|
conditional,
|
|
5525
6381
|
configureLangSmith,
|
|
6382
|
+
createActivateSkillTool,
|
|
5526
6383
|
createAlertManager,
|
|
5527
6384
|
createApprovalRequiredInterrupt,
|
|
5528
6385
|
createAuditLogger,
|
|
@@ -5551,11 +6408,13 @@ function loadPrompt(promptName, options = {}, promptsDir) {
|
|
|
5551
6408
|
createParallelWorkflow,
|
|
5552
6409
|
createProfiler,
|
|
5553
6410
|
createProgressTracker,
|
|
6411
|
+
createReadSkillResourceTool,
|
|
5554
6412
|
createSSEFormatter,
|
|
5555
6413
|
createSequentialWorkflow,
|
|
5556
6414
|
createSharedCache,
|
|
5557
6415
|
createSharedConcurrencyController,
|
|
5558
6416
|
createSharedRateLimiter,
|
|
6417
|
+
createSkillActivationTools,
|
|
5559
6418
|
createSqliteCheckpointer,
|
|
5560
6419
|
createStateAnnotation,
|
|
5561
6420
|
createSubgraph,
|
|
@@ -5566,6 +6425,8 @@ function loadPrompt(promptName, options = {}, promptsDir) {
|
|
|
5566
6425
|
createToolUnsafe,
|
|
5567
6426
|
createWebSocketHandler,
|
|
5568
6427
|
development,
|
|
6428
|
+
evaluateTrustPolicy,
|
|
6429
|
+
expandHome,
|
|
5569
6430
|
filter,
|
|
5570
6431
|
formatAgentResumedEvent,
|
|
5571
6432
|
formatAgentWaitingEvent,
|
|
@@ -5585,20 +6446,26 @@ function loadPrompt(promptName, options = {}, promptsDir) {
|
|
|
5585
6446
|
isCustomInterrupt,
|
|
5586
6447
|
isHumanRequestInterrupt,
|
|
5587
6448
|
isMemoryCheckpointer,
|
|
6449
|
+
isScriptResource,
|
|
5588
6450
|
isTracingEnabled,
|
|
5589
6451
|
loadPrompt,
|
|
5590
6452
|
map,
|
|
5591
6453
|
merge,
|
|
5592
6454
|
mergeState,
|
|
6455
|
+
normalizeRootConfig,
|
|
5593
6456
|
parallel,
|
|
5594
6457
|
parseSSEEvent,
|
|
6458
|
+
parseSkillContent,
|
|
5595
6459
|
presets,
|
|
5596
6460
|
production,
|
|
5597
6461
|
reduce,
|
|
5598
6462
|
renderTemplate,
|
|
6463
|
+
resolveResourcePath,
|
|
5599
6464
|
retry,
|
|
5600
6465
|
safeValidateSchemaDescriptions,
|
|
5601
6466
|
sanitizeValue,
|
|
6467
|
+
scanAllSkillRoots,
|
|
6468
|
+
scanSkillRoot,
|
|
5602
6469
|
sendMessage,
|
|
5603
6470
|
sequential,
|
|
5604
6471
|
sequentialBuilder,
|
|
@@ -5610,6 +6477,9 @@ function loadPrompt(promptName, options = {}, promptsDir) {
|
|
|
5610
6477
|
toLangChainTools,
|
|
5611
6478
|
toolBuilder,
|
|
5612
6479
|
validateSchemaDescriptions,
|
|
6480
|
+
validateSkillDescription,
|
|
6481
|
+
validateSkillName,
|
|
6482
|
+
validateSkillNameMatchesDir,
|
|
5613
6483
|
validateState,
|
|
5614
6484
|
validateTool,
|
|
5615
6485
|
validateToolMetadata,
|