free-code-dotnet/docs/基础设施设计/基础设施设计-状态管理.md
应文浩wenhao.ying@xiaobao100.com e25ac591a7 init easy-code
2026-04-06 07:24:24 +08:00

9.0 KiB
Raw Permalink Blame History

基础设施设计 — 状态管理

文档元数据

  • 项目名称: free-code
  • 文档类型: 基础设施设计
  • 原始代码来源: ../../src/state/5个文件
  • 原始设计意图: 用不可变全局状态串联 MCP、插件、通知、后台任务、桥接与推理状态并通过事件驱动方式向 UI 广播更新
  • 交叉引用: 基础设施设计总览 | 核心模块设计-查询引擎

设计目标

状态管理层负责承载应用运行态、MCP 状态、插件状态、通知状态与推理/猜测状态,并通过不可变更新维持可预测性。它是基础设施层的共享事实来源,供 UI、协议层和后台任务共同读取。

14.1 AppState 不可变 Record

/// <summary>
/// 应用全局状态 — 不可变 record
/// 对应原始 AppState 类型 (AppStateStore.ts 中 ~450 行类型定义)
/// </summary>
public sealed record AppState
{
    // === 核心状态 ===
    public SettingsJson Settings { get; init; } = new();
    public bool Verbose { get; init; }
    public ModelSetting MainLoopModel { get; init; }
    public ModelSetting MainLoopModelForSession { get; init; }
    public string? StatusLineText { get; init; }
    public ExpandedView ExpandedView { get; init; } = ExpandedView.None;
    public bool IsBriefOnly { get; init; }

    // === 权限 ===
    public ToolPermissionContext ToolPermissionContext { get; init; } = new();
    public string? Agent { get; init; }
    public bool KairosEnabled { get; init; }

    // === UI 状态 ===
    public int SelectedIPAgentIndex { get; init; } = -1;
    public int CoordinatorTaskIndex { get; init; } = -1;
    public ViewSelectionMode ViewSelectionMode { get; init; } = ViewSelectionMode.None;
    public FooterItem? FooterSelection { get; init; }

    // === 后台任务 ===
    public IReadOnlyDictionary<string, BackgroundTask> Tasks { get; init; } = new Dictionary<string, BackgroundTask>();
    public string? ForegroundedTaskId { get; init; }
    public string? ViewingAgentTaskId { get; init; }
    public IReadOnlyDictionary<string, AgentId> AgentNameRegistry { get; init; } = new Dictionary<string, AgentId>();

    // === MCP ===
    public McpState Mcp { get; init; } = new();

    // === 插件 ===
    public PluginState Plugins { get; init; } = new();

    // === 远程/桥接 ===
    public string? RemoteSessionUrl { get; init; }
    public RemoteConnectionStatus RemoteConnectionStatus { get; init; } = RemoteConnectionStatus.Connecting;
    public int RemoteBackgroundTaskCount { get; init; }

    // === 桥接 ===
    public bool ReplBridgeEnabled { get; init; }
    public bool ReplBridgeExplicit { get; init; }
    public bool ReplBridgeOutboundOnly { get; init; }
    public bool ReplBridgeConnected { get; init; }
    public bool ReplBridgeSessionActive { get; init; }
    public bool ReplBridgeReconnecting { get; init; }
    public string? ReplBridgeConnectUrl { get; init; }
    public string? ReplBridgeSessionUrl { get; init; }
    public string? ReplBridgeEnvironmentId { get; init; }
    public string? ReplBridgeSessionId { get; init; }
    public string? ReplBridgeError { get; init; }
    public string? ReplBridgeInitialName { get; init; }
    public bool ShowRemoteCallout { get; init; }

    // === 同伴 ===
    public string? CompanionReaction { get; init; }
    public long? CompanionPetAt { get; init; }

    // === 通知 ===
    public NotificationState Notifications { get; init; } = new();

    // === 记忆 ===
    public AgentDefinitionsResult AgentDefinitions { get; init; } = new();

    // === 文件历史 ===
    public FileHistoryState FileHistory { get; init; } = new();

    // === 归属 ===
    public AttributionState Attribution { get; init; } = new();

    // === TODO ===
    public IReadOnlyDictionary<string, TodoList> Todos { get; init; } = new Dictionary<string, TodoList>();

    // === 推测 ===
    public SpeculationState Speculation { get; init; } = SpeculationState.Idle;
    public long SpeculationSessionTimeSavedMs { get; init; }

    // === 其他 ===
    public bool? ThinkingEnabled { get; init; }
    public bool PromptSuggestionEnabled { get; init; }
    public EffortValue? EffortValue { get; init; }
    public bool FastMode { get; init; }
    public int AuthVersion { get; init; }
    public IReadOnlySet<string> ActiveOverlays { get; init; } = new HashSet<string>();
}

// === 嵌套状态类型 ===

public sealed record McpState
{
    public IReadOnlyList<MCPServerConnection> Clients { get; init; } = [];
    public IReadOnlyList<ITool> Tools { get; init; } = [];
    public IReadOnlyList<ICommand> Commands { get; init; } = [];
    public IReadOnlyDictionary<string, List<ServerResource>> Resources { get; init; } = new Dictionary<string, List<ServerResource>>();
    public int PluginReconnectKey { get; init; }
}

public sealed record PluginState
{
    public IReadOnlyList<LoadedPlugin> Enabled { get; init; } = [];
    public IReadOnlyList<LoadedPlugin> Disabled { get; init; } = [];
    public IReadOnlyList<ICommand> Commands { get; init; } = [];
    public IReadOnlyList<PluginError> Errors { get; init; } = [];
    public PluginInstallationStatus InstallationStatus { get; init; } = new();
    public bool NeedsRefresh { get; init; }
}

public sealed record NotificationState
{
    public Notification? Current { get; init; }
    public IReadOnlyList<Notification> Queue { get; init; } = [];
}

public record SpeculationState
{
    public static readonly SpeculationState Idle = new() { Status = "idle" };
    public string Status { get; init; } = "idle";
}

14.2 IAppStateStore 接口与 AppStateStore 实现

/// <summary>
/// 应用状态存储 — 事件驱动不可变状态管理
/// 对应原始 Store<T> + AppStateStore
/// 模式: Redux/Elm 风格 (updater 函数 + 变更通知)
/// </summary>
public interface IAppStateStore
{
    /// <summary>获取当前状态(不可变快照)</summary>
    AppState GetState();

    /// <summary>更新状态(通过 updater 函数)</summary>
    void Update(Func<AppState, AppState> updater);

    /// <summary>订阅状态变更</summary>
    IDisposable Subscribe(Action<AppState> listener);

    /// <summary>状态变更事件 (C# event 模式)</summary>
    event EventHandler<StateChangedEventArgs>? StateChanged;
}

public sealed class AppStateStore : IAppStateStore
{
    private AppState _state;
    private readonly object _lock = new();
    private readonly List<Action<AppState>> _listeners = new();
    private readonly Action<AppState, AppState>? _onChangeCallback;

    public event EventHandler<StateChangedEventArgs>? StateChanged;

    public AppStateStore()
    {
        _state = CreateDefaultState();
    }

    public AppState GetState()
    {
        lock (_lock) return _state;
    }

    public void Update(Func<AppState, AppState> updater)
    {
        AppState prev, next;
        lock (_lock)
        {
            prev = _state;
            next = updater(prev);
            if (ReferenceEquals(next, prev)) return; // 无变更
            _state = next;
        }

        // 通知变更
        _onChangeCallback?.Invoke(next, prev);
        StateChanged?.Invoke(this, new StateChangedEventArgs(prev, next));

        // 通知订阅者
        foreach (var listener in _listeners.ToArray())
            listener(next);
    }

    public IDisposable Subscribe(Action<AppState> listener)
    {
        lock (_lock) _listeners.Add(listener);
        return new Subscription(() =>
        {
            lock (_lock) _listeners.Remove(listener);
        });
    }

    private static AppState CreateDefaultState() => new AppState();

    private sealed class Subscription : IDisposable
    {
        private readonly Action _unsubscribe;
        public Subscription(Action unsubscribe) => _unsubscribe = unsubscribe;
        public void Dispose() => _unsubscribe();
    }
}

public sealed record StateChangedEventArgs(AppState OldState, AppState NewState);

14.3 StateSelectors

/// <summary>
/// 状态选择器 — 对应原始 selectors.ts
/// 从 AppState 中派生计算值,避免在组件中重复逻辑
/// </summary>
public static class StateSelectors
{
    public static bool IsBridgeReady(this AppState state) =>
        state.ReplBridgeEnabled && state.ReplBridgeConnected;

    public static bool IsBridgeConnected(this AppState state) =>
        state.ReplBridgeEnabled && state.ReplBridgeSessionActive;

    public static IReadOnlyList<BackgroundTask> GetBackgroundTasks(this AppState state) =>
        state.Tasks.Values.Where(t => t.Status is TaskStatus.Running or TaskStatus.Pending)
            .Where(t => t.IsBackgrounded)
            .ToList();

    public static bool HasActiveMcpServers(this AppState state) =>
        state.Mcp.Clients.Any(c => c.IsConnected);

    public static int GetActiveSessionCount(this AppState state) =>
        state.Tasks.Values.Count(t => t.Status == TaskStatus.Running);

    public static string? GetActiveModel(this AppState state) =>
        state.MainLoopModelForSession ?? state.MainLoopModel;
}