# 基础设施设计 — IDE 桥接
## 文档元数据
- 项目名称: free-code
- 文档类型: 基础设施设计
- 原始代码来源: `../../src/bridge/`(32个文件)
- 原始设计意图: 将 claude.ai 远程控制、会话启动、工作轮询和 IDE 侧事件流统一到一个可恢复的桥接服务中
- 交叉引用: [基础设施设计总览](基础设施设计.md) | [核心模块设计-查询引擎](../核心模块设计/核心模块设计-查询引擎-QueryEngine.md)
## 设计目标
IDE 桥接层负责把编辑器、运行时与后台工作流连接起来,提供会话启动、消息转发、任务轮询和工作区隔离能力。它既要支撑单会话模型,也要允许 worktree 隔离与环境注册/注销。
## 12.1 IBridgeService 接口
```csharp
///
/// IDE 桥接服务 — 实现 claude.ai 远程控制功能
/// 对应原始 bridgeMain.ts / replBridge.ts
///
public interface IBridgeService
{
/// 注册环境到 claude.ai
Task RegisterEnvironmentAsync();
/// 轮询获取工作项
Task PollForWorkAsync(CancellationToken ct);
/// 启动会话
Task SpawnSessionAsync(SessionSpawnOptions options);
/// 确认工作项
Task AcknowledgeWorkAsync(string workId, string sessionToken);
/// 发送权限响应
Task SendPermissionResponseAsync(string sessionId, PermissionResponse response);
/// 心跳(延长工作租约)
Task HeartbeatAsync(string workId, string sessionToken);
/// 停止工作
Task StopWorkAsync(string workId);
/// 注销环境
Task DeregisterEnvironmentAsync();
/// 桥接状态
BridgeStatus Status { get; }
}
```
## 12.2 BridgeConfig 与 SpawnMode
```csharp
///
/// 桥接配置 — 对应原始 BridgeConfig
///
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
}
/// 会话生成模式
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 _activeSessions = new();
private string? _environmentId;
private string? _environmentSecret;
private BridgeStatus _status = BridgeStatus.Idle;
public async Task RegisterEnvironmentAsync()
{
var result = await _apiClient.RegisterBridgeEnvironment(_config);
_environmentId = result.EnvironmentId;
_environmentSecret = result.EnvironmentSecret;
_status = BridgeStatus.Registered;
return new BridgeEnvironment(result.EnvironmentId, result.EnvironmentSecret);
}
///
/// 主轮询循环 — 对应原始 bridgeMain.ts 的 pollForWork loop
///
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
///
/// 桥接 API 客户端 — 对应原始 bridgeApi.ts
///
public interface IBridgeApiClient
{
Task<(string EnvironmentId, string EnvironmentSecret)> RegisterBridgeEnvironment(BridgeConfig config);
Task 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 补充说明
- 该层的重点不是本地执行,而是把远端工作项和本地会话生命周期做稳定映射。
- 所有状态变化最终都应回写到上层状态存储,避免桥接层形成隐式状态孤岛。