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

204 lines
7.7 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.

# 基础设施设计 — IDE 桥接
## 文档元数据
- 项目名称: free-code
- 文档类型: 基础设施设计
- 原始代码来源: `../../src/bridge/`32个文件
- 原始设计意图: 将 claude.ai 远程控制、会话启动、工作轮询和 IDE 侧事件流统一到一个可恢复的桥接服务中
- 交叉引用: [基础设施设计总览](基础设施设计.md) | [核心模块设计-查询引擎](../核心模块设计/核心模块设计-查询引擎-QueryEngine.md)
## 设计目标
IDE 桥接层负责把编辑器、运行时与后台工作流连接起来,提供会话启动、消息转发、任务轮询和工作区隔离能力。它既要支撑单会话模型,也要允许 worktree 隔离与环境注册/注销。
## 12.1 IBridgeService 接口
```csharp
/// <summary>
/// IDE 桥接服务 — 实现 claude.ai 远程控制功能
/// 对应原始 bridgeMain.ts / replBridge.ts
/// </summary>
public interface IBridgeService
{
/// <summary>注册环境到 claude.ai</summary>
Task<BridgeEnvironment> RegisterEnvironmentAsync();
/// <summary>轮询获取工作项</summary>
Task<WorkItem?> PollForWorkAsync(CancellationToken ct);
/// <summary>启动会话</summary>
Task<SessionHandle> SpawnSessionAsync(SessionSpawnOptions options);
/// <summary>确认工作项</summary>
Task AcknowledgeWorkAsync(string workId, string sessionToken);
/// <summary>发送权限响应</summary>
Task SendPermissionResponseAsync(string sessionId, PermissionResponse response);
/// <summary>心跳(延长工作租约)</summary>
Task HeartbeatAsync(string workId, string sessionToken);
/// <summary>停止工作</summary>
Task StopWorkAsync(string workId);
/// <summary>注销环境</summary>
Task DeregisterEnvironmentAsync();
/// <summary>桥接状态</summary>
BridgeStatus Status { get; }
}
```
## 12.2 BridgeConfig 与 SpawnMode
```csharp
/// <summary>
/// 桥接配置 — 对应原始 BridgeConfig
/// </summary>
public record BridgeConfig
{
public required string Directory { get; init; }
public required string MachineName { get; init; }
public required string Branch { get; init; }
public string? GitRepoUrl { get; init; }
public int MaxSessions { get; init; } = 1;
public SpawnMode SpawnMode { get; init; } = SpawnMode.Worktree;
public bool Verbose { get; init; }
public bool Sandbox { get; init; }
public required Guid BridgeId { get; init; }
public required string WorkerType { get; init; } // "claude_code" | "claude_code_assistant"
public required Guid EnvironmentId { get; init; }
public string? ReuseEnvironmentId { get; init; }
public required string ApiBaseUrl { get; init; }
public required string SessionIngressUrl { get; init; }
public int SessionTimeoutMs { get; init; } = 24 * 60 * 60 * 1000; // 24h
}
/// <summary>会话生成模式</summary>
public enum SpawnMode
{
SingleSession, // 单次会话, 结束即销毁
Worktree, // 持久, 每会话隔离 git worktree
SameDir // 持久, 共享工作目录
}
```
## 12.3 BridgeService 实现
```csharp
public sealed class BridgeService : IBridgeService, IAsyncDisposable
{
private readonly IBridgeApiClient _apiClient;
private readonly ISessionSpawner _sessionSpawner;
private readonly IBridgeLogger _logger;
private readonly BridgeConfig _config;
private readonly ConcurrentDictionary<string, SessionHandle> _activeSessions = new();
private string? _environmentId;
private string? _environmentSecret;
private BridgeStatus _status = BridgeStatus.Idle;
public async Task<BridgeEnvironment> RegisterEnvironmentAsync()
{
var result = await _apiClient.RegisterBridgeEnvironment(_config);
_environmentId = result.EnvironmentId;
_environmentSecret = result.EnvironmentSecret;
_status = BridgeStatus.Registered;
return new BridgeEnvironment(result.EnvironmentId, result.EnvironmentSecret);
}
/// <summary>
/// 主轮询循环 — 对应原始 bridgeMain.ts 的 pollForWork loop
/// </summary>
public async Task RunAsync(CancellationToken ct)
{
await RegisterEnvironmentAsync();
_logger.PrintBanner(_config, _environmentId!);
while (!ct.IsCancellationRequested)
{
try
{
var work = await PollForWorkAsync(ct);
if (work == null) continue;
_status = BridgeStatus.Attached;
_logger.LogSessionStart(work.Id, work.Data.ToString());
// 解码 work secret → 获取 session token
var secret = DecodeWorkSecret(work.Secret);
await AcknowledgeWorkAsync(work.Id, secret.SessionIngressToken);
// 生成会话 (可能使用 worktree 隔离)
var sessionDir = await PrepareSessionDirectoryAsync(work);
var handle = await _sessionSpawner.SpawnAsync(new SessionSpawnOptions
{
SessionId = work.Id,
SdkUrl = secret.ApiBaseUrl,
AccessToken = secret.SessionIngressToken,
WorkingDirectory = sessionDir,
}, ct);
_activeSessions[work.Id] = handle;
// 等待会话完成
_ = MonitorSessionAsync(work.Id, handle, ct);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_logger.LogError($"Poll error: {ex.Message}");
await Task.Delay(CalculateBackoff(), ct);
}
}
}
private async Task MonitorSessionAsync(
string sessionId, SessionHandle handle, CancellationToken ct)
{
var doneStatus = await handle.Done;
_activeSessions.TryRemove(sessionId, out _);
_logger.LogSessionComplete(sessionId, 0);
_status = _activeSessions.IsEmpty ? BridgeStatus.Registered : BridgeStatus.Attached;
}
public async ValueTask DisposeAsync()
{
foreach (var handle in _activeSessions.Values)
handle.Kill();
if (_environmentId != null)
await DeregisterEnvironmentAsync();
}
}
```
## 12.4 IBridgeApiClient
```csharp
/// <summary>
/// 桥接 API 客户端 — 对应原始 bridgeApi.ts
/// </summary>
public interface IBridgeApiClient
{
Task<(string EnvironmentId, string EnvironmentSecret)> RegisterBridgeEnvironment(BridgeConfig config);
Task<WorkResponse?> PollForWork(string envId, string envSecret, CancellationToken ct);
Task AcknowledgeWork(string envId, string workId, string sessionToken);
Task StopWork(string envId, string workId);
Task DeregisterEnvironment(string envId);
Task SendPermissionResponseEvent(string sessionId, PermissionResponseEvent evt, string token);
Task ArchiveSession(string sessionId);
Task HeartbeatWork(string envId, string workId, string sessionToken);
}
```
## 12.5 生命周期与传输策略
- 环境注册先于轮询;只有拿到 `EnvironmentId` / `EnvironmentSecret` 后才开始接收工作项。
- `RunAsync` 负责持续轮询,`SpawnSessionAsync` 负责把单个 work item 落到独立会话中。
- `Worktree` 模式用于最强隔离,`SameDir` 用于低开销复用,`SingleSession` 用于短生命周期任务。
- 传输层上保持 API 侧长轮询/轮询式控制,避免把 IDE 控制流绑死在单一 socket 连接上。
- `HeartbeatAsync` 用于延长工作租约,防止长任务被服务端提前回收。
## 12.6 补充说明
- 该层的重点不是本地执行,而是把远端工作项和本地会话生命周期做稳定映射。
- 所有状态变化最终都应回写到上层状态存储,避免桥接层形成隐式状态孤岛。