@dexto/tools-builtins 1.7.2 → 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.
Files changed (60) hide show
  1. package/dist/builtin-tools-factory.cjs +4 -0
  2. package/dist/builtin-tools-factory.d.ts +2 -1
  3. package/dist/builtin-tools-factory.d.ts.map +1 -1
  4. package/dist/builtin-tools-factory.js +4 -0
  5. package/dist/builtin-tools-factory.test.cjs +3 -2
  6. package/dist/builtin-tools-factory.test.js +3 -2
  7. package/dist/implementations/ask-user-tool.cjs +5 -5
  8. package/dist/implementations/ask-user-tool.d.ts +1 -1
  9. package/dist/implementations/ask-user-tool.d.ts.map +1 -1
  10. package/dist/implementations/ask-user-tool.js +6 -1
  11. package/dist/implementations/delegate-to-url-tool.cjs +15 -14
  12. package/dist/implementations/delegate-to-url-tool.d.ts +1 -1
  13. package/dist/implementations/delegate-to-url-tool.d.ts.map +1 -1
  14. package/dist/implementations/delegate-to-url-tool.js +2 -8
  15. package/dist/implementations/exa-code-search-tool.cjs +4 -4
  16. package/dist/implementations/exa-code-search-tool.d.ts +1 -1
  17. package/dist/implementations/exa-code-search-tool.d.ts.map +1 -1
  18. package/dist/implementations/exa-code-search-tool.js +1 -1
  19. package/dist/implementations/exa-mcp.cjs +7 -7
  20. package/dist/implementations/exa-mcp.d.ts +1 -1
  21. package/dist/implementations/exa-mcp.d.ts.map +1 -1
  22. package/dist/implementations/exa-mcp.js +2 -2
  23. package/dist/implementations/exa-web-search-tool.cjs +4 -4
  24. package/dist/implementations/exa-web-search-tool.d.ts +1 -1
  25. package/dist/implementations/exa-web-search-tool.d.ts.map +1 -1
  26. package/dist/implementations/exa-web-search-tool.js +1 -1
  27. package/dist/implementations/get-resource-tool.cjs +11 -11
  28. package/dist/implementations/get-resource-tool.d.ts +1 -1
  29. package/dist/implementations/get-resource-tool.d.ts.map +1 -1
  30. package/dist/implementations/get-resource-tool.js +12 -7
  31. package/dist/implementations/http-request-tool.cjs +45 -44
  32. package/dist/implementations/http-request-tool.d.ts +1 -1
  33. package/dist/implementations/http-request-tool.d.ts.map +1 -1
  34. package/dist/implementations/http-request-tool.js +2 -8
  35. package/dist/implementations/invoke-skill-tool.cjs +22 -170
  36. package/dist/implementations/invoke-skill-tool.d.ts +1 -8
  37. package/dist/implementations/invoke-skill-tool.d.ts.map +1 -1
  38. package/dist/implementations/invoke-skill-tool.js +19 -167
  39. package/dist/implementations/invoke-skill-tool.test.cjs +61 -85
  40. package/dist/implementations/invoke-skill-tool.test.js +61 -85
  41. package/dist/implementations/list-resources-tool.cjs +18 -16
  42. package/dist/implementations/list-resources-tool.d.ts +2 -2
  43. package/dist/implementations/list-resources-tool.d.ts.map +1 -1
  44. package/dist/implementations/list-resources-tool.js +15 -13
  45. package/dist/implementations/read-skill-tool.cjs +89 -0
  46. package/dist/implementations/read-skill-tool.d.ts +9 -0
  47. package/dist/implementations/read-skill-tool.d.ts.map +1 -0
  48. package/dist/implementations/read-skill-tool.js +65 -0
  49. package/dist/implementations/read-skill-tool.test.cjs +82 -0
  50. package/dist/implementations/read-skill-tool.test.d.ts +2 -0
  51. package/dist/implementations/read-skill-tool.test.d.ts.map +1 -0
  52. package/dist/implementations/read-skill-tool.test.js +81 -0
  53. package/dist/implementations/sleep-tool.cjs +3 -3
  54. package/dist/implementations/sleep-tool.d.ts +1 -1
  55. package/dist/implementations/sleep-tool.d.ts.map +1 -1
  56. package/dist/implementations/sleep-tool.js +1 -1
  57. package/dist/index.d.cts +2 -1
  58. package/dist/index.d.ts +1 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/package.json +3 -3
@@ -1,13 +1,11 @@
1
1
  import { z } from "zod";
2
- import { ToolError, createLocalToolCallHeader, defineTool, flattenPromptResult } from "@dexto/core";
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 skillName = input.skill;
20
- const colonIndex = skillName.indexOf(":");
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 { skill, args, taskContext } = input;
30
- const promptManager = context.services?.prompts;
31
- if (!promptManager) {
26
+ const skillManager = context.services?.skills;
27
+ if (!skillManager) {
32
28
  throw ToolError.configInvalid(
33
- "invoke_skill requires ToolExecutionContext.services.prompts"
29
+ "invoke_skill requires ToolExecutionContext.services.skills"
34
30
  );
35
31
  }
36
- const autoInvocable = await promptManager.listAutoInvocablePrompts();
37
- let skillKey;
38
- let matchedInfo;
39
- for (const key of Object.keys(autoInvocable)) {
40
- const info = autoInvocable[key];
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}' requires fork execution (context: fork), but agent spawning is not available.`,
78
- skill: skillKey
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 skillMcpError = await ensureSkillMcpServersConnected(skill, matchedInfo, context);
82
- if (skillMcpError) {
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
- ${content}` : content;
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: skillKey,
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 and execute specialized instructions for a task. Skills are predefined prompts that guide how to handle specific scenarios.
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)("retries previously registered bundled MCP servers before returning an error", async () => {
25
- const promptInfo = createPromptInfo();
26
- const promptManager = {
27
- listAutoInvocablePrompts: import_vitest.vi.fn().mockResolvedValue({ "config:echo-custom-mcp": promptInfo }),
28
- getPromptDefinition: import_vitest.vi.fn().mockResolvedValue(void 0),
29
- getPrompt: import_vitest.vi.fn().mockResolvedValue({
30
- messages: [
31
- {
32
- content: {
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: "echo-custom-mcp"
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
- prompts: promptManager
65
- },
66
- agent: {
67
- addMcpServer,
68
- getMcpServerStatus,
69
- enableMcpServer
27
+ skills: skillManager
70
28
  }
71
29
  }
72
30
  );
73
- (0, import_vitest.expect)(addMcpServer).not.toHaveBeenCalled();
74
- (0, import_vitest.expect)(enableMcpServer).toHaveBeenCalledWith("skill_echo_demo");
75
- (0, import_vitest.expect)(getMcpServerStatus).toHaveBeenCalledTimes(2);
76
- (0, import_vitest.expect)(result).toMatchObject({
77
- skill: "config:echo-custom-mcp",
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)("does not connect bundled MCP servers when fork execution is unavailable", async () => {
82
- const promptInfo = createPromptInfo();
83
- const promptManager = {
84
- listAutoInvocablePrompts: import_vitest.vi.fn().mockResolvedValue({ "config:echo-custom-mcp": promptInfo }),
85
- getPromptDefinition: import_vitest.vi.fn().mockResolvedValue({
86
- name: "echo-custom-mcp",
87
- context: "fork"
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: "echo-custom-mcp"
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
- prompts: promptManager
105
- },
106
- agent: {
107
- addMcpServer,
108
- getMcpServerStatus,
109
- enableMcpServer
58
+ skills: skillManager
110
59
  }
111
60
  }
112
61
  );
113
- (0, import_vitest.expect)(addMcpServer).not.toHaveBeenCalled();
114
- (0, import_vitest.expect)(enableMcpServer).not.toHaveBeenCalled();
115
- (0, import_vitest.expect)(getMcpServerStatus).not.toHaveBeenCalled();
116
- (0, import_vitest.expect)(promptManager.getPrompt).not.toHaveBeenCalled();
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 'echo-custom-mcp' requires fork execution (context: fork), but agent spawning is not available.",
119
- skill: "config:echo-custom-mcp"
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("retries previously registered bundled MCP servers before returning an error", async () => {
24
- const promptInfo = createPromptInfo();
25
- const promptManager = {
26
- listAutoInvocablePrompts: vi.fn().mockResolvedValue({ "config:echo-custom-mcp": promptInfo }),
27
- getPromptDefinition: vi.fn().mockResolvedValue(void 0),
28
- getPrompt: vi.fn().mockResolvedValue({
29
- messages: [
30
- {
31
- content: {
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: "echo-custom-mcp"
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
- prompts: promptManager
64
- },
65
- agent: {
66
- addMcpServer,
67
- getMcpServerStatus,
68
- enableMcpServer
26
+ skills: skillManager
69
27
  }
70
28
  }
71
29
  );
72
- expect(addMcpServer).not.toHaveBeenCalled();
73
- expect(enableMcpServer).toHaveBeenCalledWith("skill_echo_demo");
74
- expect(getMcpServerStatus).toHaveBeenCalledTimes(2);
75
- expect(result).toMatchObject({
76
- skill: "config:echo-custom-mcp",
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("does not connect bundled MCP servers when fork execution is unavailable", async () => {
81
- const promptInfo = createPromptInfo();
82
- const promptManager = {
83
- listAutoInvocablePrompts: vi.fn().mockResolvedValue({ "config:echo-custom-mcp": promptInfo }),
84
- getPromptDefinition: vi.fn().mockResolvedValue({
85
- name: "echo-custom-mcp",
86
- context: "fork"
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: "echo-custom-mcp"
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
- prompts: promptManager
104
- },
105
- agent: {
106
- addMcpServer,
107
- getMcpServerStatus,
108
- enableMcpServer
57
+ skills: skillManager
109
58
  }
110
59
  }
111
60
  );
112
- expect(addMcpServer).not.toHaveBeenCalled();
113
- expect(enableMcpServer).not.toHaveBeenCalled();
114
- expect(getMcpServerStatus).not.toHaveBeenCalled();
115
- expect(promptManager.getPrompt).not.toHaveBeenCalled();
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 'echo-custom-mcp' requires fork execution (context: fork), but agent spawning is not available.",
118
- skill: "config:echo-custom-mcp"
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
  });