diff --git a/mastracode/src/agents/workspace.ts b/mastracode/src/agents/workspace.ts index 0137ee3cde7..818134f7e1e 100644 --- a/mastracode/src/agents/workspace.ts +++ b/mastracode/src/agents/workspace.ts @@ -18,17 +18,29 @@ import { TOOL_NAME_OVERRIDES } from '../tool-names.js'; // We support multiple skill locations for compatibility: // 1. Project-local: .mastracode/skills (project-specific mastracode skills) // 2. Project-local: .claude/skills (Claude Code compatible skills) -// 3. Global: ~/.mastracode/skills (user-wide mastracode skills) -// 4. Global: ~/.claude/skills (user-wide Claude Code skills) +// 3. Project-local: .claude/commands (Claude Code slash commands used as skills) +// 4. Project-local: .agents/commands (Superset agents slash commands) +// 5. Global: ~/.mastracode/skills (user-wide mastracode skills) +// 6. Global: ~/.claude/skills (user-wide Claude Code skills) +// 7. Global: ~/.claude/commands (user-wide Claude Code slash commands) +// 8. Global: ~/.agents/commands (user-wide Superset agents slash commands) const mastraCodeLocalSkillsPath = path.join(process.cwd(), '.mastracode', 'skills'); const claudeLocalSkillsPath = path.join(process.cwd(), '.claude', 'skills'); +const claudeLocalCommandsPath = path.join(process.cwd(), '.claude', 'commands'); + +const agentsLocalCommandsPath = path.join(process.cwd(), '.agents', 'commands'); + const mastraCodeGlobalSkillsPath = path.join(os.homedir(), '.mastracode', 'skills'); const claudeGlobalSkillsPath = path.join(os.homedir(), '.claude', 'skills'); +const claudeGlobalCommandsPath = path.join(os.homedir(), '.claude', 'commands'); + +const agentsGlobalCommandsPath = path.join(os.homedir(), '.agents', 'commands'); + // Mastra's LocalSkillSource.readdir uses Node's Dirent.isDirectory() which // returns false for symlinks. Tools like `npx skills add` install skills as // symlinks, so we need to resolve them. For each symlinked skill directory, @@ -77,8 +89,12 @@ function collectSkillPaths(skillsDirs: string[]): string[] { export const skillPaths = collectSkillPaths([ mastraCodeLocalSkillsPath, claudeLocalSkillsPath, + claudeLocalCommandsPath, + agentsLocalCommandsPath, mastraCodeGlobalSkillsPath, claudeGlobalSkillsPath, + claudeGlobalCommandsPath, + agentsGlobalCommandsPath, ]); const WORKSPACE_ID_PREFIX = 'mastra-code-workspace'; diff --git a/packages/core/src/harness/harness.ts b/packages/core/src/harness/harness.ts index c56216bfd29..66ab730c451 100644 --- a/packages/core/src/harness/harness.ts +++ b/packages/core/src/harness/harness.ts @@ -1281,12 +1281,20 @@ export class Harness { tracingContext, tracingOptions, requestContext: requestContextInput, + preloadSkills, }: { content: string; files?: Array<{ data: string; mediaType: string; filename?: string }>; tracingContext?: TracingContext; tracingOptions?: TracingOptions; requestContext?: RequestContext; + /** + * Skill names to load before the LLM processes the user message. + * For each name, the agent will call the skill tool before responding, + * making the skill content available as context. The tool calls appear + * in the conversation UI immediately on submission. + */ + preloadSkills?: string[]; }): Promise { if (!this.currentThreadId) { const thread = await this.createThread(); @@ -1316,7 +1324,14 @@ export class Harness { streamOptions.toolsets = await this.buildToolsets(requestContext); - let messageInput: string | Record = content; + // Prepend skill loading instructions when the caller has requested pre-loading. + // The agent calls the skill tool for each name before responding, surfacing + // visible tool call blocks in the UI immediately on submission. + const effectiveContent = preloadSkills?.length + ? `The user has invoked the following skills. Before responding to their request, you MUST call the skill tool once for each skill listed here — do not skip or defer this step:\n${preloadSkills.map(s => ` - ${s}`).join('\n')}\n\n\n${content}` + : content; + + let messageInput: string | Record = effectiveContent; if (files?.length) { const fileParts = files.map(f => { const isText = f.mediaType.startsWith('text/') || f.mediaType === 'application/json'; @@ -1343,7 +1358,7 @@ export class Harness { }); messageInput = { role: 'user', - content: [{ type: 'text', text: content }, ...fileParts], + content: [{ type: 'text', text: effectiveContent }, ...fileParts], }; }