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

7.7 KiB
Raw Blame History

基础设施设计 — IDE 桥接

文档元数据

  • 项目名称: free-code
  • 文档类型: 基础设施设计
  • 原始代码来源: ../../src/bridge/32个文件
  • 原始设计意图: 将 claude.ai 远程控制、会话启动、工作轮询和 IDE 侧事件流统一到一个可恢复的桥接服务中
  • 交叉引用: 基础设施设计总览 | 核心模块设计-查询引擎

设计目标

IDE 桥接层负责把编辑器、运行时与后台工作流连接起来,提供会话启动、消息转发、任务轮询和工作区隔离能力。它既要支撑单会话模型,也要允许 worktree 隔离与环境注册/注销。

12.1 IBridgeService 接口

/// <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

/// <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 实现

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

/// <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 补充说明

  • 该层的重点不是本地执行,而是把远端工作项和本地会话生命周期做稳定映射。
  • 所有状态变化最终都应回写到上层状态存储,避免桥接层形成隐式状态孤岛。