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