free-code-dotnet/docs/UI与扩展设计/UI与扩展设计-特性开关系统.md
应文浩wenhao.ying@xiaobao100.com e25ac591a7 init easy-code
2026-04-06 07:24:24 +08:00

271 lines
10 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 .NET 10 重写
> 所属模块: [UI 与扩展设计](UI与扩展设计.md)
> 原始代码: `scripts/build.ts` `feature()` 编译时开关 + 运行时 GrowthBook
---
## 概述
特性开关系统控制 free-code 的 88 个功能标志,其中 54 个可编译、34 个损坏。原始实现有两个层次:编译时用 `scripts/build.ts``feature()` 函数注入 `#define`,运行时用 GrowthBook SDK 从云端拉取实验配置。
.NET 重写将这两个层次统一到同一套接口下。`FeatureFlags` 静态类持有全部 54 个常量,供编译时 `#if` 和运行时查询共同使用;`IFeatureFlagService` 抽象运行时查询;`FeatureFlagService``appsettings.json``FeatureFlags` 配置节读取初始值,取代原始 GrowthBook 的云端下发机制,确保零网络依赖。
---
## 18.1 FeatureFlags 常量清单
```csharp
/// <summary>
/// 特性开关常量 — 编译时 #if + 运行时查询
/// 对应原始 88 个 feature flags54 可编译34 损坏)
/// </summary>
public static class FeatureFlags
{
// ===== 交互与 UI =====
public const string Ultraplan = "ULTRAPLAN";
public const string Ultrathink = "ULTRATHINK";
public const string VoiceMode = "VOICE_MODE";
public const string TokenBudget = "TOKEN_BUDGET";
public const string HistoryPicker = "HISTORY_PICKER";
public const string MessageActions = "MESSAGE_ACTIONS";
public const string QuickSearch = "QUICK_SEARCH";
public const string ShotStats = "SHOT_STATS";
// ===== Agent、记忆与规划 =====
public const string BuiltinExplorePlanAgents = "BUILTIN_EXPLORE_PLAN_AGENTS";
public const string VerificationAgent = "VERIFICATION_AGENT";
public const string AgentTriggers = "AGENT_TRIGGERS";
public const string AgentTriggersRemote = "AGENT_TRIGGERS_REMOTE";
public const string ExtractMemories = "EXTRACT_MEMORIES";
public const string CompactionReminders = "COMPACTION_REMINDERS";
public const string CachedMicrocompact = "CACHED_MICROCOMPACT";
public const string TeamMem = "TEAMMEM";
// ===== 工具与基础设施 =====
public const string BridgeMode = "BRIDGE_MODE";
public const string BashClassifier = "BASH_CLASSIFIER";
public const string PromptCacheBreakDetection = "PROMPT_CACHE_BREAK_DETECTION";
// ===== 其余 35 个可编译标志 =====
public const string Buddy = "BUDDY";
public const string V2Todo = "V2_TODO";
public const string EnableLspTool = "ENABLE_LSP_TOOL";
public const string ContextCollapse = "CONTEXT_COLLAPSE";
public const string TokenBudgetWarning = "TOKEN_BUDGET_WARNING";
public const string MemoryAutoExtract = "MEMORY_AUTO_EXTRACT";
public const string CompactHistory = "COMPACT_HISTORY";
public const string PermissionClassifier = "PERMISSION_CLASSIFIER";
public const string AsyncToolExecution = "ASYNC_TOOL_EXECUTION";
public const string Speculation = "SPECULATION";
public const string PromptSuggestion = "PROMPT_SUGGESTION";
public const string SkillImprovement = "SKILL_IMPROVEMENT";
public const string AgentSwarm = "ENABLE_AGENT_SWARMS";
public const string WorkflowTool = "WORKFLOW_TOOL";
public const string MonitorTool = "MONITOR_TOOL";
public const string McpSkills = "MCP_SKILLS";
public const string ChicagoMcp = "CHICAGO_MCP";
public const string EnableDeskApi = "ENABLE_DESK_API";
public const string Heapdump = "HEAPDUMP";
public const string EffortFlag = "EFFORT";
public const string Advisor = "ADVISOR";
public const string ThinkingBudget = "THINKING_BUDGET";
public const string RemoteEnv = "REMOTE_ENV";
public const string NotifyTool = "NOTIFY_TOOL";
public const string CaptureTool = "CAPTURE_TOOL";
public const string ReplayUserMessages = "REPLAY_USER_MESSAGES";
public const string TelemetryFlush = "TELEMETRY_FLUSH";
public const string StripUnusedImports = "STRIP_UNUSED_IMPORTS";
public const string ToolResultStorage = "TOOL_RESULT_STORAGE";
public const string McpStreaming = "MCP_STREAMING";
public const string PlanMode = "PLAN_MODE";
public const string PlanModeV2 = "PLAN_MODE_V2";
public const string RemoteControl = "REMOTE_CONTROL";
public const string ConsentedRemoteControl = "CONSENTED_REMOTE_CONTROL";
public const string CcrV2 = "CCR_V2";
public const string EnableSandbox = "ENABLE_SANDBOX";
public const string DiffBasedEdit = "DIFF_BASED_EDIT";
public const string OutputTruncation = "OUTPUT_TRUNCATION";
public const string WebSearchTool = "WEB_SEARCH_TOOL";
public const string PythonTool = "PYTHON_TOOL";
public const string NotebookEditTool = "NOTEBOOK_EDIT_TOOL";
public const string DedupeToolCalls = "DEDUPE_TOOL_CALLS";
public const string IdeIntegration = "IDE_INTEGRATION";
public const string RateLimitUi = "RATE_LIMIT_UI";
public const string TokenCounting = "TOKEN_COUNTING";
}
```
---
## 标志分类一览
| 类别 | 标志数量 | 说明 |
|---|---|---|
| 交互与 UI | 8 | 提示词增强、历史记录、语音模式等 |
| Agent、记忆与规划 | 8 | 多 Agent、记忆提取、规划模式等 |
| 工具与基础设施 | 3 | IDE 桥接、分类器、缓存检测 |
| 其余可编译标志 | 35 | 各类实验功能 |
| **合计可编译** | **54** | |
| 损坏标志(不含)| 34 | 需重建,详见 FEATURES.md |
---
## 18.2 IFeatureFlagService 接口
```csharp
/// <summary>
/// 运行时特性开关查询接口
/// </summary>
public interface IFeatureFlagService
{
/// <summary>查询指定标志是否启用</summary>
bool IsEnabled(string flag);
/// <summary>动态设置标志状态(用于测试或运行时覆盖)</summary>
void SetFlag(string flag, bool enabled);
}
```
---
## 18.3 FeatureFlagService 实现
```csharp
/// <summary>
/// 运行时特性开关实现
/// 编译时使用 #if FLAG_NAME运行时使用 IsEnabled()
/// 对应原始 GrowthBook SDK 的本地替代
/// </summary>
public sealed class FeatureFlagService : IFeatureFlagService
{
private readonly ConcurrentDictionary<string, bool> _flags = new();
public FeatureFlagService(IConfiguration config)
{
// 从 appsettings.json 的 FeatureFlags 节加载
var section = config.GetSection("FeatureFlags");
foreach (var child in section.GetChildren())
_flags[child.Key] = bool.Parse(child.Value ?? "false");
}
public bool IsEnabled(string flag)
=> _flags.GetValueOrDefault(flag, false);
public void SetFlag(string flag, bool enabled)
=> _flags[flag] = enabled;
}
```
---
## 双模式设计
特性开关系统同时支持两种工作模式,对应原始实现的两个不同层次。
### 编译时模式(`#if`
```csharp
// 编译时完全消除死代码,减小二进制体积
#if ULTRAPLAN
services.AddSingleton<IUltraplanService, UltraplanService>();
#endif
#if VOICE_MODE
services.AddSingleton<IVoiceService, VoiceService>();
#endif
```
编译时标志通过 MSBuild 属性注入,对应原始 `build.ts``feature()` 函数:
```xml
<!-- Directory.Build.props -->
<PropertyGroup Condition="'$(FEATURE_ULTRAPLAN)' == 'true'">
<DefineConstants>$(DefineConstants);ULTRAPLAN</DefineConstants>
</PropertyGroup>
```
构建命令示例:
```bash
# 全功能构建(对应原始 build:dev:full
dotnet build -p:FEATURE_ULTRAPLAN=true -p:FEATURE_VOICE_MODE=true ...
# 仅启用语音模式(对应原始默认构建)
dotnet build -p:FEATURE_VOICE_MODE=true
```
### 运行时模式(`IsEnabled()`
```csharp
// 不重新编译就能切换,由配置文件驱动
if (_features.IsEnabled(FeatureFlags.TokenBudget))
{
// 显示 token 预算组件
}
if (_features.IsEnabled(FeatureFlags.ExtractMemories))
{
await _memoryService.TryExtractAsync(messages);
}
```
`appsettings.json` 配置示例:
```json
{
"FeatureFlags": {
"TOKEN_BUDGET": true,
"HISTORY_PICKER": true,
"EXTRACT_MEMORIES": false,
"ULTRAPLAN": false
}
}
```
---
## 原始实现对比
| 原始TypeScript| .NET 重写 | 说明 |
|---|---|---|
| `scripts/build.ts` `feature("FLAG")` | MSBuild `-p:FEATURE_FLAG=true` | 编译时开关 |
| `#if defined(FLAG)` in bundled output | `#if FLAG_NAME` in C# | 死代码消除 |
| GrowthBook SDK云端下发| `FeatureFlagService`(配置文件)| 运行时开关 |
| `growthbook.isOn("flag-name")` | `_features.IsEnabled(FeatureFlags.Xxx)` | 查询 API |
| 34 损坏标志(缺少资源/类型)| 保留常量,实现体标记 TODO | 向后兼容占位 |
**关键差异**.NET 重写移除了 GrowthBook 的网络依赖。原始实现在每次启动时联系 GrowthBook 服务器更新标志状态,这是一个隐式的遥测上报渠道。`FeatureFlagService` 完全本地化,配置通过 `appsettings.json` 或环境变量覆盖,无任何出站请求。
---
## DI 注册
```csharp
// Program.cs 或服务注册扩展方法
services.AddSingleton<IFeatureFlagService, FeatureFlagService>();
// 使用示例(构造函数注入)
public sealed class SomeService(IFeatureFlagService features)
{
public void DoWork()
{
if (features.IsEnabled(FeatureFlags.AsyncToolExecution))
{
// 异步工具执行路径
}
}
}
```
---
## 参考资料
- [UI 与扩展设计 — 总览](UI与扩展设计.md)
- [UI 与扩展设计 — Terminal.Gui 终端 UI](UI与扩展设计-Terminal-Gui终端UI.md)
- [核心模块设计 — 查询引擎 (QueryEngine)](../核心模块设计/核心模块设计-查询引擎-QueryEngine.md)
- [测试与构建](../测试与构建/)
- [FEATURES.md](../../../FEATURES.md)(完整 88 个标志审计)