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

254 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 基础设施设计 — 状态管理
## 文档元数据
- 项目名称: free-code
- 文档类型: 基础设施设计
- 原始代码来源: `../../src/state/`5个文件
- 原始设计意图: 用不可变全局状态串联 MCP、插件、通知、后台任务、桥接与推理状态并通过事件驱动方式向 UI 广播更新
- 交叉引用: [基础设施设计总览](基础设施设计.md) | [核心模块设计-查询引擎](../核心模块设计/核心模块设计-查询引擎-QueryEngine.md)
## 设计目标
状态管理层负责承载应用运行态、MCP 状态、插件状态、通知状态与推理/猜测状态,并通过不可变更新维持可预测性。它是基础设施层的共享事实来源,供 UI、协议层和后台任务共同读取。
## 14.1 AppState 不可变 Record
```csharp
/// <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 实现
```csharp
/// <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
```csharp
/// <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;
}
```