@dexto/tools-builtins 1.7.1 → 1.8.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/builtin-tools-factory.cjs +4 -0
- package/dist/builtin-tools-factory.d.ts +2 -1
- package/dist/builtin-tools-factory.d.ts.map +1 -1
- package/dist/builtin-tools-factory.js +4 -0
- package/dist/builtin-tools-factory.test.cjs +3 -2
- package/dist/builtin-tools-factory.test.js +3 -2
- package/dist/implementations/ask-user-tool.cjs +5 -5
- package/dist/implementations/ask-user-tool.d.ts +1 -1
- package/dist/implementations/ask-user-tool.d.ts.map +1 -1
- package/dist/implementations/ask-user-tool.js +6 -1
- package/dist/implementations/delegate-to-url-tool.cjs +15 -14
- package/dist/implementations/delegate-to-url-tool.d.ts +1 -1
- package/dist/implementations/delegate-to-url-tool.d.ts.map +1 -1
- package/dist/implementations/delegate-to-url-tool.js +2 -8
- package/dist/implementations/exa-code-search-tool.cjs +4 -4
- package/dist/implementations/exa-code-search-tool.d.ts +1 -1
- package/dist/implementations/exa-code-search-tool.d.ts.map +1 -1
- package/dist/implementations/exa-code-search-tool.js +1 -1
- package/dist/implementations/exa-mcp.cjs +7 -7
- package/dist/implementations/exa-mcp.d.ts +1 -1
- package/dist/implementations/exa-mcp.d.ts.map +1 -1
- package/dist/implementations/exa-mcp.js +2 -2
- package/dist/implementations/exa-web-search-tool.cjs +4 -4
- package/dist/implementations/exa-web-search-tool.d.ts +1 -1
- package/dist/implementations/exa-web-search-tool.d.ts.map +1 -1
- package/dist/implementations/exa-web-search-tool.js +1 -1
- package/dist/implementations/get-resource-tool.cjs +11 -11
- package/dist/implementations/get-resource-tool.d.ts +1 -1
- package/dist/implementations/get-resource-tool.d.ts.map +1 -1
- package/dist/implementations/get-resource-tool.js +12 -7
- package/dist/implementations/http-request-tool.cjs +45 -44
- package/dist/implementations/http-request-tool.d.ts +1 -1
- package/dist/implementations/http-request-tool.d.ts.map +1 -1
- package/dist/implementations/http-request-tool.js +2 -8
- package/dist/implementations/invoke-skill-tool.cjs +22 -170
- package/dist/implementations/invoke-skill-tool.d.ts +1 -8
- package/dist/implementations/invoke-skill-tool.d.ts.map +1 -1
- package/dist/implementations/invoke-skill-tool.js +19 -167
- package/dist/implementations/invoke-skill-tool.test.cjs +61 -85
- package/dist/implementations/invoke-skill-tool.test.js +61 -85
- package/dist/implementations/list-resources-tool.cjs +18 -16
- package/dist/implementations/list-resources-tool.d.ts +2 -2
- package/dist/implementations/list-resources-tool.d.ts.map +1 -1
- package/dist/implementations/list-resources-tool.js +15 -13
- package/dist/implementations/read-skill-tool.cjs +89 -0
- package/dist/implementations/read-skill-tool.d.ts +9 -0
- package/dist/implementations/read-skill-tool.d.ts.map +1 -0
- package/dist/implementations/read-skill-tool.js +65 -0
- package/dist/implementations/read-skill-tool.test.cjs +82 -0
- package/dist/implementations/read-skill-tool.test.d.ts +2 -0
- package/dist/implementations/read-skill-tool.test.d.ts.map +1 -0
- package/dist/implementations/read-skill-tool.test.js +81 -0
- package/dist/implementations/sleep-tool.cjs +3 -3
- package/dist/implementations/sleep-tool.d.ts +1 -1
- package/dist/implementations/sleep-tool.d.ts.map +1 -1
- package/dist/implementations/sleep-tool.js +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { ToolError, createLocalToolCallHeader, defineTool
|
|
2
|
+
import { ToolError, createLocalToolCallHeader, defineTool } from "@dexto/core/tools";
|
|
3
3
|
const InvokeSkillInputSchema = z.object({
|
|
4
4
|
skill: z.string().min(1, "Skill name is required").describe(
|
|
5
5
|
'The name of the skill to invoke (e.g., "plugin-name:skill-name" or "skill-name")'
|
|
6
6
|
),
|
|
7
7
|
args: z.record(z.string(), z.string()).optional().describe("Optional arguments to pass to the skill"),
|
|
8
|
-
taskContext: z.string().optional().describe(
|
|
9
|
-
"Context about what task this skill should accomplish. Recommended for forked skills to provide context since they run in isolation without conversation history."
|
|
10
|
-
)
|
|
8
|
+
taskContext: z.string().optional().describe("Context about what task this skill should accomplish")
|
|
11
9
|
}).strict();
|
|
12
10
|
function createInvokeSkillTool() {
|
|
13
11
|
return defineTool({
|
|
@@ -16,9 +14,8 @@ function createInvokeSkillTool() {
|
|
|
16
14
|
inputSchema: InvokeSkillInputSchema,
|
|
17
15
|
presentation: {
|
|
18
16
|
describeHeader: (input) => {
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const displaySkillName = colonIndex >= 0 ? skillName.slice(colonIndex + 1) : skillName;
|
|
17
|
+
const colonIndex = input.skill.indexOf(":");
|
|
18
|
+
const displaySkillName = colonIndex >= 0 ? input.skill.slice(colonIndex + 1) : input.skill;
|
|
22
19
|
return createLocalToolCallHeader({
|
|
23
20
|
title: "Skill",
|
|
24
21
|
argsText: `/${displaySkillName}`
|
|
@@ -26,113 +23,29 @@ function createInvokeSkillTool() {
|
|
|
26
23
|
}
|
|
27
24
|
},
|
|
28
25
|
async execute(input, context) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
if (!promptManager) {
|
|
26
|
+
const skillManager = context.services?.skills;
|
|
27
|
+
if (!skillManager) {
|
|
32
28
|
throw ToolError.configInvalid(
|
|
33
|
-
"invoke_skill requires ToolExecutionContext.services.
|
|
29
|
+
"invoke_skill requires ToolExecutionContext.services.skills"
|
|
34
30
|
);
|
|
35
31
|
}
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (!info) continue;
|
|
42
|
-
if (key === skill || info.displayName === skill || info.commandName === skill || info.name === skill) {
|
|
43
|
-
skillKey = key;
|
|
44
|
-
matchedInfo = info;
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (!skillKey) {
|
|
49
|
-
return {
|
|
50
|
-
error: `Skill '${skill}' not found or not available for model invocation. Use a skill from the available list.`,
|
|
51
|
-
availableSkills: Object.keys(autoInvocable)
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
const toolkits = Array.isArray(matchedInfo?.metadata?.toolkits) ? (matchedInfo?.metadata?.toolkits).filter((toolkit) => typeof toolkit === "string").map((toolkit) => toolkit.trim()).filter((toolkit) => toolkit.length > 0) : [];
|
|
55
|
-
if (toolkits.length > 0) {
|
|
56
|
-
if (!context.agent?.loadToolkits) {
|
|
57
|
-
return {
|
|
58
|
-
error: `Skill '${skill}' requires toolkits (${toolkits.join(
|
|
59
|
-
", "
|
|
60
|
-
)}), but this agent does not support dynamic tool loading.`,
|
|
61
|
-
toolkits
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
try {
|
|
65
|
-
await context.agent.loadToolkits(toolkits);
|
|
66
|
-
} catch (error) {
|
|
67
|
-
return {
|
|
68
|
-
error: error instanceof Error ? error.message : "Failed to load required toolkits",
|
|
69
|
-
toolkits
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
const promptDef = await promptManager.getPromptDefinition(skillKey);
|
|
74
|
-
const taskForker = context.services?.taskForker;
|
|
75
|
-
if (promptDef?.context === "fork" && !taskForker) {
|
|
32
|
+
const invoked = await skillManager.invoke(input.skill, input.args);
|
|
33
|
+
if (!invoked) {
|
|
34
|
+
const availableSkills = (await skillManager.list()).map(
|
|
35
|
+
(availableSkill) => availableSkill.displayName
|
|
36
|
+
);
|
|
76
37
|
return {
|
|
77
|
-
error: `Skill '${skill}'
|
|
78
|
-
|
|
38
|
+
error: `Skill '${input.skill}' not found or not available for model invocation. Use a skill from the available list.`,
|
|
39
|
+
availableSkills
|
|
79
40
|
};
|
|
80
41
|
}
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
return skillMcpError;
|
|
84
|
-
}
|
|
85
|
-
if (promptDef?.context !== "fork" && promptDef?.allowedTools && promptDef.allowedTools.length > 0 && context.sessionId && context.agent?.toolManager) {
|
|
86
|
-
try {
|
|
87
|
-
context.agent.toolManager.addSessionAutoApproveTools(
|
|
88
|
-
context.sessionId,
|
|
89
|
-
promptDef.allowedTools
|
|
90
|
-
);
|
|
91
|
-
} catch (error) {
|
|
92
|
-
context.logger?.warn("Failed to add auto-approve tools for skill", {
|
|
93
|
-
tools: promptDef.allowedTools,
|
|
94
|
-
error: error instanceof Error ? error.message : String(error)
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const promptResult = await promptManager.getPrompt(skillKey, args);
|
|
99
|
-
const flattened = flattenPromptResult(promptResult);
|
|
100
|
-
const content = flattened.text;
|
|
101
|
-
if (promptDef?.context === "fork") {
|
|
102
|
-
const activeTaskForker = taskForker;
|
|
103
|
-
if (!activeTaskForker) {
|
|
104
|
-
return {
|
|
105
|
-
error: `Skill '${skill}' requires fork execution (context: fork), but agent spawning is not available.`,
|
|
106
|
-
skill: skillKey
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
const instructions = taskContext ? `## Task Context
|
|
110
|
-
${taskContext}
|
|
42
|
+
const content = input.taskContext ? `## Task Context
|
|
43
|
+
${input.taskContext}
|
|
111
44
|
|
|
112
45
|
## Skill Instructions
|
|
113
|
-
${
|
|
114
|
-
const forkOptions = {
|
|
115
|
-
task: `Skill: ${skill}`,
|
|
116
|
-
instructions,
|
|
117
|
-
autoApprove: true
|
|
118
|
-
};
|
|
119
|
-
if (promptDef.agent) {
|
|
120
|
-
forkOptions.agentId = promptDef.agent;
|
|
121
|
-
}
|
|
122
|
-
if (context.toolCallId) {
|
|
123
|
-
forkOptions.toolCallId = context.toolCallId;
|
|
124
|
-
}
|
|
125
|
-
if (context.sessionId) {
|
|
126
|
-
forkOptions.sessionId = context.sessionId;
|
|
127
|
-
}
|
|
128
|
-
const result = await activeTaskForker.fork(forkOptions);
|
|
129
|
-
if (result.success) {
|
|
130
|
-
return result.response ?? "Task completed successfully.";
|
|
131
|
-
}
|
|
132
|
-
return `Error: ${result.error ?? "Unknown error during forked execution"}`;
|
|
133
|
-
}
|
|
46
|
+
${invoked.instructions}` : invoked.instructions;
|
|
134
47
|
return {
|
|
135
|
-
skill:
|
|
48
|
+
skill: invoked.id,
|
|
136
49
|
content,
|
|
137
50
|
instructions: "Follow the instructions in the skill content above to complete the task."
|
|
138
51
|
};
|
|
@@ -140,76 +53,15 @@ ${content}` : content;
|
|
|
140
53
|
});
|
|
141
54
|
}
|
|
142
55
|
function buildToolDescription() {
|
|
143
|
-
return `Invoke a skill to load
|
|
56
|
+
return `Invoke a skill to load specialized instructions for a task.
|
|
144
57
|
|
|
145
58
|
When to use:
|
|
146
59
|
- When you recognize a task that matches an available skill
|
|
147
60
|
- When you need specialized guidance for a complex operation
|
|
148
61
|
- When the user references a skill by name
|
|
149
62
|
|
|
150
|
-
Parameters:
|
|
151
|
-
- skill: The name of the skill to invoke
|
|
152
|
-
- args: Optional arguments to pass to the skill (e.g., for $ARGUMENTS substitution)
|
|
153
|
-
- taskContext: Context about what you're trying to accomplish (important for forked skills that run in isolation)
|
|
154
|
-
|
|
155
|
-
Execution modes:
|
|
156
|
-
- **Inline skills**: Return instructions for you to follow in the current conversation
|
|
157
|
-
- **Fork skills**: Automatically execute in an isolated subagent and return the result (no additional tool calls needed)
|
|
158
|
-
- **Bundled MCP skills**: Automatically connect any MCP servers carried inside the skill bundle when the skill is invoked
|
|
159
|
-
|
|
160
|
-
Fork skills run in complete isolation without access to conversation history. They're useful for tasks that should run independently.
|
|
161
|
-
|
|
162
63
|
Available skills are listed in your system prompt. Use the skill name exactly as shown.`;
|
|
163
64
|
}
|
|
164
|
-
function getSkillBundledMcpServers(promptInfo) {
|
|
165
|
-
const rawServers = promptInfo?.metadata?.mcpServers;
|
|
166
|
-
if (!rawServers || typeof rawServers !== "object" || Array.isArray(rawServers)) {
|
|
167
|
-
return {};
|
|
168
|
-
}
|
|
169
|
-
return Object.fromEntries(
|
|
170
|
-
Object.entries(rawServers).filter(
|
|
171
|
-
([, config]) => typeof config === "object" && config !== null && !Array.isArray(config)
|
|
172
|
-
)
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
async function ensureSkillMcpServersConnected(skill, promptInfo, context) {
|
|
176
|
-
const mcpServers = getSkillBundledMcpServers(promptInfo);
|
|
177
|
-
const serverNames = Object.keys(mcpServers);
|
|
178
|
-
if (serverNames.length === 0) {
|
|
179
|
-
return void 0;
|
|
180
|
-
}
|
|
181
|
-
if (!context.agent?.addMcpServer || !context.agent.getMcpServerStatus || !context.agent.enableMcpServer) {
|
|
182
|
-
return {
|
|
183
|
-
error: `Skill '${skill}' requires bundled MCP servers (${serverNames.join(", ")}), but this agent does not support dynamic MCP loading.`,
|
|
184
|
-
mcpServers: serverNames
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
try {
|
|
188
|
-
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
189
|
-
let serverStatus = context.agent.getMcpServerStatus(serverName);
|
|
190
|
-
if (!serverStatus) {
|
|
191
|
-
await context.agent.addMcpServer(serverName, serverConfig);
|
|
192
|
-
serverStatus = context.agent.getMcpServerStatus(serverName);
|
|
193
|
-
}
|
|
194
|
-
if (!serverStatus?.enabled || serverStatus.status !== "connected") {
|
|
195
|
-
await context.agent.enableMcpServer(serverName);
|
|
196
|
-
serverStatus = context.agent.getMcpServerStatus(serverName);
|
|
197
|
-
}
|
|
198
|
-
if (!serverStatus || serverStatus.status !== "connected") {
|
|
199
|
-
return {
|
|
200
|
-
error: `Skill '${skill}' requires MCP server '${serverName}', but it is currently ${serverStatus?.status ?? "unavailable"}${serverStatus?.error ? `: ${serverStatus.error}` : ""}.`,
|
|
201
|
-
mcpServers: serverNames
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
} catch (error) {
|
|
206
|
-
return {
|
|
207
|
-
error: error instanceof Error ? error.message : "Failed to connect bundled MCP servers for skill",
|
|
208
|
-
mcpServers: serverNames
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
return void 0;
|
|
212
|
-
}
|
|
213
65
|
export {
|
|
214
66
|
createInvokeSkillTool
|
|
215
67
|
};
|
|
@@ -1,122 +1,98 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var import_vitest = require("vitest");
|
|
3
3
|
var import_invoke_skill_tool = require("./invoke-skill-tool.js");
|
|
4
|
-
function createPromptInfo() {
|
|
5
|
-
return {
|
|
6
|
-
name: "echo-custom-mcp",
|
|
7
|
-
displayName: "echo-custom-mcp",
|
|
8
|
-
commandName: "echo-custom-mcp",
|
|
9
|
-
source: "config",
|
|
10
|
-
metadata: {
|
|
11
|
-
type: "file",
|
|
12
|
-
filePath: "/tmp/skills/echo-custom-mcp/SKILL.md",
|
|
13
|
-
mcpServers: {
|
|
14
|
-
skill_echo_demo: {
|
|
15
|
-
type: "stdio",
|
|
16
|
-
command: "node",
|
|
17
|
-
args: ["scripts/echo-mcp-server.mjs"]
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
4
|
(0, import_vitest.describe)("invoke_skill tool", () => {
|
|
24
|
-
(0, import_vitest.it)("
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
type: "text",
|
|
34
|
-
text: "Use the bundled echo MCP tool."
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
]
|
|
5
|
+
(0, import_vitest.it)("invokes skills through SkillManager", async () => {
|
|
6
|
+
const skillManager = {
|
|
7
|
+
list: import_vitest.vi.fn().mockResolvedValue([
|
|
8
|
+
{ id: "alpha", displayName: "Alpha", description: "Use alpha" }
|
|
9
|
+
]),
|
|
10
|
+
invoke: import_vitest.vi.fn().mockResolvedValue({
|
|
11
|
+
id: "alpha",
|
|
12
|
+
displayName: "Alpha",
|
|
13
|
+
instructions: "Use alpha instructions."
|
|
38
14
|
})
|
|
39
15
|
};
|
|
40
|
-
const addMcpServer = import_vitest.vi.fn();
|
|
41
|
-
const enableMcpServer = import_vitest.vi.fn().mockResolvedValue(void 0);
|
|
42
|
-
const getMcpServerStatus = import_vitest.vi.fn().mockReturnValueOnce({
|
|
43
|
-
name: "skill_echo_demo",
|
|
44
|
-
type: "stdio",
|
|
45
|
-
enabled: true,
|
|
46
|
-
status: "error",
|
|
47
|
-
error: "Request timed out"
|
|
48
|
-
}).mockReturnValueOnce({
|
|
49
|
-
name: "skill_echo_demo",
|
|
50
|
-
type: "stdio",
|
|
51
|
-
enabled: true,
|
|
52
|
-
status: "connected"
|
|
53
|
-
});
|
|
54
16
|
const tool = (0, import_invoke_skill_tool.createInvokeSkillTool)();
|
|
55
17
|
const result = await tool.execute(
|
|
56
18
|
{
|
|
57
|
-
skill: "
|
|
19
|
+
skill: "Alpha",
|
|
20
|
+
args: { mode: "fast" }
|
|
58
21
|
},
|
|
59
22
|
{
|
|
60
23
|
logger: {
|
|
61
24
|
warn: import_vitest.vi.fn()
|
|
62
25
|
},
|
|
63
26
|
services: {
|
|
64
|
-
|
|
65
|
-
},
|
|
66
|
-
agent: {
|
|
67
|
-
addMcpServer,
|
|
68
|
-
getMcpServerStatus,
|
|
69
|
-
enableMcpServer
|
|
27
|
+
skills: skillManager
|
|
70
28
|
}
|
|
71
29
|
}
|
|
72
30
|
);
|
|
73
|
-
(0, import_vitest.expect)(
|
|
74
|
-
(0, import_vitest.expect)(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
content: "Use the bundled echo MCP tool."
|
|
31
|
+
(0, import_vitest.expect)(skillManager.invoke).toHaveBeenCalledWith("Alpha", { mode: "fast" });
|
|
32
|
+
(0, import_vitest.expect)(result).toEqual({
|
|
33
|
+
skill: "alpha",
|
|
34
|
+
content: "Use alpha instructions.",
|
|
35
|
+
instructions: "Follow the instructions in the skill content above to complete the task."
|
|
79
36
|
});
|
|
80
37
|
});
|
|
81
|
-
(0, import_vitest.it)("
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
})
|
|
89
|
-
getPrompt: import_vitest.vi.fn()
|
|
38
|
+
(0, import_vitest.it)("adds task context to returned skill instructions", async () => {
|
|
39
|
+
const skillManager = {
|
|
40
|
+
list: import_vitest.vi.fn(),
|
|
41
|
+
invoke: import_vitest.vi.fn().mockResolvedValue({
|
|
42
|
+
id: "alpha",
|
|
43
|
+
displayName: "Alpha",
|
|
44
|
+
instructions: "Use alpha instructions."
|
|
45
|
+
})
|
|
90
46
|
};
|
|
91
|
-
const addMcpServer = import_vitest.vi.fn();
|
|
92
|
-
const enableMcpServer = import_vitest.vi.fn();
|
|
93
|
-
const getMcpServerStatus = import_vitest.vi.fn();
|
|
94
47
|
const tool = (0, import_invoke_skill_tool.createInvokeSkillTool)();
|
|
95
48
|
const result = await tool.execute(
|
|
96
49
|
{
|
|
97
|
-
skill: "
|
|
50
|
+
skill: "Alpha",
|
|
51
|
+
taskContext: "Refactor the storage layer."
|
|
98
52
|
},
|
|
99
53
|
{
|
|
100
54
|
logger: {
|
|
101
55
|
warn: import_vitest.vi.fn()
|
|
102
56
|
},
|
|
103
57
|
services: {
|
|
104
|
-
|
|
105
|
-
},
|
|
106
|
-
agent: {
|
|
107
|
-
addMcpServer,
|
|
108
|
-
getMcpServerStatus,
|
|
109
|
-
enableMcpServer
|
|
58
|
+
skills: skillManager
|
|
110
59
|
}
|
|
111
60
|
}
|
|
112
61
|
);
|
|
113
|
-
(0, import_vitest.expect)(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
62
|
+
(0, import_vitest.expect)(result).toMatchObject({
|
|
63
|
+
content: "## Task Context\nRefactor the storage layer.\n\n## Skill Instructions\nUse alpha instructions."
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
(0, import_vitest.it)("returns available skills when the requested skill is missing", async () => {
|
|
67
|
+
const skillManager = {
|
|
68
|
+
list: import_vitest.vi.fn().mockResolvedValue([
|
|
69
|
+
{ id: "alpha", displayName: "Alpha" },
|
|
70
|
+
{ id: "beta", displayName: "Beta" }
|
|
71
|
+
]),
|
|
72
|
+
invoke: import_vitest.vi.fn().mockResolvedValue(null)
|
|
73
|
+
};
|
|
74
|
+
const tool = (0, import_invoke_skill_tool.createInvokeSkillTool)();
|
|
75
|
+
const result = await tool.execute({ skill: "missing" }, {
|
|
76
|
+
logger: {
|
|
77
|
+
warn: import_vitest.vi.fn()
|
|
78
|
+
},
|
|
79
|
+
services: {
|
|
80
|
+
skills: skillManager
|
|
81
|
+
}
|
|
82
|
+
});
|
|
117
83
|
(0, import_vitest.expect)(result).toEqual({
|
|
118
|
-
error: "Skill '
|
|
119
|
-
|
|
84
|
+
error: "Skill 'missing' not found or not available for model invocation. Use a skill from the available list.",
|
|
85
|
+
availableSkills: ["Alpha", "Beta"]
|
|
120
86
|
});
|
|
121
87
|
});
|
|
88
|
+
(0, import_vitest.it)("requires ToolExecutionContext.services.skills", async () => {
|
|
89
|
+
const tool = (0, import_invoke_skill_tool.createInvokeSkillTool)();
|
|
90
|
+
await (0, import_vitest.expect)(
|
|
91
|
+
tool.execute({ skill: "alpha" }, {
|
|
92
|
+
logger: {
|
|
93
|
+
warn: import_vitest.vi.fn()
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
).rejects.toThrow("invoke_skill requires ToolExecutionContext.services.skills");
|
|
97
|
+
});
|
|
122
98
|
});
|
|
@@ -1,121 +1,97 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { createInvokeSkillTool } from "./invoke-skill-tool.js";
|
|
3
|
-
function createPromptInfo() {
|
|
4
|
-
return {
|
|
5
|
-
name: "echo-custom-mcp",
|
|
6
|
-
displayName: "echo-custom-mcp",
|
|
7
|
-
commandName: "echo-custom-mcp",
|
|
8
|
-
source: "config",
|
|
9
|
-
metadata: {
|
|
10
|
-
type: "file",
|
|
11
|
-
filePath: "/tmp/skills/echo-custom-mcp/SKILL.md",
|
|
12
|
-
mcpServers: {
|
|
13
|
-
skill_echo_demo: {
|
|
14
|
-
type: "stdio",
|
|
15
|
-
command: "node",
|
|
16
|
-
args: ["scripts/echo-mcp-server.mjs"]
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
3
|
describe("invoke_skill tool", () => {
|
|
23
|
-
it("
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
type: "text",
|
|
33
|
-
text: "Use the bundled echo MCP tool."
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
]
|
|
4
|
+
it("invokes skills through SkillManager", async () => {
|
|
5
|
+
const skillManager = {
|
|
6
|
+
list: vi.fn().mockResolvedValue([
|
|
7
|
+
{ id: "alpha", displayName: "Alpha", description: "Use alpha" }
|
|
8
|
+
]),
|
|
9
|
+
invoke: vi.fn().mockResolvedValue({
|
|
10
|
+
id: "alpha",
|
|
11
|
+
displayName: "Alpha",
|
|
12
|
+
instructions: "Use alpha instructions."
|
|
37
13
|
})
|
|
38
14
|
};
|
|
39
|
-
const addMcpServer = vi.fn();
|
|
40
|
-
const enableMcpServer = vi.fn().mockResolvedValue(void 0);
|
|
41
|
-
const getMcpServerStatus = vi.fn().mockReturnValueOnce({
|
|
42
|
-
name: "skill_echo_demo",
|
|
43
|
-
type: "stdio",
|
|
44
|
-
enabled: true,
|
|
45
|
-
status: "error",
|
|
46
|
-
error: "Request timed out"
|
|
47
|
-
}).mockReturnValueOnce({
|
|
48
|
-
name: "skill_echo_demo",
|
|
49
|
-
type: "stdio",
|
|
50
|
-
enabled: true,
|
|
51
|
-
status: "connected"
|
|
52
|
-
});
|
|
53
15
|
const tool = createInvokeSkillTool();
|
|
54
16
|
const result = await tool.execute(
|
|
55
17
|
{
|
|
56
|
-
skill: "
|
|
18
|
+
skill: "Alpha",
|
|
19
|
+
args: { mode: "fast" }
|
|
57
20
|
},
|
|
58
21
|
{
|
|
59
22
|
logger: {
|
|
60
23
|
warn: vi.fn()
|
|
61
24
|
},
|
|
62
25
|
services: {
|
|
63
|
-
|
|
64
|
-
},
|
|
65
|
-
agent: {
|
|
66
|
-
addMcpServer,
|
|
67
|
-
getMcpServerStatus,
|
|
68
|
-
enableMcpServer
|
|
26
|
+
skills: skillManager
|
|
69
27
|
}
|
|
70
28
|
}
|
|
71
29
|
);
|
|
72
|
-
expect(
|
|
73
|
-
expect(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
content: "Use the bundled echo MCP tool."
|
|
30
|
+
expect(skillManager.invoke).toHaveBeenCalledWith("Alpha", { mode: "fast" });
|
|
31
|
+
expect(result).toEqual({
|
|
32
|
+
skill: "alpha",
|
|
33
|
+
content: "Use alpha instructions.",
|
|
34
|
+
instructions: "Follow the instructions in the skill content above to complete the task."
|
|
78
35
|
});
|
|
79
36
|
});
|
|
80
|
-
it("
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
})
|
|
88
|
-
getPrompt: vi.fn()
|
|
37
|
+
it("adds task context to returned skill instructions", async () => {
|
|
38
|
+
const skillManager = {
|
|
39
|
+
list: vi.fn(),
|
|
40
|
+
invoke: vi.fn().mockResolvedValue({
|
|
41
|
+
id: "alpha",
|
|
42
|
+
displayName: "Alpha",
|
|
43
|
+
instructions: "Use alpha instructions."
|
|
44
|
+
})
|
|
89
45
|
};
|
|
90
|
-
const addMcpServer = vi.fn();
|
|
91
|
-
const enableMcpServer = vi.fn();
|
|
92
|
-
const getMcpServerStatus = vi.fn();
|
|
93
46
|
const tool = createInvokeSkillTool();
|
|
94
47
|
const result = await tool.execute(
|
|
95
48
|
{
|
|
96
|
-
skill: "
|
|
49
|
+
skill: "Alpha",
|
|
50
|
+
taskContext: "Refactor the storage layer."
|
|
97
51
|
},
|
|
98
52
|
{
|
|
99
53
|
logger: {
|
|
100
54
|
warn: vi.fn()
|
|
101
55
|
},
|
|
102
56
|
services: {
|
|
103
|
-
|
|
104
|
-
},
|
|
105
|
-
agent: {
|
|
106
|
-
addMcpServer,
|
|
107
|
-
getMcpServerStatus,
|
|
108
|
-
enableMcpServer
|
|
57
|
+
skills: skillManager
|
|
109
58
|
}
|
|
110
59
|
}
|
|
111
60
|
);
|
|
112
|
-
expect(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
61
|
+
expect(result).toMatchObject({
|
|
62
|
+
content: "## Task Context\nRefactor the storage layer.\n\n## Skill Instructions\nUse alpha instructions."
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
it("returns available skills when the requested skill is missing", async () => {
|
|
66
|
+
const skillManager = {
|
|
67
|
+
list: vi.fn().mockResolvedValue([
|
|
68
|
+
{ id: "alpha", displayName: "Alpha" },
|
|
69
|
+
{ id: "beta", displayName: "Beta" }
|
|
70
|
+
]),
|
|
71
|
+
invoke: vi.fn().mockResolvedValue(null)
|
|
72
|
+
};
|
|
73
|
+
const tool = createInvokeSkillTool();
|
|
74
|
+
const result = await tool.execute({ skill: "missing" }, {
|
|
75
|
+
logger: {
|
|
76
|
+
warn: vi.fn()
|
|
77
|
+
},
|
|
78
|
+
services: {
|
|
79
|
+
skills: skillManager
|
|
80
|
+
}
|
|
81
|
+
});
|
|
116
82
|
expect(result).toEqual({
|
|
117
|
-
error: "Skill '
|
|
118
|
-
|
|
83
|
+
error: "Skill 'missing' not found or not available for model invocation. Use a skill from the available list.",
|
|
84
|
+
availableSkills: ["Alpha", "Beta"]
|
|
119
85
|
});
|
|
120
86
|
});
|
|
87
|
+
it("requires ToolExecutionContext.services.skills", async () => {
|
|
88
|
+
const tool = createInvokeSkillTool();
|
|
89
|
+
await expect(
|
|
90
|
+
tool.execute({ skill: "alpha" }, {
|
|
91
|
+
logger: {
|
|
92
|
+
warn: vi.fn()
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
).rejects.toThrow("invoke_skill requires ToolExecutionContext.services.skills");
|
|
96
|
+
});
|
|
121
97
|
});
|