-
Notifications
You must be signed in to change notification settings - Fork 3
feat: add commands/ to skill paths and preloadSkills to sendMessage #9
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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'); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Project-local command skill directories are resolved from process CWD instead of the active project path, so command skills may be missed when projectPath != process.cwd(). Prompt for AI agents |
||
|
|
||
| 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, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Added commands paths are treated as skill roots, but core discovery only loads SKILL.md skills, so command markdown files are likely not discovered. Prompt for AI agents |
||
| agentsLocalCommandsPath, | ||
| mastraCodeGlobalSkillsPath, | ||
| claudeGlobalSkillsPath, | ||
| claudeGlobalCommandsPath, | ||
| agentsGlobalCommandsPath, | ||
| ]); | ||
|
|
||
| const WORKSPACE_ID_PREFIX = 'mastra-code-workspace'; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1281,12 +1281,20 @@ export class Harness<TState = {}> { | |
| 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<void> { | ||
| if (!this.currentThreadId) { | ||
| const thread = await this.createThread(); | ||
|
|
@@ -1316,7 +1324,14 @@ export class Harness<TState = {}> { | |
|
|
||
| streamOptions.toolsets = await this.buildToolsets(requestContext); | ||
|
|
||
| let messageInput: string | Record<string, unknown> = 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 | ||
| ? `<system>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</system>\n\n${content}` | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Internal preload control instructions are injected into persisted user message content, causing prompt leakage and long-lived context pollution. Prompt for AI agents |
||
| : content; | ||
|
Comment on lines
+1327
to
+1332
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Check how preloadSkills values are populated upstream
# Look for callers of sendMessage that pass preloadSkills to understand the data source
rg -n --type=ts -C5 'preloadSkills' -g '!packages/core/src/harness/harness.ts'Repository: superset-sh/mastra Length of output: 44 🏁 Script executed: # Find the function containing lines 1327-1332 in harness.ts
head -n 1332 packages/core/src/harness/harness.ts | tail -n 50Repository: superset-sh/mastra Length of output: 2243 🏁 Script executed: # Search for the function signature that includes preloadSkills parameter
rg -n 'preloadSkills' packages/core/src/harness/harness.ts -B 20 | head -n 100Repository: superset-sh/mastra Length of output: 2611 🏁 Script executed: # Check if there are any type definitions or interfaces that define preloadSkills
rg -n 'interface|type.*\{' packages/core/src/harness/harness.ts | head -n 20Repository: superset-sh/mastra Length of output: 1256 🏁 Script executed: # Look for skill-related validation or normalization
rg -n 'skill.*validat|skill.*normaliz|validat.*skill' packages/core/src/harness/harness.tsRepository: superset-sh/mastra Length of output: 44 🏁 Script executed: # Find all callers of sendMessage to see how preloadSkills is passed
rg -n 'sendMessage' packages/core --type=ts | head -n 50Repository: superset-sh/mastra Length of output: 1623 🏁 Script executed: # Look for skill-related type definitions or constants that might constrain names
rg -n 'skill' packages/core/src --type=ts -i | grep -E 'interface|type|const.*=' | head -n 30Repository: superset-sh/mastra Length of output: 3668 🏁 Script executed: # Check if there's any validation or constraints on tool/skill names
rg -n 'tool.*name|skill.*name|buildToolsets' packages/core/src/harness/harness.ts | head -n 20Repository: superset-sh/mastra Length of output: 1362 🏁 Script executed: # Check the Skill type definition and how skill names are validated/constrained
rg -n 'interface Skill|type Skill' packages/core/src --type=ts -A 10 | head -n 50Repository: superset-sh/mastra Length of output: 3886 🏁 Script executed: # Look for how skill names are used or validated in skill resolution
rg -n 'resolveByName|resolveByPath|Skill.*name' packages/core/src/workspace/skills --type=ts -A 3 | head -n 60Repository: superset-sh/mastra Length of output: 4460 🏁 Script executed: # Check if preloadSkills parameter is documented with any constraints
rg -n 'preloadSkills' packages/core/src --type=ts -B 5 -A 5Repository: superset-sh/mastra Length of output: 3066 🏁 Script executed: # Check if preloadSkills is validated anywhere in the sendMessage function body
sed -n '1278,1400p' packages/core/src/harness/harness.ts | grep -n -C 3 'preloadSkills'Repository: superset-sh/mastra Length of output: 1264 🏁 Script executed: # Check if there's any validation or error handling for invalid skill names
rg -n 'preloadSkills' packages/core/src/harness/harness.ts -A 30 | grep -E 'validat|check|throw|error|sanitiz'Repository: superset-sh/mastra Length of output: 44 Validate preloadSkills names to prevent prompt injection. The Apply the same validation used for skill metadata: either validate that each name matches the skill name pattern, or verify that each name exists in the workspace skill registry before interpolation. 🤖 Prompt for AI Agents |
||
|
|
||
| let messageInput: string | Record<string, unknown> = 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<TState = {}> { | |
| }); | ||
| messageInput = { | ||
| role: 'user', | ||
| content: [{ type: 'text', text: content }, ...fileParts], | ||
| content: [{ type: 'text', text: effectiveContent }, ...fileParts], | ||
| }; | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Project-local command paths are computed from process CWD at module load, causing workspace skill/command discovery to ignore request-specific
projectPathfor non-CWD projects.Prompt for AI agents