CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
SkillCommandLoader.ts149 linesDownload Raw Back to services
1/**2 * @license3 * Copyright 2025 Qwen4 * SPDX-License-Identifier: Apache-2.05 */6 7import type { Config } from '@qwen-code/qwen-code-core';8import {9  createDebugLogger,10  appendToLastTextPart,11  buildSkillLlmContent,12  applySkillAllowedTools,13} from '@qwen-code/qwen-code-core';14import { dirname } from 'node:path';15import type { ICommandLoader } from './types.js';16import type {17  SlashCommand,18  SlashCommandActionReturn,19  CommandSource,20} from '../ui/commands/types.js';21import { CommandKind } from '../ui/commands/types.js';22import { t } from '../i18n/index.js';23 24const debugLogger = createDebugLogger('SKILL_COMMAND_LOADER');25 26/**27 * Loads user-level, project-level, and extension-level skills as slash28 * commands, making them directly invocable via /<skill-name>.29 *30 * - User/project skills: always model-invocable (same as bundled), unless31 *   disable-model-invocation is set.32 * - Extension skills: model-invocable only when description or whenToUse is33 *   present (same rule as plugin commands), unless disable-model-invocation34 *   is set.35 */36export class SkillCommandLoader implements ICommandLoader {37  constructor(private readonly config: Config | null) {}38 39  async loadCommands(_signal: AbortSignal): Promise<SlashCommand[]> {40    if (this.config?.getBareMode?.()) {41      debugLogger.debug('Bare mode enabled, skipping skill commands');42      return [];43    }44 45    const skillManager = this.config?.getSkillManager();46    if (!skillManager) {47      debugLogger.debug('SkillManager not available, skipping skill commands');48      return [];49    }50 51    try {52      const [userSkills, projectSkills, extensionSkills] = await Promise.all([53        skillManager.listSkills({ level: 'user' }),54        skillManager.listSkills({ level: 'project' }),55        skillManager.listSkills({ level: 'extension' }),56      ]);57 58      const allSkills = [...userSkills, ...projectSkills, ...extensionSkills];59 60      // Apply user-controlled `skills.disabled` filter HERE (inside the61      // skill loader) rather than via `CommandService`'s global denylist —62      // a global filter would also hide a same-named built-in command or63      // MCP prompt. See `Config.getDisabledSkillNames` for why this is a64      // live-read provider rather than a frozen field.65      const disabled =66        this.config?.getDisabledSkillNames() ?? new Set<string>();67      const visibleSkills = allSkills.filter(68        (skill) => !disabled.has(skill.name.toLowerCase()),69      );70      const nonUserInvocableCount = visibleSkills.filter(71        (skill) => skill.userInvocable === false,72      ).length;73 74      debugLogger.debug(75        `Loaded ${userSkills.length} user + ${projectSkills.length} project + ${extensionSkills.length} extension skill(s) as slash commands; ${allSkills.length - visibleSkills.length} hidden by skills.disabled; ${nonUserInvocableCount} marked non-user-invocable`,76      );77 78      return visibleSkills.map((skill) => {79        const isExtension = skill.level === 'extension';80 81        // Extension skills need explicit description or whenToUse to be82        // model-invocable (same rule as plugin commands).83        // User/project skills are always model-invocable.84        const modelInvocable = skill.disableModelInvocation85          ? false86          : isExtension87            ? !!(skill.description || skill.whenToUse)88            : true;89 90        const sourceLabel = isExtension91          ? `${t('Extension:')} ${skill.extensionName ?? 'unknown'}`92          : skill.level === 'project'93            ? t('Project')94            : t('User');95 96        return {97          name: skill.name,98          description: skill.description,99          modelDescription: skill.description,100          kind: CommandKind.SKILL,101          source: (isExtension102            ? 'plugin-command'103            : 'skill-dir-command') as CommandSource,104          sourceLabel,105          sourceDetail: isExtension106            ? 'extension'107            : skill.level === 'project'108              ? 'project'109              : 'user',110          userInvocable: skill.userInvocable ?? true,111          modelInvocable,112          argumentHint: skill.argumentHint,113          whenToUse: skill.whenToUse,114          skillDetail: {115            name: skill.name,116            description: skill.description,117            body: skill.body,118            level: skill.level,119          },120          action: async (context, _args): Promise<SlashCommandActionReturn> => {121            // Auto-approve the skill's declared allowedTools before its body is submitted.122            applySkillAllowedTools(123              this.config?.getPermissionManager(),124              skill.allowedTools,125            );126 127            const body = buildSkillLlmContent(128              dirname(skill.filePath),129              skill.body,130            );131 132            const content = context.invocation?.args133              ? appendToLastTextPart([{ text: body }], context.invocation.raw)134              : [{ text: body }];135 136            return {137              type: 'submit_prompt',138              content,139            };140          },141        };142      });143    } catch (error) {144      debugLogger.error('Failed to load skill commands:', error);145      return [];146    }147  }148}149 
basant307/AI_Governance_Project · CoolFace