Files
LazyBearWorks/LazyBear.MCP/Services/ToolRegistry/ToolRegistryService.cs
Shahovalov MIkhail 9b27cd7dc2 fix: исправить навигацию клавиатуры в TUI через GlobalKeyboardService
- Добавить GlobalKeyboardService — выделенный поток с блокирующим
  Console.ReadKey, единственный источник клавишных событий для TUI
- Убрать FocusManager из App.razor: перехватывал Tab до компонентов
- Удалить @onkeydown с <Select>: RazorConsole не пробрасывает Tab/стрелки
  через этот механизм
- Использовать FocusedValue вместо Value в Select для корректной подсветки
- Обновить CLAUDE.md и AGENTS.md: архитектура TUI, RazorConsole gotchas
- Добавить docs/tui_log.md: разбор проблемы и справочник по RazorConsole

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 23:31:53 +03:00

115 lines
4.6 KiB
C#

using System.Collections.Concurrent;
namespace LazyBear.MCP.Services.ToolRegistry;
/// <summary>
/// Singleton. Хранит runtime-состояние включённости модулей и отдельных инструментов.
/// Thread-safe. Уведомляет подписчиков при любом изменении.
/// </summary>
public sealed class ToolRegistryService
{
private readonly ConcurrentDictionary<string, bool> _moduleEnabled = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, bool> _toolEnabled = new(StringComparer.OrdinalIgnoreCase);
private readonly List<IToolModule> _modules = [];
private readonly Lock _modulesLock = new();
/// <summary>Вызывается при любом изменении состояния.</summary>
public event Action? StateChanged;
// ── Регистрация ──────────────────────────────────────────────────────────
public void RegisterModule(IToolModule module)
{
lock (_modulesLock)
{
if (_modules.Any(m => string.Equals(m.ModuleName, module.ModuleName, StringComparison.OrdinalIgnoreCase)))
return;
_modules.Add(module);
}
_moduleEnabled.TryAdd(module.ModuleName, true);
foreach (var tool in module.ToolNames)
{
_toolEnabled.TryAdd(MakeKey(module.ModuleName, tool), true);
}
}
// ── Запросы состояния ────────────────────────────────────────────────────
public IReadOnlyList<IToolModule> GetModules()
{
lock (_modulesLock)
{
return _modules.ToList();
}
}
public bool IsModuleEnabled(string moduleName) =>
_moduleEnabled.GetValueOrDefault(moduleName, true);
public bool IsToolConfiguredEnabled(string moduleName, string toolName) =>
_toolEnabled.GetValueOrDefault(MakeKey(moduleName, toolName), true);
public bool IsToolEnabled(string moduleName, string toolName) =>
IsModuleEnabled(moduleName) &&
IsToolConfiguredEnabled(moduleName, toolName);
// ── Переключение ─────────────────────────────────────────────────────────
public void SetModuleEnabled(string moduleName, bool enabled)
{
_moduleEnabled[moduleName] = enabled;
StateChanged?.Invoke();
}
public void SetToolEnabled(string moduleName, string toolName, bool enabled)
{
_toolEnabled[MakeKey(moduleName, toolName)] = enabled;
StateChanged?.Invoke();
}
public void ToggleModule(string moduleName) =>
SetModuleEnabled(moduleName, !IsModuleEnabled(moduleName));
public void ToggleTool(string moduleName, string toolName) =>
SetToolEnabled(moduleName, toolName, !IsToolConfiguredEnabled(moduleName, toolName));
// ── Счётчики для Overview ─────────────────────────────────────────────────
public (int Active, int Total) GetToolCounts(string moduleName)
{
lock (_modulesLock)
{
var module = _modules.FirstOrDefault(m =>
string.Equals(m.ModuleName, moduleName, StringComparison.OrdinalIgnoreCase));
if (module is null) return (0, 0);
var total = module.ToolNames.Count;
var active = module.ToolNames.Count(t => IsToolEnabled(moduleName, t));
return (active, total);
}
}
public (int Enabled, int Total) GetConfiguredToolCounts(string moduleName)
{
lock (_modulesLock)
{
var module = _modules.FirstOrDefault(m =>
string.Equals(m.ModuleName, moduleName, StringComparison.OrdinalIgnoreCase));
if (module is null) return (0, 0);
var total = module.ToolNames.Count;
var enabled = module.ToolNames.Count(t => IsToolConfiguredEnabled(moduleName, t));
return (enabled, total);
}
}
// ── Helpers ───────────────────────────────────────────────────────────────
private static string MakeKey(string module, string tool) => $"{module}::{tool}";
}