// Force strict mode and setup for ESM
"use strict";
import {
  applySkillAllowedTools,
  buildSkillLlmContent,
  escapeXml,
  registerSkillHooks
} from "./chunk-PLYZAP4W.js";
import "./chunk-UABFCMPA.js";
import {
  SkillLaunchEvent,
  logSkillLaunch
} from "./chunk-3H7ENG2Q.js";
import "./chunk-3PJXIDKI.js";
import "./chunk-UWCTAVOD.js";
import "./chunk-OFEVLU4C.js";
import "./chunk-AVLOK27J.js";
import "./chunk-JSYEZAYV.js";
import "./chunk-5RNZ2QKF.js";
import {
  BaseDeclarativeTool,
  BaseToolInvocation,
  ToolDisplayNames,
  ToolNames
} from "./chunk-G763GDO6.js";
import "./chunk-F5ORN4YO.js";
import "./chunk-D46KOV3C.js";
import "./chunk-BNESGOSJ.js";
import "./chunk-SZOEIL6S.js";
import "./chunk-5IFG2VC4.js";
import {
  createDebugLogger
} from "./chunk-R5PDRHEF.js";
import "./chunk-ZERZSAZL.js";
import "./chunk-QN5NZ3UQ.js";
import "./chunk-BR4QREVK.js";
import "./chunk-WFVXF3OM.js";
import {
  init_esbuild_shims
} from "./chunk-A4BMJM77.js";
import {
  __name
} from "./chunk-J2S4EL5Y.js";

// packages/core/src/tools/skill.ts
init_esbuild_shims();
import path from "path";
var debugLogger = createDebugLogger("SKILL");
var SkillTool = class _SkillTool extends BaseDeclarativeTool {
  constructor(config) {
    const initialSchema = {
      type: "object",
      properties: {
        skill: {
          type: "string",
          description: 'The skill or command name. E.g., "pdf" or "xlsx"'
        },
        args: {
          type: "string",
          description: "Optional arguments for model-invocable slash commands."
        }
      },
      required: ["skill"],
      additionalProperties: false,
      $schema: "http://json-schema.org/draft-07/schema#"
    };
    super(
      _SkillTool.Name,
      ToolDisplayNames.SKILL,
      "Execute a skill within the main conversation. Loading available skills...",
      // Initial description
      "read" /* Read */,
      initialSchema,
      false,
      // isOutputMarkdown
      false
      // canUpdateOutput
    );
    this.config = config;
    const skillManager = config.getSkillManager();
    if (!skillManager) {
      throw new Error("SkillManager not available");
    }
    this.skillManager = skillManager;
    this.removeChangeListener = this.skillManager.addChangeListener(
      () => this.refreshSkills()
    );
    this.refreshSkills();
  }
  static {
    __name(this, "SkillTool");
  }
  static Name = ToolNames.SKILL;
  skillManager;
  availableSkills = [];
  // Conditional skills (with `paths:`) that exist on disk but have not yet
  // been activated by a matching tool invocation. Tracked separately so
  // validateToolParams can give a distinct error message when the model
  // names one of these: "gated by paths:, access a matching file first"
  // instead of the generic "not found".
  pendingConditionalSkillNames = /* @__PURE__ */ new Set();
  modelInvocableCommands = [];
  loadedSkillNames = /* @__PURE__ */ new Set();
  // Cleanup function returned by `addChangeListener`. Stored so per-agent
  // SkillTool instances (subagents share the parent's SkillManager) can
  // detach their listener at teardown — without this the SkillManager
  // accumulates listeners across subagent lifetimes, and each path
  // activation would serialize through every stale listener's
  // refreshSkills / setTools round-trip.
  removeChangeListener;
  /**
   * Asynchronously initializes the tool by loading available skills
   * and updating the description and schema.
   */
  async refreshSkills() {
    try {
      const allSkills = await this.skillManager.listSkills();
      const disabledNames = this.config.getDisabledSkillNames();
      const isDisabled = /* @__PURE__ */ __name((name) => disabledNames.has(name.toLowerCase()), "isDisabled");
      this.availableSkills = allSkills.filter(
        (s) => !s.disableModelInvocation && this.skillManager.isSkillActive(s) && !isDisabled(s.name)
      );
      this.pendingConditionalSkillNames = new Set(
        allSkills.filter(
          (s) => !s.disableModelInvocation && s.paths && s.paths.length > 0 && !this.skillManager.isSkillActive(s) && !isDisabled(s.name)
        ).map((s) => s.name)
      );
      const provider = this.config.getModelInvocableCommandsProvider();
      const allCommands = provider ? provider() : [];
      const fileBasedSkillNames = new Set(
        allSkills.filter((s) => !s.disableModelInvocation && !isDisabled(s.name)).map((s) => s.name)
      );
      this.modelInvocableCommands = allCommands.filter(
        (cmd) => !fileBasedSkillNames.has(cmd.name)
      );
      this.updateDescriptionAndSchema();
    } catch (error) {
      debugLogger.warn("Failed to load skills for Skills tool:", error);
      this.availableSkills = [];
      this.pendingConditionalSkillNames = /* @__PURE__ */ new Set();
      this.modelInvocableCommands = [];
      this.updateDescriptionAndSchema();
    } finally {
      const geminiClient = this.config.getGeminiClient();
      if (geminiClient) {
        await geminiClient.setTools();
      }
    }
  }
  /**
   * Updates the tool's description and schema based on available skills and
   * model-invocable commands (e.g. bundled skills, file commands, MCP prompts).
   */
  updateDescriptionAndSchema() {
    const allSkillEntries = [];
    for (const skill of this.availableSkills) {
      const descText = `${escapeXml(skill.description)}${skill.whenToUse ? ` \u2014 ${escapeXml(skill.whenToUse)}` : ""} (${skill.level})`;
      allSkillEntries.push(`<skill>
<name>
${escapeXml(skill.name)}
</name>
<description>
${descText}
</description>
<location>
${skill.level}
</location>
</skill>`);
    }
    for (const cmd of this.modelInvocableCommands) {
      allSkillEntries.push(`<skill>
<name>
${escapeXml(cmd.name)}
</name>
<description>
${escapeXml(cmd.description)}
</description>
</skill>`);
    }
    let skillDescriptions = "";
    if (allSkillEntries.length === 0) {
      skillDescriptions = "No skills are currently configured. Skills can be created by adding directories with SKILL.md files to .qwen/skills/ or ~/.qwen/skills/.";
    } else {
      skillDescriptions = allSkillEntries.join("\n");
    }
    const baseDescription = `Execute a skill within the main conversation

<skills_instructions>
When users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.

How to invoke:
- Use this tool with the skill name only (no arguments)
- Examples:
  - \`skill: "pdf"\` - invoke the pdf skill
  - \`skill: "xlsx"\` - invoke the xlsx skill
  - \`skill: "ms-office-suite:pdf"\` - invoke using fully qualified name
  - \`skill: "mcp-prompt", args: "topic"\` - invoke a model-invocable command with arguments

Important:
- When a skill is relevant, you must invoke this tool IMMEDIATELY as your first action
- NEVER just announce or mention a skill in your text response without actually calling this tool
- This is a BLOCKING REQUIREMENT: invoke the relevant Skill tool BEFORE generating any other response about the task
- Only use skills listed in <available_skills> below
- Do not invoke a skill that is already running
- Do not use this tool for built-in CLI commands (like /help, /clear, etc.)
- When executing scripts or loading referenced files, ALWAYS resolve absolute paths from skill's base directory. Examples:
  - \`bash scripts/init.sh\` -> \`bash /path/to/skill/scripts/init.sh\`
  - \`python scripts/helper.py\` -> \`python /path/to/skill/scripts/helper.py\`
  - \`reference.md\` -> \`/path/to/skill/reference.md\`
</skills_instructions>

<available_skills>
${skillDescriptions}
</available_skills>`;
    this.description = baseDescription;
  }
  validateToolParams(params) {
    if (!params.skill || typeof params.skill !== "string" || params.skill.trim() === "") {
      return 'Parameter "skill" must be a non-empty string.';
    }
    if (params.args !== void 0 && typeof params.args !== "string") {
      return 'Parameter "args" must be a string when provided.';
    }
    const skillExists = this.availableSkills.some(
      (skill) => skill.name === params.skill
    );
    if (skillExists) return null;
    const commandExists = this.modelInvocableCommands.some(
      (cmd) => cmd.name === params.skill
    );
    if (commandExists) return null;
    if (this.config.getDisabledSkillNames().has(params.skill.toLowerCase())) {
      return `Skill "${params.skill}" is disabled. Re-enable it via /skills or remove it from skills.disabled.`;
    }
    if (this.pendingConditionalSkillNames.has(params.skill)) {
      return `Skill "${params.skill}" is gated by path-based activation (paths: frontmatter) and is not yet available. Access a file matching its paths patterns first to activate it.`;
    }
    const availableNames = [
      ...this.availableSkills.map((s) => s.name),
      ...this.modelInvocableCommands.map((c) => c.name)
    ];
    if (availableNames.length === 0) {
      return `Skill "${params.skill}" not found. No skills are currently available.`;
    }
    return `Skill "${params.skill}" not found. Available skills: ${availableNames.join(", ")}`;
  }
  createInvocation(params) {
    return new SkillToolInvocation(
      this.config,
      this.skillManager,
      params,
      (name) => this.loadedSkillNames.add(name),
      this.config.getModelInvocableCommandsExecutor()
    );
  }
  toAutoClassifierInput(params) {
    return params.args === void 0 ? { skill: params.skill } : { skill: params.skill, args: params.args };
  }
  getAvailableSkillNames() {
    return this.availableSkills.map((skill) => skill.name);
  }
  /**
   * Returns the set of skill names that have been successfully loaded
   * (invoked) during the current session. Used by /context to attribute
   * loaded skill body tokens separately from the tool-definition cost.
   */
  getLoadedSkillNames() {
    return this.loadedSkillNames;
  }
  /**
   * Clears the loaded-skills tracking. Should be called when the session
   * is reset (e.g. /clear) so that stale body-token data is not shown.
   */
  clearLoadedSkills() {
    this.loadedSkillNames.clear();
  }
  /**
   * Detach the change listener from SkillManager. Tool registries call
   * this on teardown (mirroring AgentTool's pattern). Per-subagent
   * SkillTool instances share the parent's SkillManager via
   * `InProcessBackend.createPerAgentConfig`, so without dispose the
   * SkillManager would accumulate one stale listener per subagent
   * lifetime — and `notifyChangeListeners` is now `await`-ed
   * sequentially, so each path activation would serialize through every
   * accumulated listener's refreshSkills + setTools round-trip.
   */
  dispose() {
    this.removeChangeListener();
  }
};
var SkillToolInvocation = class extends BaseToolInvocation {
  constructor(config, skillManager, params, onSkillLoaded, commandExecutor = null) {
    super(params);
    this.config = config;
    this.skillManager = skillManager;
    this.onSkillLoaded = onSkillLoaded;
    this.commandExecutor = commandExecutor;
  }
  static {
    __name(this, "SkillToolInvocation");
  }
  // Populated by scheduler via setPromptId; empty = direct/non-scheduled
  // call, filter `prompt_id != ''` downstream. See design doc §4.1.1.
  promptId = "";
  setPromptId(promptId) {
    this.promptId = promptId;
  }
  getDescription() {
    return this.params.args === void 0 ? `Use skill: "${this.params.skill}"` : `Use skill: "${this.params.skill}" with args: "${formatArgsForDescription(this.params.args)}"`;
  }
  /**
   * Skills load user-defined code that runs with the agent's tool
   * access — they're a privileged sink. In AUTO mode the classifier
   * needs to inspect the skill name and any inline args before the
   * skill loads, but the scheduler short-circuits at L4 when
   * `finalPermission === 'allow'`. The L3 default must be `'ask'` so
   * the classifier projection added in this PR can be reached.
   */
  async getDefaultPermission() {
    return "ask";
  }
  async execute(_signal, _updateOutput) {
    const disabled = this.config.getDisabledSkillNames().has(this.params.skill.toLowerCase());
    if (disabled) {
      if (this.commandExecutor) {
        try {
          const content = await this.commandExecutor(
            this.params.skill,
            this.params.args ?? ""
          );
          if (content && typeof content === "object" && "error" in content) {
            return {
              llmContent: content.error,
              returnDisplay: content.error
            };
          }
          if (typeof content === "string") {
            return {
              llmContent: [{ text: content }],
              returnDisplay: `Delegated to command: ${this.params.skill}`
            };
          }
        } catch {
        }
      }
      logSkillLaunch(
        this.config,
        new SkillLaunchEvent(this.params.skill, false, this.promptId)
      );
      const msg = `Skill "${this.params.skill}" is disabled. Re-enable it via /skills or remove it from skills.disabled.`;
      return { llmContent: msg, returnDisplay: msg };
    }
    try {
      const skill = await this.skillManager.loadSkillForRuntime(
        this.params.skill
      );
      if (!skill) {
        if (this.commandExecutor) {
          const commandResult = await this.commandExecutor(
            this.params.skill,
            this.params.args ?? ""
          );
          if (commandResult && typeof commandResult === "object" && "error" in commandResult) {
            logSkillLaunch(
              this.config,
              new SkillLaunchEvent(this.params.skill, false, this.promptId)
            );
            return {
              llmContent: commandResult.error,
              returnDisplay: commandResult.error
            };
          }
          if (typeof commandResult === "string") {
            logSkillLaunch(
              this.config,
              new SkillLaunchEvent(this.params.skill, true, this.promptId)
            );
            this.onSkillLoaded(this.params.skill);
            return {
              llmContent: [{ text: commandResult }],
              returnDisplay: `Executed command: ${this.params.skill}`
            };
          }
        }
        logSkillLaunch(
          this.config,
          new SkillLaunchEvent(this.params.skill, false, this.promptId)
        );
        const parseErrors = this.skillManager.getParseErrors();
        const errorMessages = [];
        for (const [filePath, error] of parseErrors) {
          if (filePath.includes(this.params.skill)) {
            errorMessages.push(`Parse error at ${filePath}: ${error.message}`);
          }
        }
        const errorDetail = errorMessages.length > 0 ? `
Errors:
${errorMessages.join("\n")}` : "";
        return {
          llmContent: `Skill "${this.params.skill}" not found.${errorDetail}`,
          returnDisplay: `Skill "${this.params.skill}" not found.${errorDetail}`
        };
      }
      logSkillLaunch(
        this.config,
        new SkillLaunchEvent(this.params.skill, true, this.promptId)
      );
      this.onSkillLoaded(this.params.skill);
      applySkillAllowedTools(
        this.config.getPermissionManager(),
        skill.allowedTools
      );
      debugLogger.debug("Skill hooks check:", {
        hasHooks: !!skill.hooks,
        hooksKeys: skill.hooks ? Object.keys(skill.hooks) : [],
        skillName: skill.name
      });
      if (skill.hooks) {
        const hookSystem = this.config.getHookSystem();
        const sessionId = this.config.getSessionId();
        debugLogger.debug("Hook system and session:", {
          hasHookSystem: !!hookSystem,
          sessionId
        });
        if (hookSystem && sessionId) {
          const sessionHooksManager = hookSystem.getSessionHooksManager();
          const hookCount = registerSkillHooks(
            sessionHooksManager,
            sessionId,
            skill
          );
          if (hookCount > 0) {
            debugLogger.info(
              `Registered ${hookCount} hooks from skill "${this.params.skill}"`
            );
          } else {
            debugLogger.warn(
              `No hooks registered from skill "${this.params.skill}"`
            );
          }
        }
      } else {
        debugLogger.warn(
          `Skill "${this.params.skill}" has no hooks to register`
        );
      }
      const baseDir = path.dirname(skill.filePath);
      const llmContent = buildSkillLlmContent(baseDir, skill.body);
      return {
        llmContent: [{ text: llmContent }],
        returnDisplay: skill.description,
        modelOverride: skill.model
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      debugLogger.error(`[SkillsTool] Error using skill: ${errorMessage}`);
      logSkillLaunch(
        this.config,
        new SkillLaunchEvent(this.params.skill, false, this.promptId)
      );
      return {
        llmContent: `Failed to load skill "${this.params.skill}": ${errorMessage}`,
        returnDisplay: `Failed to load skill "${this.params.skill}": ${errorMessage}`
      };
    }
  }
};
function formatArgsForDescription(args) {
  const escapeMarkdown = /* @__PURE__ */ __name((value) => value.replace(/([\\`*_{}[\]()#+\-.!|>])/g, "\\$1"), "escapeMarkdown");
  return args.length > 120 ? `${escapeMarkdown(args.slice(0, 117))}...` : escapeMarkdown(args);
}
__name(formatArgsForDescription, "formatArgsForDescription");
export {
  SkillTool,
  buildSkillLlmContent
};
/**
 * @license
 * Copyright 2025 Qwen
 * SPDX-License-Identifier: Apache-2.0
 */
