* Processes skill directories that were discovered during file operations. * Uses dynamicSkillDirTriggers field from ToolUseContext
( toolUseContext: ToolUseContext, )
| 2614 | * Uses dynamicSkillDirTriggers field from ToolUseContext |
| 2615 | */ |
| 2616 | async function getDynamicSkillAttachments( |
| 2617 | toolUseContext: ToolUseContext, |
| 2618 | ): Promise<Attachment[]> { |
| 2619 | const attachments: Attachment[] = [] |
| 2620 | |
| 2621 | if ( |
| 2622 | toolUseContext.dynamicSkillDirTriggers && |
| 2623 | toolUseContext.dynamicSkillDirTriggers.size > 0 |
| 2624 | ) { |
| 2625 | // Parallelize: readdir all skill dirs concurrently |
| 2626 | const perDirResults = await Promise.all( |
| 2627 | Array.from(toolUseContext.dynamicSkillDirTriggers).map(async skillDir => { |
| 2628 | try { |
| 2629 | const entries = await readdir(skillDir, { withFileTypes: true }) |
| 2630 | const candidates = entries |
| 2631 | .filter(e => e.isDirectory() || e.isSymbolicLink()) |
| 2632 | .map(e => e.name) |
| 2633 | // Parallelize: stat all SKILL.md candidates concurrently |
| 2634 | const checked = await Promise.all( |
| 2635 | candidates.map(async name => { |
| 2636 | try { |
| 2637 | await stat(resolve(skillDir, name, 'SKILL.md')) |
| 2638 | return name |
| 2639 | } catch { |
| 2640 | return null // SKILL.md doesn't exist, skip this entry |
| 2641 | } |
| 2642 | }), |
| 2643 | ) |
| 2644 | return { |
| 2645 | skillDir, |
| 2646 | skillNames: checked.filter((n): n is string => n !== null), |
| 2647 | } |
| 2648 | } catch { |
| 2649 | // Ignore errors reading skill directories (e.g., directory doesn't exist) |
| 2650 | return { skillDir, skillNames: [] } |
| 2651 | } |
| 2652 | }), |
| 2653 | ) |
| 2654 | |
| 2655 | for (const { skillDir, skillNames } of perDirResults) { |
| 2656 | if (skillNames.length > 0) { |
| 2657 | attachments.push({ |
| 2658 | type: 'dynamic_skill', |
| 2659 | skillDir, |
| 2660 | skillNames, |
| 2661 | displayPath: relative(getCwd(), skillDir), |
| 2662 | }) |
| 2663 | } |
| 2664 | } |
| 2665 | |
| 2666 | toolUseContext.dynamicSkillDirTriggers.clear() |
| 2667 | } |
| 2668 | |
| 2669 | return attachments |
| 2670 | } |
| 2671 | |
| 2672 | // Track which skills have been sent to avoid re-sending. Keyed by agentId |
| 2673 | // (empty string = main thread) so subagents get their own turn-0 listing — |