# 基础设施设计 — 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 补充说明 - 该层的重点不是本地执行,而是把远端工作项和本地会话生命周期做稳定映射。 - 所有状态变化最终都应回写到上层状态存储,避免桥接层形成隐式状态孤岛。