// Force strict mode and setup for ESM
"use strict";
import {
  applySkillAllowedTools,
  buildSkillLlmContent,
  clearCollectedSkillEntriesCache,
  collectAvailableSkillEntries,
  registerSkillHooks
} from "./chunk-2Z5UX4KI.js";
import "./chunk-ARKANCNX.js";
import {
  SkillLaunchEvent,
  logSkillLaunch,
  recordSkillInvocation
} from "./chunk-D5K5YBSN.js";
import "./chunk-4WKAA4KS.js";
import "./chunk-CR3C7WXL.js";
import "./chunk-RTV5TUYB.js";
import "./chunk-VGC4I5JJ.js";
import "./chunk-FBU7WRZI.js";
import "./chunk-Y66VOBRP.js";
import "./chunk-WTNZ6DXB.js";
import {
  BaseDeclarativeTool,
  BaseToolInvocation,
  ToolDisplayNames,
  ToolNames
} from "./chunk-JGUT3LWZ.js";
import "./chunk-WIEO4CWB.js";
import "./chunk-VRR65QYW.js";
import "./chunk-A4UCTEB5.js";
import "./chunk-XWRJCPHC.js";
import "./chunk-4LD5APFL.js";
import "./chunk-TYAMGABM.js";
import "./chunk-FCMNKVXT.js";
import {
  createDebugLogger
} from "./chunk-7NFB7LBQ.js";
import "./chunk-75DOP5OR.js";
import "./chunk-DMTGGOSA.js";
import "./chunk-YQ3U5MUC.js";
import "./chunk-TB6UDU4T.js";
import {
  init_esbuild_shims
} from "./chunk-5O2XNYP6.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 SKILL_TOOL_DESCRIPTION = `Execute a skill within the main conversation

<skills_instructions>
When users ask you to perform tasks, check if any of the available skills 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:
- Available skills are listed in <system-reminder> messages in the conversation; only use skills listed there.
- 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
- 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>`;
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,
      SKILL_TOOL_DESCRIPTION,
      // Static; live skill list is injected via system-reminders.
      "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 run.
  removeChangeListener;
  /**
   * Refreshes the in-memory runtime sets — `availableSkills`,
   * `pendingConditionalSkillNames`, `modelInvocableCommands` — that back
   * `validateToolParams` / `execute`. Invoked on construction and whenever the
   * SkillManager fires a change (skill-file edit, conditional activation, config
   * toggle, or MCP-prompt provider change).
   *
   * It deliberately does NOT mutate the tool declaration or call
   * `geminiClient.setTools()`. The Skill tool's description is static
   * (`SKILL_TOOL_DESCRIPTION`), so the skill set no longer affects the tools
   * block — and the tools block is the front of the tools → system → messages
   * prompt-cache prefix, where any byte change invalidates the whole cached
   * prefix. These runtime sets are in-memory only and never serialized into a
   * request, so refreshing them is prompt-cache-neutral. The model's view of the
   * available skills comes from the `<available_skills>` snapshot in the startup
   * prelude plus per-turn `<system-reminder>` deltas.
   */
  async refreshSkills() {
    try {
      clearCollectedSkillEntriesCache(this.skillManager);
      const collected = await collectAvailableSkillEntries(
        this.skillManager,
        this.config
      );
      this.availableSkills = collected.availableSkills;
      this.pendingConditionalSkillNames = collected.pendingConditionalSkillNames;
      this.modelInvocableCommands = collected.modelInvocableCommands;
    } catch (error) {
      debugLogger.warn("Failed to load skills for Skills tool:", error);
      this.availableSkills = [];
      this.pendingConditionalSkillNames = /* @__PURE__ */ new Set();
      this.modelInvocableCommands = [];
    }
  }
  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(),
      (name) => this.loadedSkillNames.has(name)
    );
  }
  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 run.
   */
  dispose() {
    this.removeChangeListener();
  }
};
var SkillToolInvocation = class extends BaseToolInvocation {
  constructor(config, skillManager, params, onSkillLoaded, commandExecutor = null, isSkillLoaded = () => false) {
    super(params);
    this.config = config;
    this.skillManager = skillManager;
    this.onSkillLoaded = onSkillLoaded;
    this.commandExecutor = commandExecutor;
    this.isSkillLoaded = isSkillLoaded;
  }
  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) {
      let disabledCommandFallbackAttempted = false;
      if (this.commandExecutor) {
        disabledCommandFallbackAttempted = true;
        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)
      );
      if (!disabledCommandFallbackAttempted) {
        recordSkillInvocation(this.config, {
          skillName: this.params.skill,
          success: false
        });
      }
      const msg = `Skill "${this.params.skill}" is disabled. Re-enable it via /skills or remove it from skills.disabled.`;
      return { llmContent: msg, returnDisplay: msg };
    }
    let commandFallbackAttempted = false;
    try {
      const skill = await this.skillManager.loadSkillForRuntime(
        this.params.skill
      );
      if (!skill) {
        if (this.commandExecutor) {
          commandFallbackAttempted = true;
          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)
        );
        if (!commandFallbackAttempted) {
          recordSkillInvocation(this.config, {
            skillName: this.params.skill,
            success: false
          });
        }
        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)
      );
      if (this.isSkillLoaded(this.params.skill)) {
        this.onSkillLoaded(this.params.skill);
        const msg = `Skill "${this.params.skill}" is already loaded in context.`;
        return {
          llmContent: msg,
          returnDisplay: msg
        };
      }
      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);
      recordSkillInvocation(this.config, {
        skillName: this.params.skill,
        success: true
      });
      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)
      );
      if (!commandFallbackAttempted) {
        recordSkillInvocation(this.config, {
          skillName: this.params.skill,
          success: false
        });
      }
      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
 */
