254 lines
9.0 KiB
Markdown
254 lines
9.0 KiB
Markdown
# 基础设施设计 — 状态管理
|
||
|
||
## 文档元数据
|
||
- 项目名称: 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;
|
||
}
|
||
```
|