From 5d19d4a8840cacde67cdfced8d69e199c0038803 Mon Sep 17 00:00:00 2001 From: "huang.zewei" Date: Mon, 14 Sep 2026 15:56:49 +0800 Subject: [PATCH] feat(tools): add conditional breakpoint support --- CLAUDE.md | 5 +- README.md | 5 +- .../RpcClient.cs | 11 +- .../Tools/DebuggerTools.cs | 88 +++++++++++++- .../Models/DebuggerModels.cs | 17 +++ .../RpcContracts.cs | 9 +- .../DebuggerBreakpointTests.cs | 60 ++++++++++ .../Services/IVisualStudioService.cs | 9 +- .../Services/RpcServer.cs | 11 +- .../Services/VisualStudioService.cs | 108 +++++++++++++++++- 10 files changed, 307 insertions(+), 16 deletions(-) create mode 100644 src/CodingWithCalvin.MCPServer.Tests/DebuggerBreakpointTests.cs diff --git a/CLAUDE.md b/CLAUDE.md index 285a2bc..4eb5de7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -176,7 +176,8 @@ dotnet build src/CodingWithCalvin.VSMCP/CodingWithCalvin.VSMCP.csproj - `symbol_workspace` - Search for symbols across the solution ### Debugger Tools -- `debugger_add_breakpoint` - Add a breakpoint at a file and line +- `breakpoint_enable` - Enable or disable a breakpoint at a file and line +- `debugger_add_breakpoint` - Add a breakpoint, optionally with a condition and hit-count rule - `debugger_break` - Pause execution (Ctrl+Alt+Break) - `debugger_continue` - Continue execution (F5) - `debugger_evaluate` - Evaluate an expression in the current debug context @@ -184,7 +185,7 @@ dotnet build src/CodingWithCalvin.VSMCP/CodingWithCalvin.VSMCP.csproj - `debugger_get_locals` - Get local variables in current frame - `debugger_launch` - Start debugging (F5), optionally for a specific project - `debugger_launch_without_debugging` - Start without debugger (Ctrl+F5), optionally for a specific project -- `debugger_list_breakpoints` - List all breakpoints +- `debugger_list_breakpoints` - List all breakpoints, including conditions and hit-count rules - `debugger_remove_breakpoint` - Remove a breakpoint - `debugger_set_variable` - Set the value of a local variable - `debugger_status` - Get current debugger state diff --git a/README.md b/README.md index bc7356b..c3fa612 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,8 @@ | Tool | Description | |------|-------------| -| `debugger_add_breakpoint` | Add a breakpoint at a file and line | +| `breakpoint_enable` | Enable or disable a breakpoint at a file and line | +| `debugger_add_breakpoint` | Add a breakpoint, optionally with a condition and hit-count rule | | `debugger_break` | Pause execution (Ctrl+Alt+Break) | | `debugger_continue` | Continue execution (F5) | | `debugger_evaluate` | Evaluate an expression in the current debug context | @@ -96,7 +97,7 @@ | `debugger_get_locals` | Get local variables in current frame | | `debugger_launch` | Start debugging (F5), optionally for a specific project | | `debugger_launch_without_debugging` | Start without debugger (Ctrl+F5), optionally for a specific project | -| `debugger_list_breakpoints` | List all breakpoints | +| `debugger_list_breakpoints` | List all breakpoints, including conditions and hit-count rules | | `debugger_remove_breakpoint` | Remove a breakpoint | | `debugger_set_variable` | Set the value of a local variable | | `debugger_status` | Get current debugger state | diff --git a/src/CodingWithCalvin.MCPServer.Server/RpcClient.cs b/src/CodingWithCalvin.MCPServer.Server/RpcClient.cs index e85d276..64afa7a 100644 --- a/src/CodingWithCalvin.MCPServer.Server/RpcClient.cs +++ b/src/CodingWithCalvin.MCPServer.Server/RpcClient.cs @@ -155,7 +155,16 @@ public Task FindReferencesAsync(string path, int line, int col public Task DebugStepIntoAsync() => Proxy.DebugStepIntoAsync(); public Task DebugStepOutAsync() => Proxy.DebugStepOutAsync(); - public Task DebugAddBreakpointAsync(string file, int line) => Proxy.DebugAddBreakpointAsync(file, line); + public Task DebugAddBreakpointAsync( + string file, + int line, + string? condition = null, + BreakpointConditionType conditionType = BreakpointConditionType.WhenTrue, + int? hitCount = null, + BreakpointHitCountType hitCountType = BreakpointHitCountType.None) + => Proxy.DebugAddBreakpointAsync(file, line, condition, conditionType, hitCount, hitCountType); + public Task DebugSetBreakpointEnabledAsync(string file, int line, bool enabled) + => Proxy.DebugSetBreakpointEnabledAsync(file, line, enabled); public Task DebugRemoveBreakpointAsync(string file, int line) => Proxy.DebugRemoveBreakpointAsync(file, line); public Task> DebugGetBreakpointsAsync() => Proxy.DebugGetBreakpointsAsync(); public Task> DebugGetLocalsAsync() => Proxy.DebugGetLocalsAsync(); diff --git a/src/CodingWithCalvin.MCPServer.Server/Tools/DebuggerTools.cs b/src/CodingWithCalvin.MCPServer.Server/Tools/DebuggerTools.cs index cc7ae9f..8b3e65c 100644 --- a/src/CodingWithCalvin.MCPServer.Server/Tools/DebuggerTools.cs +++ b/src/CodingWithCalvin.MCPServer.Server/Tools/DebuggerTools.cs @@ -1,6 +1,8 @@ +using System; using System.ComponentModel; using System.Text.Json; using System.Threading.Tasks; +using CodingWithCalvin.MCPServer.Shared.Models; using ModelContextProtocol.Server; namespace CodingWithCalvin.MCPServer.Server.Tools; @@ -112,13 +114,77 @@ public async Task DebugStepOutAsync() } [McpServerTool(Name = "debugger_add_breakpoint", Destructive = false)] - [Description("Add a breakpoint at a specific file and line number. Works in any debugger mode (Design, Run, or Break). The file path must be absolute.")] - public async Task DebugAddBreakpointAsync(string path, int line) - { - var success = await _rpcClient.DebugAddBreakpointAsync(path, line); + [Description("Add a breakpoint at a specific file and line number, optionally with a conditional expression and/or hit-count rule. Works in any debugger mode (Design, Run, or Break). The file path must be absolute.")] + public async Task DebugAddBreakpointAsync( + [Description("Absolute path to the source file.")] string path, + [Description("One-based source line number.")] int line, + [Description("Optional expression that controls whether execution breaks.")] string? condition = null, + [Description("How to evaluate condition: whenTrue or whenChanged. Defaults to whenTrue.")] string conditionType = "whenTrue", + [Description("Optional positive hit-count target. When supplied without hitCountType, the equal rule is used.")] int? hitCount = null, + [Description("How to apply hitCount: none, equal, greaterOrEqual, or multiple. Defaults to none.")] string hitCountType = "none") + { + if (line < 1) + { + return "Invalid line. The line number must be greater than zero."; + } + + if (!TryParseConditionType(conditionType, out var parsedConditionType)) + { + return $"Invalid conditionType '{conditionType}'. Valid values are: whenTrue, whenChanged."; + } + + if (!TryParseHitCountType(hitCountType, out var parsedHitCountType)) + { + return $"Invalid hitCountType '{hitCountType}'. Valid values are: none, equal, greaterOrEqual, multiple."; + } + + if (string.IsNullOrWhiteSpace(condition) && parsedConditionType == BreakpointConditionType.WhenChanged) + { + return "A condition expression is required when conditionType is whenChanged."; + } + + if (hitCount.HasValue && hitCount.Value < 1) + { + return "Invalid hitCount. The hit-count target must be greater than zero."; + } + + if (parsedHitCountType != BreakpointHitCountType.None && !hitCount.HasValue) + { + return $"A hitCount value is required when hitCountType is {hitCountType}."; + } + + if (hitCount.HasValue && parsedHitCountType == BreakpointHitCountType.None) + { + parsedHitCountType = BreakpointHitCountType.Equal; + } + + var success = await _rpcClient.DebugAddBreakpointAsync( + path, + line, + condition, + parsedConditionType, + hitCount, + parsedHitCountType); return success ? $"Breakpoint added at {path}:{line}" : $"Failed to add breakpoint at {path}:{line}"; } + [McpServerTool(Name = "breakpoint_enable", Destructive = false)] + [Description("Enable or disable a breakpoint at a specific file and line number. Returns whether a matching breakpoint was found and updated.")] + public async Task DebugSetBreakpointEnabledAsync( + [Description("Absolute path to the source file.")] string path, + [Description("One-based source line number.")] int line, + [Description("True to enable the breakpoint; false to disable it.")] bool enabled) + { + if (line < 1) + { + return "Invalid line. The line number must be greater than zero."; + } + + var success = await _rpcClient.DebugSetBreakpointEnabledAsync(path, line, enabled); + var action = enabled ? "enabled" : "disabled"; + return success ? $"Breakpoint {action} at {path}:{line}" : $"No breakpoint found at {path}:{line}"; + } + [McpServerTool(Name = "debugger_remove_breakpoint", Destructive = true)] [Description("Remove a breakpoint at a specific file and line number. Returns whether a breakpoint was found and removed.")] public async Task DebugRemoveBreakpointAsync(string path, int line) @@ -128,7 +194,7 @@ public async Task DebugRemoveBreakpointAsync(string path, int line) } [McpServerTool(Name = "debugger_list_breakpoints", ReadOnly = true)] - [Description("List all breakpoints in the current solution. Returns file, line, column, function name, condition, enabled state, and hit count for each breakpoint.")] + [Description("List all breakpoints in the current solution. Returns file, line, column, function name, condition and condition type, enabled state, current hits, hit-count target, and hit-count rule for each breakpoint.")] public async Task DebugListBreakpointsAsync() { var breakpoints = await _rpcClient.DebugGetBreakpointsAsync(); @@ -171,4 +237,16 @@ public async Task DebugGetCallStackAsync() var callStack = await _rpcClient.DebugGetCallStackAsync(); return JsonSerializer.Serialize(callStack, _jsonOptions); } + + private static bool TryParseConditionType(string value, out BreakpointConditionType conditionType) + { + return Enum.TryParse(value, ignoreCase: true, out conditionType) + && Enum.IsDefined(typeof(BreakpointConditionType), conditionType); + } + + private static bool TryParseHitCountType(string value, out BreakpointHitCountType hitCountType) + { + return Enum.TryParse(value, ignoreCase: true, out hitCountType) + && Enum.IsDefined(typeof(BreakpointHitCountType), hitCountType); + } } diff --git a/src/CodingWithCalvin.MCPServer.Shared/Models/DebuggerModels.cs b/src/CodingWithCalvin.MCPServer.Shared/Models/DebuggerModels.cs index 3ee98b5..4c9f02c 100644 --- a/src/CodingWithCalvin.MCPServer.Shared/Models/DebuggerModels.cs +++ b/src/CodingWithCalvin.MCPServer.Shared/Models/DebuggerModels.cs @@ -11,6 +11,20 @@ public class DebuggerStatus public string CurrentFunction { get; set; } = string.Empty; } +public enum BreakpointConditionType +{ + WhenTrue, + WhenChanged +} + +public enum BreakpointHitCountType +{ + None, + Equal, + GreaterOrEqual, + Multiple +} + public class BreakpointInfo { public string File { get; set; } = string.Empty; @@ -18,8 +32,11 @@ public class BreakpointInfo public int Column { get; set; } public string FunctionName { get; set; } = string.Empty; public string Condition { get; set; } = string.Empty; + public string ConditionType { get; set; } = string.Empty; public bool Enabled { get; set; } public int CurrentHits { get; set; } + public int HitCountTarget { get; set; } + public string HitCountType { get; set; } = string.Empty; } public class LocalVariableInfo diff --git a/src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs b/src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs index 8ec913f..7dd0a83 100644 --- a/src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs +++ b/src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs @@ -59,7 +59,14 @@ public interface IVisualStudioRpc Task DebugStepIntoAsync(); Task DebugStepOutAsync(); - Task DebugAddBreakpointAsync(string file, int line); + Task DebugAddBreakpointAsync( + string file, + int line, + string? condition = null, + BreakpointConditionType conditionType = BreakpointConditionType.WhenTrue, + int? hitCount = null, + BreakpointHitCountType hitCountType = BreakpointHitCountType.None); + Task DebugSetBreakpointEnabledAsync(string file, int line, bool enabled); Task DebugRemoveBreakpointAsync(string file, int line); Task> DebugGetBreakpointsAsync(); Task> DebugGetLocalsAsync(); diff --git a/src/CodingWithCalvin.MCPServer.Tests/DebuggerBreakpointTests.cs b/src/CodingWithCalvin.MCPServer.Tests/DebuggerBreakpointTests.cs new file mode 100644 index 0000000..d16560b --- /dev/null +++ b/src/CodingWithCalvin.MCPServer.Tests/DebuggerBreakpointTests.cs @@ -0,0 +1,60 @@ +using CodingWithCalvin.MCPServer.Services; +using CodingWithCalvin.MCPServer.Shared.Models; +using EnvDTE; +using Xunit; + +namespace CodingWithCalvin.MCPServer.Tests; + +public class DebuggerBreakpointTests +{ + [Fact] + public void ConditionTypes_MapToDteAndApiNames() + { + Assert.Equal( + dbgBreakpointConditionType.dbgBreakpointConditionTypeWhenTrue, + VisualStudioService.ToDteConditionType(BreakpointConditionType.WhenTrue)); + Assert.Equal( + dbgBreakpointConditionType.dbgBreakpointConditionTypeWhenChanged, + VisualStudioService.ToDteConditionType(BreakpointConditionType.WhenChanged)); + Assert.Equal( + "whenTrue", + VisualStudioService.GetConditionTypeName(dbgBreakpointConditionType.dbgBreakpointConditionTypeWhenTrue)); + Assert.Equal( + "whenChanged", + VisualStudioService.GetConditionTypeName(dbgBreakpointConditionType.dbgBreakpointConditionTypeWhenChanged)); + } + + [Fact] + public void HitCountTypes_MapToDteAndApiNames() + { + Assert.Equal(dbgHitCountType.dbgHitCountTypeNone, VisualStudioService.ToDteHitCountType(BreakpointHitCountType.None)); + Assert.Equal(dbgHitCountType.dbgHitCountTypeEqual, VisualStudioService.ToDteHitCountType(BreakpointHitCountType.Equal)); + Assert.Equal(dbgHitCountType.dbgHitCountTypeGreaterOrEqual, VisualStudioService.ToDteHitCountType(BreakpointHitCountType.GreaterOrEqual)); + Assert.Equal(dbgHitCountType.dbgHitCountTypeMultiple, VisualStudioService.ToDteHitCountType(BreakpointHitCountType.Multiple)); + + Assert.Equal("none", VisualStudioService.GetHitCountTypeName(dbgHitCountType.dbgHitCountTypeNone)); + Assert.Equal("equal", VisualStudioService.GetHitCountTypeName(dbgHitCountType.dbgHitCountTypeEqual)); + Assert.Equal("greaterOrEqual", VisualStudioService.GetHitCountTypeName(dbgHitCountType.dbgHitCountTypeGreaterOrEqual)); + Assert.Equal("multiple", VisualStudioService.GetHitCountTypeName(dbgHitCountType.dbgHitCountTypeMultiple)); + } + + [Theory] + [InlineData(1, null, BreakpointHitCountType.None, true)] + [InlineData(0, null, BreakpointHitCountType.None, false)] + [InlineData(1, 1, BreakpointHitCountType.Equal, true)] + [InlineData(1, 5, BreakpointHitCountType.GreaterOrEqual, true)] + [InlineData(1, 2, BreakpointHitCountType.Multiple, true)] + [InlineData(1, null, BreakpointHitCountType.Equal, false)] + [InlineData(1, 0, BreakpointHitCountType.Equal, false)] + [InlineData(1, 1, BreakpointHitCountType.None, false)] + public void BreakpointConfiguration_ValidatesLineAndHitCount( + int line, + int? hitCount, + BreakpointHitCountType hitCountType, + bool expected) + { + Assert.Equal( + expected, + VisualStudioService.IsValidBreakpointConfiguration(line, hitCount, hitCountType)); + } +} diff --git a/src/CodingWithCalvin.MCPServer/Services/IVisualStudioService.cs b/src/CodingWithCalvin.MCPServer/Services/IVisualStudioService.cs index 6540efd..b8114f3 100644 --- a/src/CodingWithCalvin.MCPServer/Services/IVisualStudioService.cs +++ b/src/CodingWithCalvin.MCPServer/Services/IVisualStudioService.cs @@ -55,7 +55,14 @@ public interface IVisualStudioService Task DebugStepIntoAsync(); Task DebugStepOutAsync(); - Task DebugAddBreakpointAsync(string file, int line); + Task DebugAddBreakpointAsync( + string file, + int line, + string? condition = null, + BreakpointConditionType conditionType = BreakpointConditionType.WhenTrue, + int? hitCount = null, + BreakpointHitCountType hitCountType = BreakpointHitCountType.None); + Task DebugSetBreakpointEnabledAsync(string file, int line, bool enabled); Task DebugRemoveBreakpointAsync(string file, int line); Task> DebugGetBreakpointsAsync(); Task> DebugGetLocalsAsync(); diff --git a/src/CodingWithCalvin.MCPServer/Services/RpcServer.cs b/src/CodingWithCalvin.MCPServer/Services/RpcServer.cs index d49d0c3..6ae60c2 100644 --- a/src/CodingWithCalvin.MCPServer/Services/RpcServer.cs +++ b/src/CodingWithCalvin.MCPServer/Services/RpcServer.cs @@ -264,7 +264,16 @@ public Task FindReferencesAsync(string path, int line, int col public Task DebugStepIntoAsync() => _vsService.DebugStepIntoAsync(); public Task DebugStepOutAsync() => _vsService.DebugStepOutAsync(); - public Task DebugAddBreakpointAsync(string file, int line) => _vsService.DebugAddBreakpointAsync(file, line); + public Task DebugAddBreakpointAsync( + string file, + int line, + string? condition = null, + BreakpointConditionType conditionType = BreakpointConditionType.WhenTrue, + int? hitCount = null, + BreakpointHitCountType hitCountType = BreakpointHitCountType.None) + => _vsService.DebugAddBreakpointAsync(file, line, condition, conditionType, hitCount, hitCountType); + public Task DebugSetBreakpointEnabledAsync(string file, int line, bool enabled) + => _vsService.DebugSetBreakpointEnabledAsync(file, line, enabled); public Task DebugRemoveBreakpointAsync(string file, int line) => _vsService.DebugRemoveBreakpointAsync(file, line); public Task> DebugGetBreakpointsAsync() => _vsService.DebugGetBreakpointsAsync(); public Task> DebugGetLocalsAsync() => _vsService.DebugGetLocalsAsync(); diff --git a/src/CodingWithCalvin.MCPServer/Services/VisualStudioService.cs b/src/CodingWithCalvin.MCPServer/Services/VisualStudioService.cs index fa8aa66..664f09f 100644 --- a/src/CodingWithCalvin.MCPServer/Services/VisualStudioService.cs +++ b/src/CodingWithCalvin.MCPServer/Services/VisualStudioService.cs @@ -46,6 +46,51 @@ private static string NormalizePath(string path) return Path.GetFullPath(path.Replace('/', '\\')); } + internal static dbgBreakpointConditionType ToDteConditionType(BreakpointConditionType conditionType) => + conditionType switch + { + BreakpointConditionType.WhenTrue => dbgBreakpointConditionType.dbgBreakpointConditionTypeWhenTrue, + BreakpointConditionType.WhenChanged => dbgBreakpointConditionType.dbgBreakpointConditionTypeWhenChanged, + _ => throw new ArgumentOutOfRangeException(nameof(conditionType)) + }; + + internal static dbgHitCountType ToDteHitCountType(BreakpointHitCountType hitCountType) => + hitCountType switch + { + BreakpointHitCountType.None => dbgHitCountType.dbgHitCountTypeNone, + BreakpointHitCountType.Equal => dbgHitCountType.dbgHitCountTypeEqual, + BreakpointHitCountType.GreaterOrEqual => dbgHitCountType.dbgHitCountTypeGreaterOrEqual, + BreakpointHitCountType.Multiple => dbgHitCountType.dbgHitCountTypeMultiple, + _ => throw new ArgumentOutOfRangeException(nameof(hitCountType)) + }; + + internal static string GetConditionTypeName(dbgBreakpointConditionType conditionType) => + conditionType switch + { + dbgBreakpointConditionType.dbgBreakpointConditionTypeWhenTrue => "whenTrue", + dbgBreakpointConditionType.dbgBreakpointConditionTypeWhenChanged => "whenChanged", + _ => conditionType.ToString() + }; + + internal static string GetHitCountTypeName(dbgHitCountType hitCountType) => + hitCountType switch + { + dbgHitCountType.dbgHitCountTypeNone => "none", + dbgHitCountType.dbgHitCountTypeEqual => "equal", + dbgHitCountType.dbgHitCountTypeGreaterOrEqual => "greaterOrEqual", + dbgHitCountType.dbgHitCountTypeMultiple => "multiple", + _ => hitCountType.ToString() + }; + + internal static bool IsValidBreakpointConfiguration( + int line, + int? hitCount, + BreakpointHitCountType hitCountType) => + line > 0 + && (hitCountType == BreakpointHitCountType.None + ? !hitCount.HasValue + : hitCount.HasValue && hitCount.Value > 0); + internal static bool MatchesBuildConfiguration( string candidateConfiguration, string candidatePlatform, @@ -1771,10 +1816,22 @@ public async Task DebugStepOutAsync() } } - public async Task DebugAddBreakpointAsync(string file, int line) + public async Task DebugAddBreakpointAsync( + string file, + int line, + string? condition = null, + BreakpointConditionType conditionType = BreakpointConditionType.WhenTrue, + int? hitCount = null, + BreakpointHitCountType hitCountType = BreakpointHitCountType.None) { using var activity = VsixTelemetry.Tracer.StartActivity("DebugAddBreakpoint"); + if (!IsValidBreakpointConfiguration(line, hitCount, hitCountType)) + { + activity?.SetStatus(ActivityStatusCode.Error, "Invalid breakpoint configuration"); + return false; + } + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var dte = await GetDteAsync(); var debugger = (Debugger2)dte.Debugger; @@ -1782,7 +1839,14 @@ public async Task DebugAddBreakpointAsync(string file, int line) try { var normalizedPath = NormalizePath(file); - debugger.Breakpoints.Add(Function: "", File: normalizedPath, Line: line); + debugger.Breakpoints.Add( + Function: "", + File: normalizedPath, + Line: line, + Condition: condition ?? string.Empty, + ConditionType: ToDteConditionType(conditionType), + HitCount: hitCount.GetValueOrDefault(), + HitCountType: ToDteHitCountType(hitCountType)); return true; } catch (Exception ex) @@ -1793,6 +1857,41 @@ public async Task DebugAddBreakpointAsync(string file, int line) } } + public async Task DebugSetBreakpointEnabledAsync(string file, int line, bool enabled) + { + using var activity = VsixTelemetry.Tracer.StartActivity("DebugSetBreakpointEnabled"); + + if (line < 1) + { + activity?.SetStatus(ActivityStatusCode.Error, "Invalid breakpoint line"); + return false; + } + + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var dte = await GetDteAsync(); + var debugger = (Debugger2)dte.Debugger; + + try + { + foreach (Breakpoint breakpoint in debugger.Breakpoints) + { + if (breakpoint.File != null && PathsEqual(breakpoint.File, file) && breakpoint.FileLine == line) + { + breakpoint.Enabled = enabled; + return true; + } + } + + return false; + } + catch (Exception ex) + { + activity?.SetStatus(ActivityStatusCode.Error, ex.Message); + activity?.RecordException(ex); + return false; + } + } + public async Task DebugRemoveBreakpointAsync(string file, int line) { using var activity = VsixTelemetry.Tracer.StartActivity("DebugRemoveBreakpoint"); @@ -1844,8 +1943,11 @@ public async Task> DebugGetBreakpointsAsync() Column = bp.FileColumn, FunctionName = bp.FunctionName ?? string.Empty, Condition = bp.Condition ?? string.Empty, + ConditionType = GetConditionTypeName(bp.ConditionType), Enabled = bp.Enabled, - CurrentHits = bp.CurrentHits + CurrentHits = bp.CurrentHits, + HitCountTarget = bp.HitCountTarget, + HitCountType = GetHitCountTypeName(bp.HitCountType) }); } catch (Exception ex)