@chatbotkit/agent 1.27.0 → 1.28.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/README.md CHANGED
@@ -9,20 +9,6 @@
9
9
 
10
10
  Build autonomous AI agents that can use custom tools and execute complex tasks with the full power of the ChatBotKit platform.
11
11
 
12
- ## Why ChatBotKit?
13
-
14
- **Build lighter, future-proof AI agents.** When you build with ChatBotKit, the heavy lifting happens on our servers—not in your application. This architectural advantage delivers:
15
-
16
- - 🪶 **Lightweight Agents**: Your agents stay lean because complex AI processing, model orchestration, and tool execution happen server-side. Less code in your app means faster load times and simpler maintenance.
17
-
18
- - 🛡️ **Robust & Streamlined**: Server-side processing provides a more reliable experience with built-in error handling, automatic retries, and consistent behavior across all platforms.
19
-
20
- - 🔄 **Backward & Forward Compatible**: As AI technology evolves—new models, new capabilities, new paradigms—your agents automatically benefit. No code changes required on your end.
21
-
22
- - 🔮 **Future-Proof**: Agents you build today will remain capable tomorrow. When we add support for new AI models or capabilities, your existing agents gain those powers without any updates to your codebase.
23
-
24
- This means you can focus on building great user experiences while ChatBotKit handles the complexity of the ever-changing AI landscape.
25
-
26
12
  ## Installation
27
13
 
28
14
  ```bash
@@ -192,59 +178,6 @@ The `execute` mode provides system tools for task management:
192
178
  - **`progress`** - Track completion status and blockers
193
179
  - **`exit`** - Signal task completion with status code
194
180
 
195
- ### Skills Loading
196
-
197
- Load skills from local directories and pass them as a feature to the agent. Skills are defined using `SKILL.md` files with front matter containing name and description.
198
-
199
- ```javascript
200
- import { execute, loadSkills, createSkillsFeature } from '@chatbotkit/agent'
201
- import { ChatBotKit } from '@chatbotkit/sdk'
202
-
203
- const client = new ChatBotKit({ secret: process.env.CHATBOTKIT_API_TOKEN })
204
-
205
- // Load skills from directories
206
- const skillsResult = await loadSkills(['./skills'], { watch: true })
207
-
208
- // Create the skills feature for the API
209
- const skillsFeature = createSkillsFeature(skillsResult.skills)
210
-
211
- const stream = execute({
212
- client,
213
- model: 'gpt-4o',
214
- messages: [{ type: 'user', text: 'Help me with my task' }],
215
- extensions: {
216
- features: [skillsFeature],
217
- },
218
- })
219
-
220
- for await (const event of stream) {
221
- // Handle events
222
- }
223
-
224
- // Clean up when done
225
- skillsResult.close()
226
- ```
227
-
228
- #### SKILL.md Format
229
-
230
- Create a `SKILL.md` file in each skill directory:
231
-
232
- ```markdown
233
- ---
234
- name: My Skill
235
- description: A brief description of what this skill does
236
- ---
237
-
238
- # My Skill
239
-
240
- Additional documentation for the skill...
241
- ```
242
-
243
- #### Skills API
244
-
245
- - **`loadSkills(directories, options)`** - Load skills from directories containing SKILL.md files
246
- - **`createSkillsFeature(skills)`** - Create a feature configuration for the API
247
-
248
181
  ## Documentation
249
182
 
250
183
  For comprehensive information about the ChatBotKit Agent SDK, including detailed documentation on its functionalities, helper methods, and configuration options, please visit our [type documentation page](https://chatbotkit.github.io/node-sdk/modules/_chatbotkit_agent.html).
@@ -5,7 +5,7 @@ exports.execute = execute;
5
5
  const zod_1 = require("zod");
6
6
  const zod_to_json_schema_1 = require("zod-to-json-schema");
7
7
  async function* complete(options) {
8
- const { client, tools, abortSignal, ...request } = options;
8
+ const { client, tools, ...request } = options;
9
9
  const channelToTool = new Map();
10
10
  const functions = tools
11
11
  ? Object.entries(tools).map(([name, tool]) => {
@@ -47,13 +47,10 @@ async function* complete(options) {
47
47
  .complete(null, {
48
48
  ...request,
49
49
  functions,
50
- limits: {
51
- iterations: 1,
52
- },
53
50
  })
54
- .stream({ abortSignal });
51
+ .stream();
55
52
  const toolEventQueue = [];
56
- const runningToolPromises = [];
53
+ const runningTools = new Set();
57
54
  const executeToolAsync = async (channel, name, tool, args) => {
58
55
  try {
59
56
  let parsedArgs = args;
@@ -91,11 +88,11 @@ async function* complete(options) {
91
88
  message: { error: errorMessage },
92
89
  });
93
90
  }
91
+ finally {
92
+ runningTools.delete(channel);
93
+ }
94
94
  };
95
95
  for await (const event of stream) {
96
- if (abortSignal?.aborted) {
97
- break;
98
- }
99
96
  while (toolEventQueue.length > 0) {
100
97
  const toolEvent = toolEventQueue.shift();
101
98
  if (toolEvent) {
@@ -119,7 +116,9 @@ async function* complete(options) {
119
116
  },
120
117
  };
121
118
  const toolPromise = executeToolAsync(channel, name, tool, args);
122
- runningToolPromises.push(toolPromise);
119
+ runningTools.add(channel);
120
+ toolPromise.catch(() => {
121
+ });
123
122
  }
124
123
  }
125
124
  yield event;
@@ -130,8 +129,8 @@ async function* complete(options) {
130
129
  yield toolEvent;
131
130
  }
132
131
  }
133
- if (runningToolPromises.length > 0) {
134
- await Promise.allSettled(runningToolPromises);
132
+ while (runningTools.size > 0) {
133
+ await new Promise((resolve) => setTimeout(resolve, 10));
135
134
  while (toolEventQueue.length > 0) {
136
135
  const toolEvent = toolEventQueue.shift();
137
136
  if (toolEvent) {
@@ -141,10 +140,9 @@ async function* complete(options) {
141
140
  }
142
141
  }
143
142
  async function* execute(options) {
144
- const { client, tools = {}, maxIterations = 100, abortSignal, ...request } = options;
145
- const messages = request.messages || [];
143
+ const { client, tools = {}, maxIterations = 50, ...request } = options;
144
+ const messages = [...(request.messages || [])];
146
145
  let exitResult = null;
147
- let internalAbort = null;
148
146
  const systemTools = {
149
147
  plan: {
150
148
  description: 'Create or update a plan for approaching the task. Break down the task into clear, actionable steps. Use this at the start and whenever you need to revise your approach.',
@@ -211,29 +209,8 @@ async function* execute(options) {
211
209
  };
212
210
  },
213
211
  },
214
- abort: {
215
- description: 'Immediately abort the current task. Use this when the user explicitly asks you to stop, cancel, or abort what you are doing. Set hard to true to kill running processes immediately.',
216
- input: zod_1.z.object({
217
- reason: zod_1.z.string().optional().describe('Brief reason for aborting'),
218
- hard: zod_1.z
219
- .boolean()
220
- .optional()
221
- .describe('If true, immediately kill running processes. If false (default), finish the current operation gracefully.'),
222
- }),
223
- handler: async (input) => {
224
- const reason = input.reason || 'aborted by user request';
225
- exitResult = { code: 1, message: reason };
226
- if (input.hard && internalAbort) {
227
- internalAbort.abort(reason);
228
- }
229
- return {
230
- success: true,
231
- message: `Task aborted: ${reason}`,
232
- };
233
- },
234
- },
235
212
  };
236
- const allTools = { ...tools, ...systemTools };
213
+ const allTools = { ...systemTools, ...tools };
237
214
  const systemInstruction = `
238
215
  ${options.extensions?.backstory || ''}
239
216
 
@@ -245,58 +222,31 @@ The goal is to complete the assigned task efficiently and effectively. Follow th
245
222
  2. **Track Progress**: Regularly use the 'progress' function to update status and identify issues
246
223
  3. **Use Tools**: Leverage available tools to accomplish each step of your plan
247
224
  4. **Exit When Done**: Call the 'exit' function with code 0 when successful, or non-zero code if unable to complete
248
- 5. **Abort**: If the user asks you to stop, cancel, or abort, call the 'abort' function immediately. Use hard=true if processes are running that need to be killed right away.
249
- 6. **Be Autonomous**: Work through the task systematically without waiting for additional input
250
- 7. **Be Responsive**: If the user sends a new message while you are working, acknowledge it briefly and adjust your approach if needed. Always prioritize user input over your current plan.
225
+ 5. **Be Autonomous**: Work through the task systematically without waiting for additional input
251
226
  `.trim();
252
227
  let iteration = 0;
253
228
  while (iteration < maxIterations && exitResult === null) {
254
- if (abortSignal?.aborted) {
255
- exitResult = {
256
- code: 1,
257
- message: 'Task execution aborted',
258
- };
259
- break;
260
- }
261
229
  iteration++;
262
230
  yield { type: 'iteration', data: { iteration } };
263
- let lastEndReason = null;
264
- internalAbort = new AbortController();
265
- if (abortSignal?.aborted) {
266
- internalAbort.abort(abortSignal.reason);
267
- }
268
- else if (abortSignal) {
269
- const capturedAbort = internalAbort;
270
- abortSignal.addEventListener('abort', () => capturedAbort.abort(abortSignal.reason), { once: true });
271
- }
272
- const iterSignal = internalAbort.signal;
273
231
  for await (const event of complete({
274
232
  ...request,
275
233
  client,
276
234
  messages,
277
235
  tools: allTools,
278
- abortSignal: iterSignal,
279
236
  extensions: {
280
237
  ...options.extensions,
281
238
  backstory: systemInstruction,
282
239
  },
283
240
  })) {
284
- if (event.type === 'message') {
285
- messages.push(event.data);
286
- }
287
- if (event.type === 'result') {
288
- if (event.data.end.reason) {
289
- lastEndReason = event.data.end.reason;
290
- }
291
- }
292
241
  yield event;
293
242
  }
294
243
  if (exitResult) {
295
244
  break;
296
245
  }
297
- if (lastEndReason === 'stop') {
298
- break;
299
- }
246
+ messages.push({
247
+ type: 'user',
248
+ text: 'Continue with the next step of your plan. If all steps are complete, call exit with the appropriate status code.',
249
+ });
300
250
  }
301
251
  if (exitResult === null) {
302
252
  exitResult = {
@@ -1,13 +1,11 @@
1
- export function complete(options: Omit<ConversationCompleteRequest, "functions" | "limits"> & {
1
+ export function complete(options: ConversationCompleteRequest & {
2
2
  client: ChatBotKit;
3
3
  tools?: Tools;
4
- abortSignal?: AbortSignal;
5
4
  }): AsyncGenerator<ConversationCompleteStreamType | ToolCallStartEvent | ToolCallEndEvent | ToolCallErrorEvent, void, unknown>;
6
- export function execute(options: Omit<ConversationCompleteRequest, "functions" | "limits"> & {
5
+ export function execute(options: ConversationCompleteRequest & {
7
6
  client: ChatBotKit;
8
7
  tools?: Tools;
9
8
  maxIterations?: number;
10
- abortSignal?: AbortSignal;
11
9
  }): AsyncGenerator<ConversationCompleteStreamType | ToolCallStartEvent | ToolCallEndEvent | ToolCallErrorEvent | IterationEvent | ExitEvent, void, unknown>;
12
10
  export type ZodObject = import("zod").ZodObject<any>;
13
11
  export type ChatBotKit = import("@chatbotkit/sdk").ChatBotKit;
@@ -1,11 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createSkillsFeature = exports.loadSkills = exports.tools = exports.execute = exports.complete = void 0;
3
+ exports.tools = exports.execute = exports.complete = void 0;
4
4
  var agent_js_1 = require("./agent.cjs");
5
5
  Object.defineProperty(exports, "complete", { enumerable: true, get: function () { return agent_js_1.complete; } });
6
6
  Object.defineProperty(exports, "execute", { enumerable: true, get: function () { return agent_js_1.execute; } });
7
7
  var tools_js_1 = require("./tools.cjs");
8
8
  Object.defineProperty(exports, "tools", { enumerable: true, get: function () { return tools_js_1.tools; } });
9
- var skills_js_1 = require("./skills.cjs");
10
- Object.defineProperty(exports, "loadSkills", { enumerable: true, get: function () { return skills_js_1.loadSkills; } });
11
- Object.defineProperty(exports, "createSkillsFeature", { enumerable: true, get: function () { return skills_js_1.createSkillsFeature; } });
@@ -1,3 +1,2 @@
1
1
  export { tools } from "./tools.js";
2
2
  export { complete, execute } from "./agent.js";
3
- export { loadSkills, createSkillsFeature } from "./skills.js";
@@ -1,13 +1,11 @@
1
- export function complete(options: Omit<ConversationCompleteRequest, "functions" | "limits"> & {
1
+ export function complete(options: ConversationCompleteRequest & {
2
2
  client: ChatBotKit;
3
3
  tools?: Tools;
4
- abortSignal?: AbortSignal;
5
4
  }): AsyncGenerator<ConversationCompleteStreamType | ToolCallStartEvent | ToolCallEndEvent | ToolCallErrorEvent, void, unknown>;
6
- export function execute(options: Omit<ConversationCompleteRequest, "functions" | "limits"> & {
5
+ export function execute(options: ConversationCompleteRequest & {
7
6
  client: ChatBotKit;
8
7
  tools?: Tools;
9
8
  maxIterations?: number;
10
- abortSignal?: AbortSignal;
11
9
  }): AsyncGenerator<ConversationCompleteStreamType | ToolCallStartEvent | ToolCallEndEvent | ToolCallErrorEvent | IterationEvent | ExitEvent, void, unknown>;
12
10
  export type ZodObject = import("zod").ZodObject<any>;
13
11
  export type ChatBotKit = import("@chatbotkit/sdk").ChatBotKit;
package/dist/esm/agent.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { zodToJsonSchema } from 'zod-to-json-schema';
3
3
  export async function* complete(options) {
4
- const { client, tools, abortSignal, ...request } = options;
4
+ const { client, tools, ...request } = options;
5
5
  const channelToTool = new Map();
6
6
  const functions = tools
7
7
  ? Object.entries(tools).map(([name, tool]) => {
@@ -43,13 +43,10 @@ export async function* complete(options) {
43
43
  .complete(null, {
44
44
  ...request,
45
45
  functions,
46
- limits: {
47
- iterations: 1,
48
- },
49
46
  })
50
- .stream({ abortSignal });
47
+ .stream();
51
48
  const toolEventQueue = [];
52
- const runningToolPromises = [];
49
+ const runningTools = new Set();
53
50
  const executeToolAsync = async (channel, name, tool, args) => {
54
51
  try {
55
52
  let parsedArgs = args;
@@ -87,11 +84,11 @@ export async function* complete(options) {
87
84
  message: { error: errorMessage },
88
85
  });
89
86
  }
87
+ finally {
88
+ runningTools.delete(channel);
89
+ }
90
90
  };
91
91
  for await (const event of stream) {
92
- if (abortSignal?.aborted) {
93
- break;
94
- }
95
92
  while (toolEventQueue.length > 0) {
96
93
  const toolEvent = toolEventQueue.shift();
97
94
  if (toolEvent) {
@@ -115,7 +112,9 @@ export async function* complete(options) {
115
112
  },
116
113
  };
117
114
  const toolPromise = executeToolAsync(channel, name, tool, args);
118
- runningToolPromises.push(toolPromise);
115
+ runningTools.add(channel);
116
+ toolPromise.catch(() => {
117
+ });
119
118
  }
120
119
  }
121
120
  yield event;
@@ -126,8 +125,8 @@ export async function* complete(options) {
126
125
  yield toolEvent;
127
126
  }
128
127
  }
129
- if (runningToolPromises.length > 0) {
130
- await Promise.allSettled(runningToolPromises);
128
+ while (runningTools.size > 0) {
129
+ await new Promise((resolve) => setTimeout(resolve, 10));
131
130
  while (toolEventQueue.length > 0) {
132
131
  const toolEvent = toolEventQueue.shift();
133
132
  if (toolEvent) {
@@ -137,10 +136,9 @@ export async function* complete(options) {
137
136
  }
138
137
  }
139
138
  export async function* execute(options) {
140
- const { client, tools = {}, maxIterations = 100, abortSignal, ...request } = options;
141
- const messages = request.messages || [];
139
+ const { client, tools = {}, maxIterations = 50, ...request } = options;
140
+ const messages = [...(request.messages || [])];
142
141
  let exitResult = null;
143
- let internalAbort = null;
144
142
  const systemTools = {
145
143
  plan: {
146
144
  description: 'Create or update a plan for approaching the task. Break down the task into clear, actionable steps. Use this at the start and whenever you need to revise your approach.',
@@ -207,29 +205,8 @@ export async function* execute(options) {
207
205
  };
208
206
  },
209
207
  },
210
- abort: {
211
- description: 'Immediately abort the current task. Use this when the user explicitly asks you to stop, cancel, or abort what you are doing. Set hard to true to kill running processes immediately.',
212
- input: z.object({
213
- reason: z.string().optional().describe('Brief reason for aborting'),
214
- hard: z
215
- .boolean()
216
- .optional()
217
- .describe('If true, immediately kill running processes. If false (default), finish the current operation gracefully.'),
218
- }),
219
- handler: async (input) => {
220
- const reason = input.reason || 'aborted by user request';
221
- exitResult = { code: 1, message: reason };
222
- if (input.hard && internalAbort) {
223
- internalAbort.abort(reason);
224
- }
225
- return {
226
- success: true,
227
- message: `Task aborted: ${reason}`,
228
- };
229
- },
230
- },
231
208
  };
232
- const allTools = { ...tools, ...systemTools };
209
+ const allTools = { ...systemTools, ...tools };
233
210
  const systemInstruction = `
234
211
  ${options.extensions?.backstory || ''}
235
212
 
@@ -241,58 +218,31 @@ The goal is to complete the assigned task efficiently and effectively. Follow th
241
218
  2. **Track Progress**: Regularly use the 'progress' function to update status and identify issues
242
219
  3. **Use Tools**: Leverage available tools to accomplish each step of your plan
243
220
  4. **Exit When Done**: Call the 'exit' function with code 0 when successful, or non-zero code if unable to complete
244
- 5. **Abort**: If the user asks you to stop, cancel, or abort, call the 'abort' function immediately. Use hard=true if processes are running that need to be killed right away.
245
- 6. **Be Autonomous**: Work through the task systematically without waiting for additional input
246
- 7. **Be Responsive**: If the user sends a new message while you are working, acknowledge it briefly and adjust your approach if needed. Always prioritize user input over your current plan.
221
+ 5. **Be Autonomous**: Work through the task systematically without waiting for additional input
247
222
  `.trim();
248
223
  let iteration = 0;
249
224
  while (iteration < maxIterations && exitResult === null) {
250
- if (abortSignal?.aborted) {
251
- exitResult = {
252
- code: 1,
253
- message: 'Task execution aborted',
254
- };
255
- break;
256
- }
257
225
  iteration++;
258
226
  yield { type: 'iteration', data: { iteration } };
259
- let lastEndReason = null;
260
- internalAbort = new AbortController();
261
- if (abortSignal?.aborted) {
262
- internalAbort.abort(abortSignal.reason);
263
- }
264
- else if (abortSignal) {
265
- const capturedAbort = internalAbort;
266
- abortSignal.addEventListener('abort', () => capturedAbort.abort(abortSignal.reason), { once: true });
267
- }
268
- const iterSignal = internalAbort.signal;
269
227
  for await (const event of complete({
270
228
  ...request,
271
229
  client,
272
230
  messages,
273
231
  tools: allTools,
274
- abortSignal: iterSignal,
275
232
  extensions: {
276
233
  ...options.extensions,
277
234
  backstory: systemInstruction,
278
235
  },
279
236
  })) {
280
- if (event.type === 'message') {
281
- messages.push(event.data);
282
- }
283
- if (event.type === 'result') {
284
- if (event.data.end.reason) {
285
- lastEndReason = event.data.end.reason;
286
- }
287
- }
288
237
  yield event;
289
238
  }
290
239
  if (exitResult) {
291
240
  break;
292
241
  }
293
- if (lastEndReason === 'stop') {
294
- break;
295
- }
242
+ messages.push({
243
+ type: 'user',
244
+ text: 'Continue with the next step of your plan. If all steps are complete, call exit with the appropriate status code.',
245
+ });
296
246
  }
297
247
  if (exitResult === null) {
298
248
  exitResult = {
@@ -1,3 +1,2 @@
1
1
  export { tools } from "./tools.js";
2
2
  export { complete, execute } from "./agent.js";
3
- export { loadSkills, createSkillsFeature } from "./skills.js";
package/dist/esm/index.js CHANGED
@@ -1,3 +1,2 @@
1
1
  export { complete, execute } from './agent.js';
2
2
  export { tools } from './tools.js';
3
- export { loadSkills, createSkillsFeature } from './skills.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatbotkit/agent",
3
- "version": "1.27.0",
3
+ "version": "1.28.0",
4
4
  "description": "ChatBotKit Agent implementation",
5
5
  "license": "ISC",
6
6
  "engines": {
@@ -72,26 +72,6 @@
72
72
  "default": "./dist/cjs/index.cjs"
73
73
  }
74
74
  },
75
- "./skills": {
76
- "import": {
77
- "types": "./dist/esm/skills.d.ts",
78
- "default": "./dist/esm/skills.js"
79
- },
80
- "require": {
81
- "types": "./dist/cjs/skills.d.ts",
82
- "default": "./dist/cjs/skills.cjs"
83
- }
84
- },
85
- "./skills.js": {
86
- "import": {
87
- "types": "./dist/esm/skills.d.ts",
88
- "default": "./dist/esm/skills.js"
89
- },
90
- "require": {
91
- "types": "./dist/cjs/skills.d.ts",
92
- "default": "./dist/cjs/skills.cjs"
93
- }
94
- },
95
75
  "./tools": {
96
76
  "import": {
97
77
  "types": "./dist/esm/tools.d.ts",
@@ -116,14 +96,12 @@
116
96
  },
117
97
  "types": "./dist/cjs/index.d.ts",
118
98
  "dependencies": {
119
- "js-yaml": "^4.1.0",
120
99
  "tslib": "^2.6.2",
121
100
  "zod": "^3.25.76",
122
101
  "zod-to-json-schema": "^3.24.6",
123
- "@chatbotkit/sdk": "1.27.0"
102
+ "@chatbotkit/sdk": "1.28.0"
124
103
  },
125
104
  "devDependencies": {
126
- "@types/js-yaml": "^4.0.9",
127
105
  "npm-run-all": "^4.1.5",
128
106
  "typedoc": "^0.28.14",
129
107
  "typedoc-plugin-markdown": "^4.9.0",
@@ -1,116 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.loadSkills = loadSkills;
4
- exports.createSkillsFeature = createSkillsFeature;
5
- const tslib_1 = require("tslib");
6
- const promises_1 = require("fs/promises");
7
- const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
8
- const path_1 = require("path");
9
- function parseFrontMatter(content) {
10
- const frontMatterRegex = /^---\s*\n([\s\S]*?)\n---/;
11
- const match = content.match(frontMatterRegex);
12
- if (!match) {
13
- return {};
14
- }
15
- try {
16
- const frontMatter = match[1];
17
- const parsed = (js_yaml_1.default.load(frontMatter));
18
- if (typeof parsed !== 'object' || parsed === null) {
19
- return {};
20
- }
21
- return {
22
- name: typeof parsed.name === 'string' ? parsed.name : undefined,
23
- description: typeof parsed.description === 'string' ? parsed.description : undefined,
24
- };
25
- }
26
- catch {
27
- return {};
28
- }
29
- }
30
- async function loadSkillFromDirectory(skillDir) {
31
- const skillFilePath = (0, path_1.join)(skillDir, 'SKILL.md');
32
- try {
33
- const content = await (0, promises_1.readFile)(skillFilePath, 'utf-8');
34
- const { name, description } = parseFrontMatter(content);
35
- if (!name || !description) {
36
- return null;
37
- }
38
- return {
39
- name,
40
- description,
41
- path: (0, path_1.resolve)(skillDir),
42
- };
43
- }
44
- catch {
45
- return null;
46
- }
47
- }
48
- async function loadSkills(directories, options = {}) {
49
- const skills = ([]);
50
- const watchControllers = ([]);
51
- async function scanDirectory(baseDir) {
52
- try {
53
- const entries = await (0, promises_1.readdir)(baseDir);
54
- for (const entry of entries) {
55
- const entryPath = (0, path_1.join)(baseDir, entry);
56
- const entryStat = await (0, promises_1.stat)(entryPath);
57
- if (entryStat.isDirectory()) {
58
- const skill = await loadSkillFromDirectory(entryPath);
59
- if (skill) {
60
- const existingIdx = skills.findIndex((s) => s.path === skill.path);
61
- if (existingIdx !== -1) {
62
- skills[existingIdx] = skill;
63
- }
64
- else {
65
- skills.push(skill);
66
- }
67
- }
68
- }
69
- }
70
- }
71
- catch {
72
- }
73
- }
74
- for (const dir of directories) {
75
- await scanDirectory(dir);
76
- }
77
- if (options.watch) {
78
- for (const dir of directories) {
79
- const controller = new AbortController();
80
- watchControllers.push(controller);
81
- (async () => {
82
- try {
83
- const watcher = (0, promises_1.watch)(dir, {
84
- recursive: true,
85
- signal: controller.signal,
86
- });
87
- for await (const event of watcher) {
88
- if (event.filename?.endsWith('SKILL.md')) {
89
- await scanDirectory(dir);
90
- }
91
- }
92
- }
93
- catch (err) {
94
- if (err instanceof Error &&
95
- err.name !== 'AbortError' &&
96
- !err.message.includes('AbortError')) {
97
- }
98
- }
99
- })();
100
- }
101
- }
102
- return {
103
- skills,
104
- close: () => {
105
- for (const controller of watchControllers) {
106
- controller.abort();
107
- }
108
- },
109
- };
110
- }
111
- function createSkillsFeature(skills) {
112
- return {
113
- name: ('skills'),
114
- options: { skills },
115
- };
116
- }
@@ -1,18 +0,0 @@
1
- export function loadSkills(directories: string[], options?: {
2
- watch?: boolean;
3
- }): Promise<SkillsResult>;
4
- export function createSkillsFeature(skills: SkillDefinition[]): {
5
- name: "skills";
6
- options: {
7
- skills: SkillDefinition[];
8
- };
9
- };
10
- export type SkillDefinition = {
11
- name: string;
12
- description: string;
13
- path: string;
14
- };
15
- export type SkillsResult = {
16
- skills: SkillDefinition[];
17
- close: () => void;
18
- };
@@ -1,18 +0,0 @@
1
- export function loadSkills(directories: string[], options?: {
2
- watch?: boolean;
3
- }): Promise<SkillsResult>;
4
- export function createSkillsFeature(skills: SkillDefinition[]): {
5
- name: "skills";
6
- options: {
7
- skills: SkillDefinition[];
8
- };
9
- };
10
- export type SkillDefinition = {
11
- name: string;
12
- description: string;
13
- path: string;
14
- };
15
- export type SkillsResult = {
16
- skills: SkillDefinition[];
17
- close: () => void;
18
- };
@@ -1,111 +0,0 @@
1
- import { readFile, readdir, stat, watch } from 'fs/promises';
2
- import yaml from 'js-yaml';
3
- import { join, resolve } from 'path';
4
- function parseFrontMatter(content) {
5
- const frontMatterRegex = /^---\s*\n([\s\S]*?)\n---/;
6
- const match = content.match(frontMatterRegex);
7
- if (!match) {
8
- return {};
9
- }
10
- try {
11
- const frontMatter = match[1];
12
- const parsed = (yaml.load(frontMatter));
13
- if (typeof parsed !== 'object' || parsed === null) {
14
- return {};
15
- }
16
- return {
17
- name: typeof parsed.name === 'string' ? parsed.name : undefined,
18
- description: typeof parsed.description === 'string' ? parsed.description : undefined,
19
- };
20
- }
21
- catch {
22
- return {};
23
- }
24
- }
25
- async function loadSkillFromDirectory(skillDir) {
26
- const skillFilePath = join(skillDir, 'SKILL.md');
27
- try {
28
- const content = await readFile(skillFilePath, 'utf-8');
29
- const { name, description } = parseFrontMatter(content);
30
- if (!name || !description) {
31
- return null;
32
- }
33
- return {
34
- name,
35
- description,
36
- path: resolve(skillDir),
37
- };
38
- }
39
- catch {
40
- return null;
41
- }
42
- }
43
- export async function loadSkills(directories, options = {}) {
44
- const skills = ([]);
45
- const watchControllers = ([]);
46
- async function scanDirectory(baseDir) {
47
- try {
48
- const entries = await readdir(baseDir);
49
- for (const entry of entries) {
50
- const entryPath = join(baseDir, entry);
51
- const entryStat = await stat(entryPath);
52
- if (entryStat.isDirectory()) {
53
- const skill = await loadSkillFromDirectory(entryPath);
54
- if (skill) {
55
- const existingIdx = skills.findIndex((s) => s.path === skill.path);
56
- if (existingIdx !== -1) {
57
- skills[existingIdx] = skill;
58
- }
59
- else {
60
- skills.push(skill);
61
- }
62
- }
63
- }
64
- }
65
- }
66
- catch {
67
- }
68
- }
69
- for (const dir of directories) {
70
- await scanDirectory(dir);
71
- }
72
- if (options.watch) {
73
- for (const dir of directories) {
74
- const controller = new AbortController();
75
- watchControllers.push(controller);
76
- (async () => {
77
- try {
78
- const watcher = watch(dir, {
79
- recursive: true,
80
- signal: controller.signal,
81
- });
82
- for await (const event of watcher) {
83
- if (event.filename?.endsWith('SKILL.md')) {
84
- await scanDirectory(dir);
85
- }
86
- }
87
- }
88
- catch (err) {
89
- if (err instanceof Error &&
90
- err.name !== 'AbortError' &&
91
- !err.message.includes('AbortError')) {
92
- }
93
- }
94
- })();
95
- }
96
- }
97
- return {
98
- skills,
99
- close: () => {
100
- for (const controller of watchControllers) {
101
- controller.abort();
102
- }
103
- },
104
- };
105
- }
106
- export function createSkillsFeature(skills) {
107
- return {
108
- name: ('skills'),
109
- options: { skills },
110
- };
111
- }