目录

后台任务管理 — 并发控制与 Tmux 集成

后台任务管理 — 并发控制与 Tmux 集成

源码路径:/mnt/e/code/cc/omo-code/src/features/background-agent/, src/features/tmux-subagent/ 核心文件:BackgroundManager.ts, TmuxSessionManager.ts, ConcurrencyManager.ts 技术栈:Promise + Polling + Tmux CLI


1. 概述

后台任务管理是 Oh-My-OpenAgent 实现并行多 Agent 的核心能力。它包含两部分:BackgroundManager(任务生命周期)和 TmuxSessionManager(实时可视化)。


2. BackgroundManager

2.1 核心架构

// src/features/background-agent/BackgroundManager.ts
export class BackgroundManager {
  private tasks: Map<string, BackgroundTask> = new Map()
  private concurrencyManager: ConcurrencyManager
  private circuitBreaker: CircuitBreaker

  constructor(config: BackgroundTaskConfig) {
    this.concurrencyManager = new ConcurrencyManager({
      perModel: config.max_per_model ?? 5,
      perProvider: config.max_per_provider ?? 5,
      global: config.max_global ?? 20,
    })

    this.circuitBreaker = new CircuitBreaker({
      failureThreshold: config.circuit_breaker?.failure_threshold ?? 3,
      resetTimeout: config.circuit_breaker?.reset_timeout ?? 60_000,
    })
  }
}

2.2 任务状态机

pending → running → completed
                   → error
                   → cancelled
                   → interrupt

2.3 并发控制

// src/features/background-agent/ConcurrencyManager.ts
export class ConcurrencyManager {
  checkLimit(model: string, provider: string): boolean {
    const modelCount = this.countByModel(model)
    const providerCount = this.countByProvider(provider)
    const globalCount = this.countGlobal()

    return (
      modelCount < this.config.perModel &&
      providerCount < this.config.perProvider &&
      globalCount < this.config.global
    )
  }

  enqueue(task: BackgroundTask): void {
    const queue = this.queues.get(task.model)
    queue?.push(task)
  }

  dequeue(model: string): BackgroundTask | undefined {
    return this.queues.get(model)?.shift()
  }
}

2.4 轮询与稳定性检测

// src/features/background-agent/polling.ts
export async function pollTask(
  taskId: string,
  interval = 3_000,
  stabilityWindow = 10_000,
): Promise<TaskResult> {
  let lastResult: string | undefined
  let stableSince: number | undefined

  while (true) {
    const task = await getTaskStatus(taskId)

    if (task.status === 'completed' || task.status === 'error') {
      return task
    }

    if (task.result === lastResult) {
      if (!stableSince) {
        stableSince = Date.now()
      } else if (Date.now() - stableSince > stabilityWindow) {
        // 结果稳定超过 10 秒,视为完成
        return { ...task, status: 'completed' }
      }
    } else {
      stableSince = undefined
      lastResult = task.result
    }

    await sleep(interval)
  }
}

3. Tmux 集成

3.1 TmuxSessionManager

// src/features/tmux-subagent/TmuxSessionManager.ts
export class TmuxSessionManager {
  private sessions: Map<string, TmuxPane> = new Map()

  async createPane(config: TmuxConfig): Promise<TmuxPane> {
    // 1. 创建 tmux window
    await this.runTmux(['new-window', '-n', config.name])

    // 2. 分割窗格(Grid 布局用于多 Agent 可视化)
    if (config.layout === 'grid') {
      await this.splitGrid(config.name, config.agents?.length ?? 2)
    }

    // 3. 在每个 pane 中启动 Agent
    for (let i = 0; i < config.agents.length; i++) {
      await this.runInPane(
        config.name,
        i,
        `opencode --agent ${config.agents[i]}`,
      )
    }

    const pane: TmuxPane = {
      id: config.name,
      agents: config.agents,
      layout: config.layout,
      createdAt: Date.now(),
    }

    this.sessions.set(config.name, pane)
    return pane
  }

  async sendInput(paneId: string, paneIndex: number, input: string): Promise<void> {
    // 向特定 pane 发送输入
    await this.runTmux([
      'send-keys',
      '-t',
      `${paneId}:${paneIndex}`,
      input,
      'Enter',
    ])
  }

  async capturePane(paneId: string, paneIndex: number): Promise<string> {
    const output = await this.runTmux([
      'capture-pane',
      '-t',
      `${paneId}:${paneIndex}`,
      '-p',
    ])
    return output
  }
}

3.2 Grid 布局

// 多 Agent 网格布局 (2x2 示例)
async splitGrid(sessionName: string, count: number): Promise<void> {
  const cols = Math.ceil(Math.sqrt(count))
  const rows = Math.ceil(count / cols)

  // 垂直分割 (cols - 1 次)
  for (let i = 1; i < cols; i++) {
    await this.runTmux(['split-window', '-h', '-t', `${sessionName}:0`])
  }

  // 水平分割 (rows - 1) * cols 次
  for (let r = 1; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      await this.runTmux(['split-window', '-v', '-t', `${sessionName}:${c}`])
    }
  }

  // 均分布局
  await this.runTmux(['select-layout', '-t', sessionName, 'even-horizontal'])
}

4. 后台任务与 Tmux 联动

// src/features/background-agent/background-tmux.ts
export async function launchBackgroundWithTmux(
  config: BackgroundTaskConfig,
  tmuxConfig: TmuxConfig,
  ctx: ToolContext,
): Promise<{ taskId: string; paneId: string }> {
  // 1. 创建 Tmux pane
  const pane = await tmuxManager.createPane({
    ...tmuxConfig,
    agents: [config.agent ?? 'sisyphus'],
  })

  // 2. 启动后台任务
  const task = await backgroundManager.spawn({
    ...config,
    tmuxPaneId: pane.id,
  })

  // 3. 监听完成事件
  backgroundManager.on('task:completed', async ({ taskId, result }) => {
    // 在 Tmux pane 中显示结果
    await tmuxManager.sendInput(pane.id, 0, `\n\n[TASK COMPLETED]\n${result}\n`)
    // 保持 pane 打开 5 秒
    await sleep(5_000)
    // 关闭 pane
    await tmuxManager.closePane(pane.id)
  })

  return { taskId: task.id, paneId: pane.id }
}

5. 与 OpenCode / Claude Code 对比

维度Claude CodeOpenCodeOh-My-OpenAgent
后台任务run_in_backgroundTaskTool 子会话BackgroundManager
并发控制per-model/provider 限制
断路器自动失败恢复
稳定性检测10s 结果不变视为完成
Tmux 集成实时可视化
轮询间隔3s 可配置

文档版本:v1.0 | 更新:2026-04-06