( toolUseContext: ToolUseContext, )
| 2659 | } |
| 2660 | |
| 2661 | async function getSkillListingAttachments( |
| 2662 | toolUseContext: ToolUseContext, |
| 2663 | ): Promise<Attachment[]> { |
| 2664 | if (process.env.NODE_ENV === 'test') { |
| 2665 | return [] |
| 2666 | } |
| 2667 | |
| 2668 | // Skip skill listing for agents that don't have the Skill tool — they can't use skills directly. |
| 2669 | if ( |
| 2670 | !toolUseContext.options.tools.some(t => toolMatchesName(t, SKILL_TOOL_NAME)) |
| 2671 | ) { |
| 2672 | return [] |
| 2673 | } |
| 2674 | |
| 2675 | const cwd = getProjectRoot() |
| 2676 | const localCommands = await getSkillToolCommands(cwd) |
| 2677 | const mcpSkills = getMcpSkillCommands( |
| 2678 | toolUseContext.getAppState().mcp.commands, |
| 2679 | ) |
| 2680 | let allCommands = |
| 2681 | mcpSkills.length > 0 |
| 2682 | ? uniqBy([...localCommands, ...mcpSkills], 'name') |
| 2683 | : localCommands |
| 2684 | |
| 2685 | // When skill search is active, filter to bundled + MCP instead of full |
| 2686 | // suppression. Resolves the turn-0 gap: main thread gets turn-0 discovery |
| 2687 | // via getTurnZeroSkillDiscovery (blocking), but subagents use the async |
| 2688 | // subagent_spawn signal (collected post-tools, visible turn 1). Bundled + |
| 2689 | // MCP are small and intent-signaled; user/project/plugin skills go through |
| 2690 | // discovery. feature() first for DCE — the property-access string leaks |
| 2691 | // otherwise even with ?. on null. |
| 2692 | if ( |
| 2693 | feature('EXPERIMENTAL_SKILL_SEARCH') && |
| 2694 | skillSearchModules?.featureCheck.isSkillSearchEnabled() |
| 2695 | ) { |
| 2696 | allCommands = filterToBundledAndMcp(allCommands) |
| 2697 | } |
| 2698 | |
| 2699 | const agentKey = toolUseContext.agentId ?? '' |
| 2700 | let sent = sentSkillNames.get(agentKey) |
| 2701 | if (!sent) { |
| 2702 | sent = new Set() |
| 2703 | sentSkillNames.set(agentKey, sent) |
| 2704 | } |
| 2705 | |
| 2706 | // Resume path: prior process already injected a listing; it's in the |
| 2707 | // transcript. Mark everything current as sent so only post-resume deltas |
| 2708 | // (skills loaded later via /reload-plugins etc) get announced. |
| 2709 | if (suppressNext) { |
| 2710 | suppressNext = false |
| 2711 | for (const cmd of allCommands) { |
| 2712 | sent.add(cmd.name) |
| 2713 | } |
| 2714 | return [] |
| 2715 | } |
| 2716 | |
| 2717 | // Find skills we haven't sent yet |
| 2718 | const newSkills = allCommands.filter(cmd => !sent.has(cmd.name)) |
no test coverage detected