oricore 1.5.0 → 1.5.2
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/{chunk-DO76AL42.js → chunk-D5X6YFSK.js} +2 -1
- package/dist/{chunk-4QYFQSAC.js → chunk-E7TB3WLC.js} +321 -88
- package/dist/chunk-E7TB3WLC.js.map +1 -0
- package/dist/{chunk-OYWDQD3F.js → chunk-P4RPHQOE.js} +2 -2
- package/dist/history-WTZON3PI.js +8 -0
- package/dist/index.cjs +609 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +240 -4
- package/dist/index.d.ts +240 -4
- package/dist/index.js +282 -10
- package/dist/index.js.map +1 -1
- package/dist/{session-QMS6OYG2.js → session-Z2XIKFVN.js} +4 -4
- package/dist/undici-NSB7IUB7.js +5 -0
- package/package.json +2 -1
- package/src/core/model/models.ts +149 -0
- package/src/core/model/providers.ts +31 -0
- package/src/index.ts +12 -0
- package/src/skill/bundled.ts +225 -0
- package/src/skill/skill.ts +260 -4
- package/src/tools/tool.ts +14 -4
- package/src/tools/tools/skill.ts +86 -8
- package/dist/chunk-4QYFQSAC.js.map +0 -1
- package/dist/history-AGNMX5YW.js +0 -8
- package/dist/undici-326ZBRKH.js +0 -5
- /package/dist/{chunk-DO76AL42.js.map → chunk-D5X6YFSK.js.map} +0 -0
- /package/dist/{chunk-OYWDQD3F.js.map → chunk-P4RPHQOE.js.map} +0 -0
- /package/dist/{history-AGNMX5YW.js.map → history-WTZON3PI.js.map} +0 -0
- /package/dist/{session-QMS6OYG2.js.map → session-Z2XIKFVN.js.map} +0 -0
- /package/dist/{undici-326ZBRKH.js.map → undici-NSB7IUB7.js.map} +0 -0
package/src/skill/skill.ts
CHANGED
|
@@ -6,6 +6,11 @@ import type { Context } from '../core/context';
|
|
|
6
6
|
import type { Paths } from '../core/paths';
|
|
7
7
|
import { PluginHookType } from '../core/plugin';
|
|
8
8
|
import { safeFrontMatter } from '../utils/safeFrontMatter';
|
|
9
|
+
import {
|
|
10
|
+
bundledSkillRegistry,
|
|
11
|
+
bundledSkillToMetadata,
|
|
12
|
+
type BundledSkillDefinition,
|
|
13
|
+
} from './bundled';
|
|
9
14
|
|
|
10
15
|
/**
|
|
11
16
|
* Check if a directory entry is a directory or a symlink pointing to a directory.
|
|
@@ -28,13 +33,48 @@ export enum SkillSource {
|
|
|
28
33
|
Global = 'global',
|
|
29
34
|
ProjectClaude = 'project-claude',
|
|
30
35
|
Project = 'project',
|
|
36
|
+
Builtin = 'builtin',
|
|
31
37
|
}
|
|
32
38
|
|
|
39
|
+
export type SkillContext = 'inline' | 'fork';
|
|
40
|
+
|
|
33
41
|
export interface SkillMetadata {
|
|
34
42
|
name: string;
|
|
35
43
|
description: string;
|
|
36
44
|
path: string;
|
|
37
45
|
source: SkillSource;
|
|
46
|
+
/**
|
|
47
|
+
* Tools that this skill is allowed to use.
|
|
48
|
+
* If specified, only these tools will be available when the skill executes.
|
|
49
|
+
* Example: ['Read', 'Grep', 'Bash']
|
|
50
|
+
*/
|
|
51
|
+
allowedTools?: string[];
|
|
52
|
+
/**
|
|
53
|
+
* Execution context for the skill.
|
|
54
|
+
* - 'inline': Skill content is injected into current conversation (default)
|
|
55
|
+
* - 'fork': Skill runs in an isolated sub-agent
|
|
56
|
+
*/
|
|
57
|
+
context?: SkillContext;
|
|
58
|
+
/**
|
|
59
|
+
* Agent type to use when context is 'fork'.
|
|
60
|
+
* If not specified, defaults to a general-purpose agent.
|
|
61
|
+
*/
|
|
62
|
+
agent?: string;
|
|
63
|
+
/**
|
|
64
|
+
* File path patterns that trigger this skill's availability.
|
|
65
|
+
* Example: ['src/asterisk/asterisk.ts', '*.test.js']
|
|
66
|
+
*/
|
|
67
|
+
paths?: string[];
|
|
68
|
+
/**
|
|
69
|
+
* Whether this skill can be invoked by users via slash commands.
|
|
70
|
+
* @default true
|
|
71
|
+
*/
|
|
72
|
+
userInvocable?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Whether this skill can be invoked by the model via SkillTool.
|
|
75
|
+
* @default true
|
|
76
|
+
*/
|
|
77
|
+
modelInvocable?: boolean;
|
|
38
78
|
}
|
|
39
79
|
|
|
40
80
|
export interface SkillError {
|
|
@@ -86,26 +126,149 @@ export class SkillManager {
|
|
|
86
126
|
private errors: SkillError[] = [];
|
|
87
127
|
private paths: Paths;
|
|
88
128
|
private context: Context;
|
|
129
|
+
/**
|
|
130
|
+
* Tracks which skills are currently "active" based on file operations.
|
|
131
|
+
* Skills with `paths` frontmatter are activated when matching files are accessed.
|
|
132
|
+
*/
|
|
133
|
+
private activeSkillNames: Set<string> = new Set();
|
|
134
|
+
/**
|
|
135
|
+
* Maps alias names to original skill names for bundled skills.
|
|
136
|
+
*/
|
|
137
|
+
private aliasMap: Map<string, string> = new Map();
|
|
89
138
|
|
|
90
139
|
constructor(opts: SkillManagerOpts) {
|
|
91
140
|
this.context = opts.context;
|
|
92
141
|
this.paths = opts.context.paths;
|
|
93
142
|
}
|
|
94
143
|
|
|
95
|
-
|
|
96
|
-
|
|
144
|
+
/**
|
|
145
|
+
* Get all loaded skills.
|
|
146
|
+
* @param options Filter options
|
|
147
|
+
* @returns Array of skill metadata
|
|
148
|
+
*/
|
|
149
|
+
getSkills(options?: {
|
|
150
|
+
/** Only return skills that are user-invocable */
|
|
151
|
+
userInvocable?: boolean;
|
|
152
|
+
/** Only return skills that are model-invocable */
|
|
153
|
+
modelInvocable?: boolean;
|
|
154
|
+
/** Only return active skills (those with matching paths) */
|
|
155
|
+
activeOnly?: boolean;
|
|
156
|
+
}): SkillMetadata[] {
|
|
157
|
+
let skills = Array.from(this.skillsMap.values());
|
|
158
|
+
|
|
159
|
+
if (options?.userInvocable !== undefined) {
|
|
160
|
+
skills = skills.filter(
|
|
161
|
+
(s) => (s.userInvocable ?? true) === options.userInvocable,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (options?.modelInvocable !== undefined) {
|
|
166
|
+
skills = skills.filter(
|
|
167
|
+
(s) => (s.modelInvocable ?? true) === options.modelInvocable,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (options?.activeOnly) {
|
|
172
|
+
skills = skills.filter((s) => this.activeSkillNames.has(s.name));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return skills;
|
|
97
176
|
}
|
|
98
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Get a specific skill by name.
|
|
180
|
+
* Automatically resolves aliases to the original skill.
|
|
181
|
+
* @param name Skill name or alias
|
|
182
|
+
* @returns Skill metadata or undefined if not found
|
|
183
|
+
*/
|
|
99
184
|
getSkill(name: string): SkillMetadata | undefined {
|
|
100
|
-
|
|
185
|
+
// Check direct name first
|
|
186
|
+
const skill = this.skillsMap.get(name);
|
|
187
|
+
if (skill) return skill;
|
|
188
|
+
|
|
189
|
+
// Check if it's an alias
|
|
190
|
+
const originalName = this.aliasMap.get(name);
|
|
191
|
+
if (originalName) {
|
|
192
|
+
return this.skillsMap.get(originalName);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Check if a skill is active (has matching paths for current context).
|
|
200
|
+
* @param name Skill name
|
|
201
|
+
*/
|
|
202
|
+
isSkillActive(name: string): boolean {
|
|
203
|
+
return this.activeSkillNames.has(name);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Activate skills based on file paths.
|
|
208
|
+
* Skills with `paths` frontmatter that match the given paths will be activated.
|
|
209
|
+
* @param filePaths Array of file paths to check
|
|
210
|
+
* @returns Array of newly activated skill names
|
|
211
|
+
*/
|
|
212
|
+
activateSkillsForPaths(filePaths: string[]): string[] {
|
|
213
|
+
const newlyActivated: string[] = [];
|
|
214
|
+
const minimatch = require('minimatch');
|
|
215
|
+
|
|
216
|
+
for (const [name, skill] of this.skillsMap) {
|
|
217
|
+
if (skill.paths && skill.paths.length > 0) {
|
|
218
|
+
const isMatch = skill.paths.some((pattern) =>
|
|
219
|
+
filePaths.some((filePath) => minimatch(filePath, pattern)),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (isMatch && !this.activeSkillNames.has(name)) {
|
|
223
|
+
this.activeSkillNames.add(name);
|
|
224
|
+
newlyActivated.push(name);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return newlyActivated;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Clear all active skill activations.
|
|
234
|
+
* Useful when switching contexts or projects.
|
|
235
|
+
*/
|
|
236
|
+
clearActiveSkills(): void {
|
|
237
|
+
this.activeSkillNames.clear();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get all skills that have path-based conditional activation.
|
|
242
|
+
* @returns Array of skills with paths defined
|
|
243
|
+
*/
|
|
244
|
+
getConditionalSkills(): SkillMetadata[] {
|
|
245
|
+
return Array.from(this.skillsMap.values()).filter(
|
|
246
|
+
(s) => s.paths && s.paths.length > 0,
|
|
247
|
+
);
|
|
101
248
|
}
|
|
102
249
|
|
|
103
250
|
getErrors(): SkillError[] {
|
|
104
251
|
return this.errors;
|
|
105
252
|
}
|
|
106
253
|
|
|
107
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Read the body content of a skill.
|
|
256
|
+
* For file-based skills, reads from disk.
|
|
257
|
+
* For bundled skills, generates content via getPrompt.
|
|
258
|
+
*/
|
|
259
|
+
async readSkillBody(skill: SkillMetadata, args: string = ''): Promise<string> {
|
|
108
260
|
try {
|
|
261
|
+
// Handle bundled skills
|
|
262
|
+
if (skill.path.startsWith('bundled://')) {
|
|
263
|
+
const bundledName = skill.path.replace('bundled://', '');
|
|
264
|
+
const bundledSkill = bundledSkillRegistry.get(bundledName);
|
|
265
|
+
if (!bundledSkill) {
|
|
266
|
+
throw new Error(`Bundled skill "${bundledName}" not found in registry`);
|
|
267
|
+
}
|
|
268
|
+
return await bundledSkill.getPrompt(args, this.context);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Handle file-based skills
|
|
109
272
|
const content = fs.readFileSync(skill.path, 'utf-8');
|
|
110
273
|
const { body } = safeFrontMatter(content, skill.path);
|
|
111
274
|
return body;
|
|
@@ -116,10 +279,32 @@ export class SkillManager {
|
|
|
116
279
|
}
|
|
117
280
|
}
|
|
118
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Get a bundled skill definition by name.
|
|
284
|
+
* @param name Bundled skill name
|
|
285
|
+
* @returns Bundled skill definition or undefined if not found
|
|
286
|
+
*/
|
|
287
|
+
getBundledSkill(name: string): BundledSkillDefinition | undefined {
|
|
288
|
+
return bundledSkillRegistry.get(name);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Register a bundled skill programmatically.
|
|
293
|
+
* @param definition Bundled skill definition
|
|
294
|
+
*/
|
|
295
|
+
registerBundledSkill(definition: BundledSkillDefinition): void {
|
|
296
|
+
bundledSkillRegistry.register(definition);
|
|
297
|
+
// Reload to include the new skill
|
|
298
|
+
this.loadBuiltinSkills();
|
|
299
|
+
}
|
|
300
|
+
|
|
119
301
|
async loadSkills(): Promise<void> {
|
|
120
302
|
this.skillsMap.clear();
|
|
121
303
|
this.errors = [];
|
|
122
304
|
|
|
305
|
+
// Load builtin/bundled skills first
|
|
306
|
+
this.loadBuiltinSkills();
|
|
307
|
+
|
|
123
308
|
const pluginSkills = await this.context.apply({
|
|
124
309
|
hook: 'skill',
|
|
125
310
|
args: [],
|
|
@@ -168,6 +353,27 @@ export class SkillManager {
|
|
|
168
353
|
this.loadSkillsFromDirectory(projectDir, SkillSource.Project);
|
|
169
354
|
}
|
|
170
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Load builtin/bundled skills from the registry.
|
|
358
|
+
*/
|
|
359
|
+
private loadBuiltinSkills(): void {
|
|
360
|
+
// Clear alias map when reloading
|
|
361
|
+
this.aliasMap.clear();
|
|
362
|
+
|
|
363
|
+
const bundledSkills = bundledSkillRegistry.getAll(this.context);
|
|
364
|
+
for (const skill of bundledSkills) {
|
|
365
|
+
const metadata = bundledSkillToMetadata(skill, SkillSource.Builtin);
|
|
366
|
+
this.skillsMap.set(skill.name, metadata);
|
|
367
|
+
|
|
368
|
+
// Register aliases in the alias map
|
|
369
|
+
if (skill.aliases) {
|
|
370
|
+
for (const alias of skill.aliases) {
|
|
371
|
+
this.aliasMap.set(alias, skill.name);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
171
377
|
private loadSkillsFromDirectory(
|
|
172
378
|
skillsDir: string,
|
|
173
379
|
source: SkillSource,
|
|
@@ -225,6 +431,12 @@ export class SkillManager {
|
|
|
225
431
|
const { attributes } = safeFrontMatter<{
|
|
226
432
|
name?: string;
|
|
227
433
|
description?: string;
|
|
434
|
+
allowedTools?: string | string[];
|
|
435
|
+
context?: string;
|
|
436
|
+
agent?: string;
|
|
437
|
+
paths?: string | string[];
|
|
438
|
+
userInvocable?: boolean;
|
|
439
|
+
modelInvocable?: boolean;
|
|
228
440
|
}>(content, skillPath);
|
|
229
441
|
|
|
230
442
|
if (!attributes.name) {
|
|
@@ -267,10 +479,28 @@ export class SkillManager {
|
|
|
267
479
|
return null;
|
|
268
480
|
}
|
|
269
481
|
|
|
482
|
+
// Parse allowedTools - supports both string and array formats
|
|
483
|
+
const allowedTools = this.parseStringArrayField(attributes.allowedTools);
|
|
484
|
+
|
|
485
|
+
// Parse context (inline or fork)
|
|
486
|
+
const context: SkillContext | undefined =
|
|
487
|
+
attributes.context === 'inline' || attributes.context === 'fork'
|
|
488
|
+
? attributes.context
|
|
489
|
+
: undefined;
|
|
490
|
+
|
|
491
|
+
// Parse paths - supports both string and array formats
|
|
492
|
+
const paths = this.parseStringArrayField(attributes.paths);
|
|
493
|
+
|
|
270
494
|
return {
|
|
271
495
|
name: attributes.name,
|
|
272
496
|
description: attributes.description,
|
|
273
497
|
path: skillPath,
|
|
498
|
+
allowedTools,
|
|
499
|
+
context,
|
|
500
|
+
agent: attributes.agent,
|
|
501
|
+
paths,
|
|
502
|
+
userInvocable: attributes.userInvocable ?? true,
|
|
503
|
+
modelInvocable: attributes.modelInvocable ?? true,
|
|
274
504
|
};
|
|
275
505
|
} catch (error) {
|
|
276
506
|
this.errors.push({
|
|
@@ -284,6 +514,32 @@ export class SkillManager {
|
|
|
284
514
|
}
|
|
285
515
|
}
|
|
286
516
|
|
|
517
|
+
/**
|
|
518
|
+
* Parse a field that can be either a comma-separated string or an array of strings.
|
|
519
|
+
* @param value The value to parse
|
|
520
|
+
* @returns Array of strings or undefined if empty
|
|
521
|
+
*/
|
|
522
|
+
private parseStringArrayField(
|
|
523
|
+
value: string | string[] | undefined,
|
|
524
|
+
): string[] | undefined {
|
|
525
|
+
if (!value) return undefined;
|
|
526
|
+
|
|
527
|
+
if (Array.isArray(value)) {
|
|
528
|
+
return value
|
|
529
|
+
.map((item) => item.trim())
|
|
530
|
+
.filter((item) => item.length > 0);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (typeof value === 'string') {
|
|
534
|
+
return value
|
|
535
|
+
.split(',')
|
|
536
|
+
.map((item) => item.trim())
|
|
537
|
+
.filter((item) => item.length > 0);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return undefined;
|
|
541
|
+
}
|
|
542
|
+
|
|
287
543
|
async addSkill(
|
|
288
544
|
source: string,
|
|
289
545
|
options: AddSkillOptions = {},
|
package/src/tools/tool.ts
CHANGED
|
@@ -51,9 +51,6 @@ export async function resolveTools(opts: ResolveToolsOpts) {
|
|
|
51
51
|
createGlobTool({ cwd }),
|
|
52
52
|
createGrepTool({ cwd }),
|
|
53
53
|
createFetchTool({ model, fetch: opts.context.fetch }),
|
|
54
|
-
...(hasSkills
|
|
55
|
-
? [createSkillTool({ skillManager: opts.context.skillManager! })]
|
|
56
|
-
: []),
|
|
57
54
|
];
|
|
58
55
|
const askUserQuestionTools = opts.askUserQuestion
|
|
59
56
|
? [createAskUserQuestionTool()]
|
|
@@ -90,7 +87,7 @@ export async function resolveTools(opts: ResolveToolsOpts) {
|
|
|
90
87
|
|
|
91
88
|
const mcpTools = await getMcpTools(opts.context);
|
|
92
89
|
|
|
93
|
-
|
|
90
|
+
let allTools = [
|
|
94
91
|
...readonlyTools,
|
|
95
92
|
...askUserQuestionTools,
|
|
96
93
|
...writeTools,
|
|
@@ -99,6 +96,19 @@ export async function resolveTools(opts: ResolveToolsOpts) {
|
|
|
99
96
|
...mcpTools,
|
|
100
97
|
];
|
|
101
98
|
|
|
99
|
+
// Add skill tool if skills are available
|
|
100
|
+
// Note: skill tool is added after initial tool list so it can reference allTools for fork execution
|
|
101
|
+
if (hasSkills) {
|
|
102
|
+
const skillTool = createSkillTool({
|
|
103
|
+
skillManager: opts.context.skillManager!,
|
|
104
|
+
context: opts.context,
|
|
105
|
+
tools: allTools,
|
|
106
|
+
sessionId: opts.sessionId,
|
|
107
|
+
signal: opts.signal,
|
|
108
|
+
});
|
|
109
|
+
allTools = [...allTools, skillTool];
|
|
110
|
+
}
|
|
111
|
+
|
|
102
112
|
// 1. First, execute plugin hook to allow plugins to add/modify tools
|
|
103
113
|
let availableTools = allTools;
|
|
104
114
|
try {
|
package/src/tools/tools/skill.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import path from 'pathe';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import type { Context } from '../../core/context';
|
|
3
4
|
import type { SkillManager, SkillMetadata } from '../../skill/skill';
|
|
4
|
-
import { createTool } from '../tool';
|
|
5
|
+
import { createTool, type Tool } from '../tool';
|
|
5
6
|
import { safeStringify } from '../../utils/safeStringify';
|
|
7
|
+
import { createTaskTool } from './task';
|
|
8
|
+
import { randomUUID } from '../../utils/randomUUID';
|
|
6
9
|
|
|
7
10
|
function renderAvailableSkills(skills: SkillMetadata[]): string {
|
|
8
11
|
return skills
|
|
12
|
+
.filter((skill) => skill.modelInvocable !== false)
|
|
9
13
|
.map(
|
|
10
14
|
(skill) =>
|
|
11
15
|
`<skill>\n<name>${skill.name}</name>\n<description>${skill.description}</description>\n</skill>`,
|
|
@@ -14,7 +18,7 @@ function renderAvailableSkills(skills: SkillMetadata[]): string {
|
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
function generateDescription(skillManager: SkillManager): string {
|
|
17
|
-
const skills = skillManager.getSkills();
|
|
21
|
+
const skills = skillManager.getSkills({ modelInvocable: true });
|
|
18
22
|
return `Execute a skill within the main conversation
|
|
19
23
|
<skills_instructions>
|
|
20
24
|
When users ask you to perform tasks, check if any of the available skills below match the task. If a skill matches, use this tool to invoke it. Skills provide specialized knowledge and procedures for specific tasks.
|
|
@@ -24,22 +28,38 @@ ${renderAvailableSkills(skills)}
|
|
|
24
28
|
</available_skills>`;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
export
|
|
31
|
+
export interface CreateSkillToolOpts {
|
|
32
|
+
skillManager: SkillManager;
|
|
33
|
+
context: Context;
|
|
34
|
+
tools: Tool[];
|
|
35
|
+
sessionId: string;
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createSkillTool(opts: CreateSkillToolOpts) {
|
|
40
|
+
const { skillManager, context, tools, sessionId, signal } = opts;
|
|
41
|
+
|
|
28
42
|
return createTool({
|
|
29
43
|
name: 'skill',
|
|
30
|
-
description: generateDescription(
|
|
44
|
+
description: generateDescription(skillManager),
|
|
31
45
|
parameters: z.object({
|
|
32
46
|
skill: z.string().describe('The skill name to execute'),
|
|
47
|
+
args: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe('Optional arguments to pass to the skill'),
|
|
33
51
|
}),
|
|
34
52
|
getDescription: ({ params }) => {
|
|
35
|
-
return params.
|
|
53
|
+
return params.args
|
|
54
|
+
? `${params.skill} ${params.args}`
|
|
55
|
+
: params.skill;
|
|
36
56
|
},
|
|
37
|
-
async execute({ skill }) {
|
|
57
|
+
async execute({ skill, args }) {
|
|
38
58
|
const trimmed = skill.trim();
|
|
39
59
|
const skillName = trimmed.startsWith('/')
|
|
40
60
|
? trimmed.substring(1)
|
|
41
61
|
: trimmed;
|
|
42
|
-
const foundSkill =
|
|
62
|
+
const foundSkill = skillManager.getSkill(skillName);
|
|
43
63
|
|
|
44
64
|
if (!foundSkill) {
|
|
45
65
|
return {
|
|
@@ -48,9 +68,67 @@ export function createSkillTool(opts: { skillManager: SkillManager }) {
|
|
|
48
68
|
};
|
|
49
69
|
}
|
|
50
70
|
|
|
51
|
-
|
|
71
|
+
// Check if skill can be invoked by model
|
|
72
|
+
if (foundSkill.modelInvocable === false) {
|
|
73
|
+
return {
|
|
74
|
+
isError: true,
|
|
75
|
+
llmContent: `Skill "${skillName}" cannot be invoked by the model`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const skillArgs = args || '';
|
|
80
|
+
const body = await skillManager.readSkillBody(foundSkill, skillArgs);
|
|
52
81
|
const baseDir = path.dirname(foundSkill.path);
|
|
53
82
|
|
|
83
|
+
// If skill has context: 'fork', use task tool for isolated execution
|
|
84
|
+
if (foundSkill.context === 'fork') {
|
|
85
|
+
if (!context.agentManager) {
|
|
86
|
+
return {
|
|
87
|
+
isError: true,
|
|
88
|
+
llmContent: `Skill "${skillName}" requires fork execution but agent manager is not available`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Create filtered tools list based on allowedTools
|
|
93
|
+
// Exclude 'skill' tool itself to prevent recursive invocation loops
|
|
94
|
+
const allowedTools = foundSkill.allowedTools;
|
|
95
|
+
const filteredTools = allowedTools
|
|
96
|
+
? tools.filter(
|
|
97
|
+
(t) =>
|
|
98
|
+
t.name !== 'skill' &&
|
|
99
|
+
allowedTools.some(
|
|
100
|
+
(allowed) =>
|
|
101
|
+
allowed.toLowerCase() === t.name.toLowerCase(),
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
: tools.filter((t) => t.name !== 'skill');
|
|
105
|
+
|
|
106
|
+
// Create task tool with filtered tools
|
|
107
|
+
const taskTool = createTaskTool({
|
|
108
|
+
context,
|
|
109
|
+
tools: filteredTools,
|
|
110
|
+
sessionId,
|
|
111
|
+
signal,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Execute skill as a task
|
|
115
|
+
const agentType = foundSkill.agent || 'general-purpose';
|
|
116
|
+
const prompt = `Base directory for this skill: ${baseDir}\n\n${body}`;
|
|
117
|
+
|
|
118
|
+
// Generate a unique toolCallId for tracking
|
|
119
|
+
const toolCallId = `skill-${skillName}-${randomUUID()}`;
|
|
120
|
+
|
|
121
|
+
return taskTool.execute(
|
|
122
|
+
{
|
|
123
|
+
description: `Execute skill: ${skillName}`,
|
|
124
|
+
prompt,
|
|
125
|
+
subagent_type: agentType,
|
|
126
|
+
},
|
|
127
|
+
toolCallId,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Inline execution (default)
|
|
54
132
|
const messages = [
|
|
55
133
|
{
|
|
56
134
|
type: 'text',
|