diff --git a/.gitignore b/.gitignore index be7c44f..9e0ec99 100644 --- a/.gitignore +++ b/.gitignore @@ -420,4 +420,5 @@ FodyWeavers.xsd node_modules/* out/* +dist/* .vscode-test/* \ No newline at end of file diff --git a/.vscodeignore b/.vscodeignore index 24f8a50..af03773 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,12 +1,18 @@ .vscode/** .vscode-test/** +.github/** src/** +out/** +node_modules/** +docs/** +debugmcp-ad/** +.pytest_cache/** .gitignore .yarnrc +esbuild.js vsc-extension-quickstart.md **/tsconfig.json **/eslint.config.mjs **/*.map **/*.ts **/.vscode-test.* -debugmcp-ad/** diff --git a/README.md b/README.md index 97fb0ad..d193ca9 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Let AI agents debug your code inside VS Code - set breakpoints, step through exe [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![VS Code](https://img.shields.io/badge/VS%20Code-1.104.0+-blue.svg)](https://code.visualstudio.com/) -[![Version](https://img.shields.io/badge/version-2.2.0-green.svg)](https://github.com/microsoft/DebugMCP) +[![Version](https://img.shields.io/badge/version-2.2.1-green.svg)](https://github.com/microsoft/DebugMCP) [![VS Marketplace](https://img.shields.io/badge/VS%20Marketplace-Install-blue.svg)](https://marketplace.visualstudio.com/items?itemName=ozzafar.debugmcpextension) > ⭐ **If you find DebugMCP useful, please [star the repo on GitHub](https://github.com/microsoft/DebugMCP)!** It helps others discover the project and motivates continued development. @@ -17,11 +17,12 @@ Let AI agents debug your code inside VS Code - set breakpoints, step through exe

-## ✨ What's New in 2.2.0 +## ✨ What's New -- **`/debug-live` Agent Skill** — DebugMCP now ships a companion [Agent Skill](./skills/debug-live/SKILL.md) that is auto-installed into each configured harness's personal skills directory (e.g. `~/.copilot/skills/debug-live/`). Invoke it with `/debug-live` in supporting agents to load the systematic debugging workflow and trigger DebugMCP tools with the right context. -- **Robust debugging via the VS Code Testing API** — `start_debugging` with a `testName` now uses the VS Code Testing API to discover and launch the test, replacing the previous best-effort path. This works reliably across language test runners that integrate with the Testing API (pytest, Jest/Vitest, Java, .NET, Go, etc.) and produces consistent breakpoint hits inside individual test cases. -- **Concurrent debugging** - supports multiple concurrent debug sessions, allowing effective parallel agentic debugging. +### 2.2 +- **Cross-agent `debug-live` skill install** — the systematic debugging workflow ships as an [Agent Skill](https://agentskills.io) and is now installed into the **standard skills directories** — `~/.agents/skills/` (the cross-agent location honored by skills-compatible harnesses, including VS Code agent mode) and `~/.copilot/skills/` when present — so it's discoverable everywhere instead of being copied next to each agent's config where nothing scans it (fixes [#105](https://github.com/microsoft/DebugMCP/issues/105), where VS Code never loaded the skill). The server also advertises MCP `instructions` and the `start_debugging` tool points at the skill for the full workflow. +- **Pause running programs** — new `pause_execution` tool interrupts a freely-running program and stops at its current location, even with no breakpoint set (great for busy loops and embedded/bare-metal targets), so you can then inspect state or step from there. +- **Robust debugging via the VS Code Testing API** — `start_debugging` with a `testName` uses the VS Code Testing API to discover and launch the test, producing consistent breakpoint hits inside individual test cases across language test runners (pytest, Jest/Vitest, Java, .NET, Go, etc.). ## 🚀 Quick Install @@ -57,6 +58,7 @@ DebugMCP is an MCP server that gives AI coding agents full control over the VS C | **step_into** | Step into function calls | None | | **step_out** | Step out of the current function | None | | **continue_execution** | Continue until next breakpoint | None | +| **pause_execution** | Interrupt a freely-running program and stop at its current location (no breakpoint needed) | None | | **restart_debugging** | Restart the current debug session | None | | **add_breakpoint** | Add a breakpoint at a specific line (optionally conditional) | `fileFullPath` (required)
`lineContent` (required)
`condition` (optional) | | **remove_breakpoint** | Remove a breakpoint from a specific line | `fileFullPath` (required)
`line` (required) | @@ -65,11 +67,13 @@ DebugMCP is an MCP server that gives AI coding agents full control over the VS C | **get_variables_values** | Get variables and their values at current execution point | `scope` (optional: 'local', 'global', 'all') | | **evaluate_expression** | Evaluate an expression in debug context | `expression` (required) | -> **Note:** The MCP server intentionally exposes **tools only** — no procedural -> instructions, no documentation resources. Workflow guidance (when to debug, how to -> structure a root-cause investigation, language-specific quirks) lives in the companion -> [DebugMCP Agent Skill](./skills/debug-live/SKILL.md) so it can be loaded into an -> agent's prompt context independently of the MCP capability surface. +> **Note:** The MCP server exposes **tools** for debugger actions, while the procedural +> workflow guidance (when to debug, how to structure a root-cause investigation, +> language-specific quirks) lives in the companion [Agent Skill](./skills/debug-live/SKILL.md). +> Tool descriptions stay terse and behavioral; the extension installs the `debug-live` skill +> into the standard skills directories (`~/.agents/skills/`, plus `~/.copilot/skills/` when +> present) so skills-compatible harnesses load the full workflow on demand. The server also +> advertises MCP `instructions` pointing agents at it before debugging. ### 🎯 Debugging Best Practices diff --git a/docs/architecture/agentConfigurationManager.md b/docs/architecture/agentConfigurationManager.md index 0494ab6..8cd43c2 100644 --- a/docs/architecture/agentConfigurationManager.md +++ b/docs/architecture/agentConfigurationManager.md @@ -73,11 +73,22 @@ url = "http://localhost:3001/mcp" Uses VS Code's `globalState` to track whether the onboarding popup has been shown, preventing repeated prompts on every activation. +### Skill delivery — standard skills directories + +The `debug-live` Agent Skill is installed into the **standard personal skills directories** defined by the Agent Skills open standard (agentskills.io), rather than being copied next to each agent's config file: +- **`~/.agents/skills/debug-live/`** — the cross-agent location honored by skills-compatible harnesses, including VS Code agent mode and Copilot CLI. Always installed. +- **`~/.copilot/skills/debug-live/`** — Copilot's own skills path; also installed when a Copilot home directory (`~/.copilot`, or `$COPILOT_HOME`) exists. + +`installDebugMCPSkill()` copies the one bundled source (`skills/debug-live/SKILL.md`) into each target with `force: true` (idempotent refresh) and removes stale legacy copies (`debug`, `really-debug`). It is agent-independent — a single shared install covers every skills-compatible harness. + +This fixes issue #105: earlier builds copied the skill next to each agent's config (e.g. `Code/User/skills/` for VS Code Copilot), a directory no harness scans, so the skill never loaded. Installing to `~/.agents/skills/` — which VS Code agent mode does scan — makes it discoverable. + ## Key Code Locations - Class definition: `src/utils/agentConfigurationManager.ts` - Agent definitions: `getSupportedAgents()` - Config writing: `addDebugMCPToAgent()` +- Skill install: `installDebugMCPSkill()` / `getSkillInstallTargets()` / `ensureSkillRegistered()` - Codex TOML upsert: `upsertCodexDebugMCPConfig()` - Path detection: `getConfigBasePath()` - Popup logic: `shouldShowPopup()`, `showAgentSelectionPopup()` @@ -91,6 +102,8 @@ Uses VS Code's `globalState` to track whether the onboarding popup has been show 5. Show success message with option to open config file 6. Mark popup as shown +The bundled `debug-live` skill is installed into the standard skills directories (`~/.agents/skills/`, plus `~/.copilot/skills/` when present) during step 4, so every skills-compatible harness discovers it from one shared location. + ## Commands - `debugmcp.showAgentSelectionPopup`: Manually trigger agent setup diff --git a/docs/architecture/debugMCPServer.md b/docs/architecture/debugMCPServer.md index 6a79940..643199c 100644 --- a/docs/architecture/debugMCPServer.md +++ b/docs/architecture/debugMCPServer.md @@ -54,17 +54,26 @@ extension. To avoid debugging the wrong workspace when several windows are open: `DebugMCPServer` builds one handler **per MCP session** via a handler factory, which is what lets concurrent agent sessions drive debuggers in different repos simultaneously. -### Tools only — no resources, no instructions +### Tools & debug-live skill -`DebugMCPServer` exposes **tools only**. Procedural workflow guidance (when to debug, -how to structure a root-cause investigation, language-specific quirks) lives in the -companion Agent Skill at `skills/debug-live/SKILL.md`, not in tool descriptions or MCP -resources. This separation matches modern agent ecosystems where MCP servers provide -*capabilities* and skills provide *procedural knowledge* an agent loads as context. +`DebugMCPServer` exposes **tools** for debugger capabilities. Detailed procedural guidance +(when to debug, how to structure a root-cause investigation, language-specific quirks) lives +in the companion Agent Skill at `skills/debug-live/SKILL.md`, which `AgentConfigurationManager` +installs into the standard personal skills directories (`~/.agents/skills/`, and +`~/.copilot/skills/` when present) so skills-compatible harnesses load it on demand. This +separation matches modern agent ecosystems where MCP servers provide *capabilities* and +skills provide *procedural knowledge* an agent loads as context. Tool descriptions are intentionally terse and behavioral — they describe *what* the tool does, not *when* or *how* to use it in a multi-step workflow. +### Ensuring the workflow is loaded + +The server `instructions` (passed to the `McpServer` constructor and returned to the client +at `initialize`) and the `start_debugging` tool description both point agents at the +`debug-live` skill for the full step-through workflow, so the pointer is visible even before +the skill activates. + ### Streamable HTTP Transport Uses stateless HTTP POST requests for MCP communication. The express server exposes: @@ -96,7 +105,7 @@ error wins: - Per-window control server: `src/controlServer.ts` - Cross-window routing handler: `src/routingDebuggingHandler.ts` - Shared window registry: `src/utils/workspaceRegistry.ts` -- Agent Skill (companion, not part of the MCP surface): `skills/debug-live/SKILL.md` +- Agent Skill (procedural workflow): `skills/debug-live/SKILL.md` ## Exposed Tools @@ -106,6 +115,7 @@ error wins: | `stop_debugging` | Stop current session | | `step_over/into/out` | Stepping commands | | `continue_execution` | Continue to next breakpoint | +| `pause_execution` | Interrupt a running program (no breakpoint needed) | | `restart_debugging` | Restart session | | `add/remove_breakpoint` | Breakpoint management | | `clear_all_breakpoints` | Remove all breakpoints | diff --git a/docs/architecture/debuggingExecutor.md b/docs/architecture/debuggingExecutor.md index 4ecdf15..d147656 100644 --- a/docs/architecture/debuggingExecutor.md +++ b/docs/architecture/debuggingExecutor.md @@ -45,6 +45,7 @@ Stepping and control operations use VS Code's command system: - `workbench.action.debug.stepInto` - `workbench.action.debug.stepOut` - `workbench.action.debug.continue` +- `workbench.action.debug.pause` - `workbench.action.debug.restart` ### DAP Custom Requests diff --git a/esbuild.js b/esbuild.js new file mode 100644 index 0000000..3a0fbfc --- /dev/null +++ b/esbuild.js @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. + +const esbuild = require('esbuild'); + +const production = process.argv.includes('--production'); +const watch = process.argv.includes('--watch'); + +/** + * Bundles the extension into a single CommonJS file so the published VSIX ships + * one JS file instead of the entire production node_modules tree. `vscode` is + * kept external because it is provided by the host at runtime. + * @type {import('esbuild').Plugin} + */ +const esbuildProblemMatcherPlugin = { + name: 'esbuild-problem-matcher', + setup(build) { + build.onStart(() => { + console.log('[esbuild] build started'); + }); + build.onEnd((result) => { + result.errors.forEach(({ text, location }) => { + console.error(`✘ [ERROR] ${text}`); + if (location) { + console.error(` ${location.file}:${location.line}:${location.column}`); + } + }); + console.log('[esbuild] build finished'); + }); + }, +}; + +async function main() { + const ctx = await esbuild.context({ + entryPoints: ['src/extension.ts'], + bundle: true, + format: 'cjs', + minify: production, + sourcemap: !production, + sourcesContent: false, + platform: 'node', + outfile: 'dist/extension.js', + external: ['vscode'], + logLevel: 'silent', + plugins: [esbuildProblemMatcherPlugin], + }); + if (watch) { + await ctx.watch(); + } else { + await ctx.rebuild(); + await ctx.dispose(); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/package-lock.json b/package-lock.json index bb2bf63..520bd59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "debugmcpextension", - "version": "2.1.0", + "version": "2.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "debugmcpextension", - "version": "2.1.0", + "version": "2.2.1", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.26.0", @@ -22,6 +22,7 @@ "@typescript-eslint/parser": "^8.56.0", "@vscode/test-cli": "^0.0.12", "@vscode/test-electron": "^2.5.2", + "esbuild": "^0.28.1", "eslint": "^10.0.1", "typescript": "^5.9.3" }, @@ -39,6 +40,448 @@ "node": ">=18" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha1-egGo0uwvuy2seK2tCbD6eB5Agr4=", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha1-cEvSl95tdi3lTqu+r79V9nVqvi8=", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha1-tUCifRTkr9BYSWpNvsTT9BTbEQo=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha1-0csWbTSw+/D+irRgpVlPJKN4cB4=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha1-EDSyZFf8iGNo/mG70J9lP2r6jlQ=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha1-ZVVqQyoeTXIDLYIYwZMvzKGkl3I=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha1-LmHgWS+QMNfj2uGO4l68U1kYrvY=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha1-yV7CiZWe+AecTcqBeh4sS+Zrm9M=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha1-wJoPZ5F1kqwN6JKpvk04FN69Kmw=", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha1-QLIhdd2gYYLz7oFBGGxf8wTEpxc=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha1-pYD5xnZ5eDOJHlGfx6EzfIr9jbM=", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha1-RkUs8yHcf56Rwvp4Cla7Vuec1os=", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha1-QhGzGE3WYI9T3LIuOfXTTuCIUsg=", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha1-aXhXwqYcubC2u2ZS5AwdxeHKjl0=", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha1-0ZKUPrFGpArExkl9DPe+NbmGvwg=", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha1-rOoDVtoODrwI+Xz3ucLkAeHmSNw=", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha1-bww84MtkxTS3DExF7LLBbTTjXf0=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha1-i813B3oNzjN4tXT+2ybSolO3PTY=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha1-5/sqAemcgwyU5mI82f77TI+1g0c=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha1-xSkJNy24uG4sVeBaiUADO1Zgo7I=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha1-xCe5vlpkwmL/mn63C1+7qt9EbGw=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha1-3JsUe6yi5sSzyFVxdB70hgpIkJc=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha1-zoZtEt8TwV5MmfBzo9Rm9uBkmzo=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha1-dGjjaS0B1inVlB5dg4F7uA+eObQ=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha1-pbwAY/sryrbQ7WPyoVN5WLwmnsY=", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha1-EAZO5E9DR7kMmgK0Rrv4CpFjKxI=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -499,6 +942,7 @@ "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.0", "@typescript-eslint/types": "8.56.0", @@ -757,6 +1201,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1480,6 +1925,48 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha1-70W0Y0ycnZeilq6kEUpfmED5VXg=", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1515,6 +2002,7 @@ "integrity": "sha512-20MV9SUdeN6Jd84xESsKhRly+/vxI+hwvpBMA93s+9dAcjdCuCojn4IqUGS3lvVaqjVYGYHSRMCpeFtF2rQYxQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -1745,6 +2233,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -2149,6 +2638,7 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.26.tgz", "integrity": "sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw==", "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -3732,6 +4222,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3807,6 +4298,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4122,6 +4614,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 3872e82..2da86e3 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "debugmcpextension", - "displayName": "DebugMCP", - "description": "Let AI agents debug your code inside VS Code — breakpoints, step-through execution, variable inspection, and expression evaluation. Automatically exposes itself as an MCP (Model Context Protocol) server for seamless integration with AI assistants.", - "version": "2.2.0", + "displayName": "DebugMCP — AI Agent Debugging for VS Code", + "description": "Your AI agent debugs for you — right inside VS Code. Let Copilot, Cline, Cursor, Codex & any MCP agent set breakpoints, step through code, and inspect variables live instead of guessing from logs.", + "version": "2.2.1", "publisher": "ozzafar", "author": { "name": "Oz Zafar", @@ -28,15 +28,34 @@ "breakpoints", "debugger", "ai agent", - "copilot" + "copilot", + "cline", + "cursor", + "windsurf", + "roo code", + "codex", + "claude", + "autonomous debugging", + "agent debugging", + "test debugging", + "pytest", + "jest", + "debug adapter protocol", + "dap" ], "engines": { "vscode": "^1.104.0" }, "icon": "assets/debug_mcp_icon.png", + "galleryBanner": { + "color": "#1e1e1e", + "theme": "dark" + }, "categories": [ "Debuggers", "AI", + "Chat", + "Machine Learning", "Programming Languages" ], "extensionDependencies": [], @@ -48,7 +67,7 @@ "onStartupFinished", "onDebug" ], - "main": "./out/extension.js", + "main": "./dist/extension.js", "contributes": { "commands": [ { @@ -96,10 +115,13 @@ } }, "scripts": { - "vscode:prepublish": "npm run compile", + "vscode:prepublish": "npm run package", "compile": "tsc -p ./", + "check-types": "tsc --noEmit -p ./", + "bundle": "node esbuild.js", + "package": "npm run check-types && node esbuild.js --production", "watch": "tsc -watch -p ./", - "pretest": "npm run compile && npm run lint", + "pretest": "npm run compile && npm run bundle && npm run lint", "lint": "eslint src", "test": "vscode-test" }, @@ -117,6 +139,7 @@ "@typescript-eslint/parser": "^8.56.0", "@vscode/test-cli": "^0.0.12", "@vscode/test-electron": "^2.5.2", + "esbuild": "^0.28.1", "eslint": "^10.0.1", "typescript": "^5.9.3" }, diff --git a/skills/debug-live/SKILL.md b/skills/debug-live/SKILL.md index 660d6d9..ac8c010 100644 --- a/skills/debug-live/SKILL.md +++ b/skills/debug-live/SKILL.md @@ -14,6 +14,7 @@ allowed-tools: - step_into - step_out - continue_execution + - pause_execution - get_variables_values - evaluate_expression --- @@ -57,7 +58,9 @@ If you can step through the code in a few tool calls, do that instead of specula program either hits a breakpoint (`stopped`) or runs to completion without pausing (`terminated`). 4. **Navigate and inspect.** Use `step_over`, `step_into`, `step_out`, `continue_execution` - to move through code. Use `get_variables_values` to see local/global state and + to move through code. Use `pause_execution` to interrupt a freely-running program + (e.g. a busy loop or embedded target) when there is no breakpoint to stop at. + Use `get_variables_values` to see local/global state and `evaluate_expression` to test hypotheses live (call methods, read properties, run list comprehensions, etc.). 5. **Find the root cause** (see framework below). Don't stop at the first wrong thing diff --git a/src/controlServer.ts b/src/controlServer.ts index cf72d1b..6a21512 100644 --- a/src/controlServer.ts +++ b/src/controlServer.ts @@ -91,6 +91,8 @@ export class ControlServer { return this.handler.handleStepOut(); case 'handleContinue': return this.handler.handleContinue(); + case 'handlePause': + return this.handler.handlePause(); case 'handleRestart': return this.handler.handleRestart(); case 'handleAddBreakpoint': diff --git a/src/debugMCPServer.ts b/src/debugMCPServer.ts index 121568f..39530ac 100644 --- a/src/debugMCPServer.ts +++ b/src/debugMCPServer.ts @@ -161,6 +161,14 @@ export class DebugMCPServer { const server = new McpServer({ name: 'debugmcp', version: '1.0.0', + }, { + // Surfaced to clients at `initialize`. Points agents at the + // `debug-live` Agent Skill, which the extension installs into the + // standard skills directories for harnesses that load skills. + instructions: 'These tools drive the VS Code debugger to investigate bugs, failing tests, ' + + 'wrong/null values and other "it doesn\'t work" reports by stepping through code. ' + + 'The companion "debug-live" Agent Skill describes the full interactive workflow: ' + + 'when to set breakpoints, how to step and inspect state, and how to do root-cause analysis.', }); this.setupTools(server, this.handlerFactory()); return server; @@ -206,7 +214,7 @@ export class DebugMCPServer { server.registerTool('start_debugging', { description: 'Start a VS Code debug session for a source file, optionally for a single test method. ' + 'Use when investigating bugs, failing tests, wrong/null variable values, unexpected runtime behavior, ' + - 'or any "it doesn\'t work" report where stepping through the code is cheaper than speculation.', + 'or any "it doesn\'t work" report. See the "debug-live" skill for the full investigation workflow.', inputSchema: { fileFullPath: z.string().describe('Full path to the source code file to debug'), workingDirectory: z.string().describe('Working directory for the debug session'), @@ -248,6 +256,11 @@ export class DebugMCPServer { description: 'Resume program execution until the next breakpoint is hit or the program completes.', }, async () => this.runTool('continue_execution', () => debuggingHandler.handleContinue())); + // Pause execution tool + server.registerTool('pause_execution', { + description: 'Interrupt a running program and stop at its current location, even when no breakpoint is set. Useful for busy loops or embedded/bare-metal targets running freely — then inspect variables or step from where it stopped.', + }, async () => this.runTool('pause_execution', () => debuggingHandler.handlePause())); + // Restart debugging tool server.registerTool('restart_debugging', { description: 'Restart the debug session from the beginning with the same configuration.', diff --git a/src/debuggingExecutor.ts b/src/debuggingExecutor.ts index c2d2cd8..39d0b68 100644 --- a/src/debuggingExecutor.ts +++ b/src/debuggingExecutor.ts @@ -30,6 +30,7 @@ export interface IDebuggingExecutor { stepInto(): Promise; stepOut(): Promise; continue(): Promise; + pause(): Promise; restart(): Promise; addBreakpoint(uri: vscode.Uri, line: number, condition?: string): Promise; removeBreakpoint(uri: vscode.Uri, line: number): Promise; @@ -305,6 +306,19 @@ export class DebuggingExecutor implements IDebuggingExecutor { } } + /** + * Execute pause command — interrupt a running program so execution stops at + * its current point (useful for embedded/bare-metal or busy-loop debugging + * where there is no breakpoint to stop at). + */ + public async pause(): Promise { + try { + await vscode.commands.executeCommand('workbench.action.debug.pause'); + } catch (error) { + throw new Error(`Failed to pause: ${error}`); + } + } + /** * Execute restart command */ diff --git a/src/debuggingHandler.ts b/src/debuggingHandler.ts index b04a785..cec55e5 100644 --- a/src/debuggingHandler.ts +++ b/src/debuggingHandler.ts @@ -16,6 +16,7 @@ export interface IDebuggingHandler { handleStepInto(): Promise; handleStepOut(): Promise; handleContinue(): Promise; + handlePause(): Promise; handleRestart(): Promise; handleAddBreakpoint(args: { fileFullPath: string; lineContent: string; condition?: string }): Promise; handleRemoveBreakpoint(args: { fileFullPath: string; line: number }): Promise; @@ -250,6 +251,31 @@ export class DebuggingHandler implements IDebuggingHandler { } } + /** + * Pause execution — interrupt a running program so it stops at its current + * location. Unlike a breakpoint, this works even when no breakpoint is set + * (e.g. a busy loop or an embedded/bare-metal target that is running freely). + */ + public async handlePause(): Promise { + try { + if (!(await this.executor.hasActiveSession())) { + throw new Error('Debug session is not ready. Please wait for initialization to complete.'); + } + + // Get the state before executing the command + const beforeState = await this.executor.getCurrentDebugState(this.numNextLines); + + await this.executor.pause(); + + // Wait for the debugger to stop (pause raises a 'stopped' event) + const afterState = await this.waitForStateChange(beforeState); + + return afterState.toString(); + } catch (error) { + throw new Error(`Error executing pause: ${error}`); + } + } + /** * Restart the debugging session */ diff --git a/src/routingDebuggingHandler.ts b/src/routingDebuggingHandler.ts index efb7171..ee1acc4 100644 --- a/src/routingDebuggingHandler.ts +++ b/src/routingDebuggingHandler.ts @@ -190,6 +190,10 @@ export class RoutingDebuggingHandler implements IDebuggingHandler { return this.forward('handleContinue', {}); } + public handlePause(): Promise { + return this.forward('handlePause', {}); + } + public handleRestart(): Promise { return this.forward('handleRestart', {}); } diff --git a/src/test/debuggingHandler.test.ts b/src/test/debuggingHandler.test.ts index d49e511..e2d2730 100644 --- a/src/test/debuggingHandler.test.ts +++ b/src/test/debuggingHandler.test.ts @@ -144,6 +144,7 @@ suite('DebuggingHandler waitForStateChange (event-driven)', () => { stepInto: async () => { /* noop */ }, stepOut: async () => { /* noop */ }, continue: async () => { /* noop */ }, + pause: async () => { /* noop */ }, restart: async () => { /* noop */ }, addBreakpoint: async () => { /* noop */ }, removeBreakpoint: async () => { /* noop */ }, diff --git a/src/test/routing.test.ts b/src/test/routing.test.ts index cec6a3b..a53ccf8 100644 --- a/src/test/routing.test.ts +++ b/src/test/routing.test.ts @@ -24,6 +24,7 @@ class RecordingHandler implements IDebuggingHandler { handleStepInto() { return this.record('stepInto', {}); } handleStepOut() { return this.record('stepOut', {}); } handleContinue() { return this.record('continue', {}); } + handlePause() { return this.record('pause', {}); } handleRestart() { return this.record('restart', {}); } handleAddBreakpoint(args: any) { return this.record('addBp', args); } handleRemoveBreakpoint(args: any) { return this.record('removeBp', args); } diff --git a/src/test/startDebuggingMatrix.test.ts b/src/test/startDebuggingMatrix.test.ts index 5b9feca..098934b 100644 --- a/src/test/startDebuggingMatrix.test.ts +++ b/src/test/startDebuggingMatrix.test.ts @@ -75,6 +75,7 @@ function makeMocks(opts: MockOpts) { stepInto: async () => { /* noop */ }, stepOut: async () => { /* noop */ }, continue: async () => { /* noop */ }, + pause: async () => { /* noop */ }, restart: async () => { /* noop */ }, addBreakpoint: async () => { /* noop */ }, removeBreakpoint: async () => { /* noop */ }, diff --git a/src/utils/agentConfigurationManager.ts b/src/utils/agentConfigurationManager.ts index 7534265..af23a90 100644 --- a/src/utils/agentConfigurationManager.ts +++ b/src/utils/agentConfigurationManager.ts @@ -189,14 +189,20 @@ export class AgentConfigurationManager { } /** - * Get the personal skills directory for a given agent. By convention we - * place skills alongside the agent's MCP config file (i.e. in a `skills/` - * sibling of the config file's parent directory). This matches harnesses - * like Copilot CLI (`~/.copilot/skills/`) and gives a sensible default - * for the other supported agents. + * Personal skill install targets, following the Agent Skills open standard + * (agentskills.io). `~/.agents/skills/` is the cross-agent location honored + * by skills-compatible harnesses (including VS Code agent mode and Copilot + * CLI); we also install into `~/.copilot/skills/` when a Copilot home exists. + * See issue #105. */ - private getSkillsDirForAgent(agent: AgentInfo): string { - return path.join(path.dirname(agent.configPath), 'skills'); + private getSkillInstallTargets(): string[] { + const home = os.homedir(); + const targets = [path.join(home, '.agents', 'skills', 'debug-live')]; + const copilotHome = process.env.COPILOT_HOME || path.join(home, '.copilot'); + if (fs.existsSync(copilotHome)) { + targets.push(path.join(copilotHome, 'skills', 'debug-live')); + } + return targets; } /** @@ -207,49 +213,59 @@ export class AgentConfigurationManager { } /** - * Copy the bundled debugmcp skill into the agent's personal skills - * directory. This is best-effort: if the bundled skill is missing or the - * copy fails, we log a warning but do not fail the MCP configuration. + * Install the bundled debugmcp skill into the standard personal skills + * directories (see getSkillInstallTargets). Agent-independent — the skill + * lives in one shared location per the Agent Skills open standard rather + * than being copied next to each agent's config. Returns the primary + * destination for UI, or null when nothing could be installed. Best-effort: + * failures are logged and skipped. */ - private async registerDebugMCPSkill(agent: AgentInfo): Promise { + private async installDebugMCPSkill(): Promise { const bundledSkillPath = this.getBundledSkillPath(); if (!fs.existsSync(bundledSkillPath)) { - console.warn(`Bundled debugmcp skill not found at ${bundledSkillPath}; skipping skill registration for ${agent.name}`); + console.warn(`Bundled debugmcp skill not found at ${bundledSkillPath}; skipping skill install`); return null; } - const skillsDir = this.getSkillsDirForAgent(agent); - const destination = path.join(skillsDir, 'debug-live'); - - try { - await fs.promises.mkdir(skillsDir, { recursive: true }); - await fs.promises.cp(bundledSkillPath, destination, { recursive: true, force: true }); - console.log(`Successfully registered debugmcp skill for ${agent.name} at ${destination}`); - - // Back-compat cleanup: earlier builds installed the skill under - // different directory names (`debug` in 1.2.0, later `really-debug`). - // Remove any stale copies so users don't end up with competing - // entries alongside the current `debug-live` skill. - const legacyDestinations = [ - path.join(skillsDir, 'debug'), - path.join(skillsDir, 'really-debug'), - ]; - for (const legacyDestination of legacyDestinations) { - if (fs.existsSync(legacyDestination)) { - try { - await fs.promises.rm(legacyDestination, { recursive: true, force: true }); - console.log(`Removed legacy debugmcp skill at ${legacyDestination}`); - } catch (cleanupError) { - console.warn(`Failed to remove legacy debugmcp skill at ${legacyDestination}:`, cleanupError); - } + let primaryDestination: string | null = null; + for (const destination of this.getSkillInstallTargets()) { + const skillsDir = path.dirname(destination); + try { + await fs.promises.mkdir(skillsDir, { recursive: true }); + await fs.promises.cp(bundledSkillPath, destination, { recursive: true, force: true }); + console.log(`Installed debugmcp skill at ${destination}`); + await this.removeLegacySkills(skillsDir); + if (!primaryDestination) { + primaryDestination = destination; } + } catch (error) { + console.warn(`Failed to install debugmcp skill at ${destination}:`, error); } + } - return destination; - } catch (error) { - console.warn(`Failed to register debugmcp skill for ${agent.name} at ${destination}:`, error); - return null; + return primaryDestination; + } + + /** + * Remove stale skill copies from earlier builds (`debug` in 1.2.0, later + * `really-debug`) so users don't end up with competing entries alongside + * the current `debug-live` skill. + */ + private async removeLegacySkills(skillsDir: string): Promise { + const legacyDestinations = [ + path.join(skillsDir, 'debug'), + path.join(skillsDir, 'really-debug'), + ]; + for (const legacyDestination of legacyDestinations) { + if (fs.existsSync(legacyDestination)) { + try { + await fs.promises.rm(legacyDestination, { recursive: true, force: true }); + console.log(`Removed legacy debugmcp skill at ${legacyDestination}`); + } catch (cleanupError) { + console.warn(`Failed to remove legacy debugmcp skill at ${legacyDestination}:`, cleanupError); + } + } } } @@ -379,7 +395,7 @@ export class AgentConfigurationManager { // Back-compat: existing Codex users had only the MCP server registered. // If DebugMCP is configured, also ensure the bundled skill is installed. if (this.hasCodexDebugMCPSection(configContent)) { - await this.ensureSkillRegistered(agent); + await this.ensureSkillRegistered(); } continue; @@ -431,7 +447,7 @@ export class AgentConfigurationManager { // Back-compat: existing users had only the MCP server registered. // If DebugMCP is configured, also ensure the bundled skill is installed. - await this.ensureSkillRegistered(agent); + await this.ensureSkillRegistered(); } catch (error) { console.error(`Error migrating config for ${agent.name}:`, error); // Continue with other agents even if one fails @@ -446,16 +462,16 @@ export class AgentConfigurationManager { } /** - * Ensure the bundled debugmcp skill is installed in the agent's personal - * skills directory. Safe to call on every activation — `registerDebugMCPSkill` - * uses `fs.cp` with `force: true`, so it will refresh stale copies but is a + * Ensure the bundled debugmcp skill is installed in the standard personal + * skills directories. Safe to call on every activation — `installDebugMCPSkill` + * uses `fs.cp` with `force: true`, so it refreshes stale copies but is a * no-op when the destination already matches. */ - private async ensureSkillRegistered(agent: AgentInfo): Promise { + private async ensureSkillRegistered(): Promise { try { - await this.registerDebugMCPSkill(agent); + await this.installDebugMCPSkill(); } catch (error) { - console.warn(`Failed to ensure skill registration for ${agent.name}:`, error); + console.warn('Failed to ensure skill installation:', error); } } @@ -508,7 +524,7 @@ export class AgentConfigurationManager { ); console.log(`Successfully added DebugMCP configuration to ${agent.name}`); - const skillPath = await this.registerDebugMCPSkill(agent); + const skillPath = await this.installDebugMCPSkill(); return { success: true, skillPath }; } @@ -542,7 +558,7 @@ export class AgentConfigurationManager { ); console.log(`Successfully added DebugMCP configuration to ${agent.name}`); - const skillPath = await this.registerDebugMCPSkill(agent); + const skillPath = await this.installDebugMCPSkill(); return { success: true, skillPath }; } catch (error) { console.error(`Error adding DebugMCP to ${agent.name}:`, error);