# 实现计划：acpx Backend 迁移

## 概述

将 claude-teams MCP server 中所有 backend 替换为 `acpx` CLI，完全去除 tmux 和 opencode HTTP API 依赖。按照从底层到上层的顺序实现：先建立 acpx_client 模块，再更新数据模型，然后重写 spawner，最后更新 server。

## 任务

- [x] 1. 新建 acpx_client.py 模块
  - 创建 `src/claude_teams/acpx_client.py`
  - 定义 `AcpxError` 异常类
  - 实现 `create_session(agent, session_name, acpx_binary, timeout)` 函数，执行 `acpx <agent> sessions new --name <session_name>`
  - 实现 `send_prompt(agent, session_name, prompt, acpx_binary, timeout)` 函数，执行 `acpx <agent> -s <session_name> '<prompt>' --no-wait`
  - 实现 `close_session(agent, session_name, acpx_binary, timeout)` 函数，执行 `acpx <agent> sessions close <session_name>`
  - 实现 `get_session_status(agent, session_name, acpx_binary, timeout)` 函数，执行 `acpx <agent> status -s <session_name>` 并返回状态字符串
  - 所有函数在 subprocess 返回非零退出码时抛出 `AcpxError`（包含 stderr 内容）
  - _需求：1.1、1.2、1.3、1.4、1.5、1.6、1.7_

  - [x]* 1.1 为 AcpxClient 编写属性测试
    - **属性 1：AcpxError 错误传播**
    - 对任意 agent/session_name 组合，mock subprocess 返回非零码，验证 `AcpxError` 被抛出
    - **验证：需求 1.5**
    - 使用 `hypothesis` 生成随机 agent 和 session_name 字符串，最少 100 次迭代
    - `# Feature: acpx-backend-migration, Property 1: AcpxError 错误传播`

  - [x]* 1.2 为 AcpxClient 编写单元测试
    - 测试 `acpx` 二进制不存在时抛出 `AcpxError`（边界条件 1.6）
    - 测试各函数在成功时正确调用 subprocess（示例测试 1.1-1.4）
    - 测试超时参数被正确传递（示例测试 1.7）

- [x] 2. 更新数据模型（models.py）
  - 修改 `TeammateMember`：删除 `tmux_pane_id`、`opencode_session_id`、`backend_type` 字段，新增 `acpx_session_name: str = Field(alias="acpxSessionName")`
  - 修改 `LeadMember`：删除 `tmux_pane_id` 字段
  - 修改 `ShutdownApproved`：删除 `pane_id`、`backend_type`、`session_id` 字段，新增 `acpx_session_name: str = Field(alias="acpxSessionName")`
  - _需求：3.1、3.2、3.3、3.4、3.5_

  - [x]* 2.1 为数据模型编写属性测试
    - **属性 2：Session Name 与 agent_id 一致性**
    - 对任意 name 和 team_name 组合，验证 `TeammateMember.acpx_session_name == TeammateMember.agent_id`（均为 `<name>@<team_name>` 格式）
    - **验证：需求 2.1、2.2、2.3**
    - `# Feature: acpx-backend-migration, Property 2: Session Name 与 agent_id 一致性`

  - [x]* 2.2 为数据模型编写单元测试
    - 验证 `TeammateMember` 不包含 `tmux_pane_id`、`opencode_session_id`、`backend_type` 字段
    - 验证 `LeadMember` 不包含 `tmux_pane_id` 字段
    - 验证 `ShutdownApproved` 包含 `acpx_session_name` 字段

- [x] 3. 重写 spawner.py
  - 删除所有 tmux 相关函数：`build_tmux_spawn_args`、`kill_tmux_pane`、`use_tmux_windows`、`build_spawn_command`
  - 删除所有 opencode 相关函数：`build_opencode_attach_command`、`discover_opencode_models`
  - 删除 `opencode_client` 导入，添加 `acpx_client` 导入
  - 重写 `spawn_teammate` 函数签名，移除 `backend_type`、`opencode_binary`、`opencode_server_url`、`opencode_agent` 参数，添加 `acpx_binary` 参数
  - 在 `spawn_teammate` 中调用 `acpx_client.create_session(subagent_type, session_name)` 创建 session
  - 在 `spawn_teammate` 中调用 `acpx_client.send_prompt(subagent_type, session_name, wrapped_prompt)` 发送初始 prompt
  - 在 `spawn_teammate` 失败时调用 `acpx_client.close_session` 清理 session
  - 新增 `kill_acpx_session(agent, session_name, acpx_binary)` 函数（best-effort，失败只记录日志）
  - 将 `_OPENCODE_PROMPT_WRAPPER` 重命名为 `_PROMPT_WRAPPER`
  - _需求：4.1、4.2、4.3、4.4、4.5、4.6、4.7_

  - [-]* 3.1 为 spawner 编写属性测试
    - **属性 3：Spawn 失败时的 session 清理**
    - 对任意有效 spawn 参数，mock `send_prompt` 抛出异常，验证 `close_session` 被调用
    - **验证：需求 4.5**
    - `# Feature: acpx-backend-migration, Property 3: Spawn 失败时的 session 清理`

  - [x] 3.2 为 spawner 编写属性测试
    - **属性 5：Prompt 包装完整性**
    - 对任意 name、team_name 和 prompt 组合，验证包装后的 prompt 包含原始 prompt、name 和 team_name
    - **验证：需求 4.7**
    - `# Feature: acpx-backend-migration, Property 5: Prompt 包装完整性`

- [x] 4. 检查点 - 确保所有测试通过
  - 确保所有测试通过，如有问题请告知。

- [x] 5. 更新 server.py
  - 替换导入：移除 `opencode_client`、`OpenCodeAPIError`、`kill_tmux_pane`、`use_tmux_windows`、`tmux_introspection` 相关导入，添加 `acpx_client`、`AcpxError` 导入
  - 移除全局变量和函数：`KNOWN_CLIENTS`、`_VALID_BACKENDS`、`_parse_backends_env`、`_push_to_opencode_session`、`_push_to_lead`、`_cleanup_opencode_session`、`_discover_lead_opencode_session`、`_get_lead_session`
  - 更新 `app_lifespan`：移除 opencode 相关发现逻辑，改为发现 `acpx` 二进制，若未找到则抛出 `FileNotFoundError`
  - 简化 `HarnessDetectionMiddleware`：移除 backend 检测逻辑
  - 简化 `spawn_teammate_tool`：移除 `backend_type` 参数和 opencode 特定逻辑，直接调用 `spawn_teammate`
  - 更新 `check_teammate`：移除 `include_output`、`output_lines`、`notify_after_minutes` 参数，改用 `acpx_client.get_session_status` 判断存活状态
  - 更新 `force_kill_teammate`：改用 `acpx_client.close_session` 替代 `kill_tmux_pane` 和 `_cleanup_opencode_session`
  - 更新 `process_shutdown_approved`：改用 `acpx_client.close_session`
  - 更新 `send_message`：移除所有 opencode push 调用（`_push_to_opencode_session`、`_push_to_lead`）
  - 更新 `shutdown_response` 处理：使用 `acpx_session_name` 替代 `tmux_pane_id`/`backend_type`/`opencode_session_id`
  - _需求：5.1、5.2、5.3、5.4、5.5、5.6、5.7、5.8、5.9、5.10、7.1、7.2、7.3_

  - [x]* 5.1 为 check_teammate 编写属性测试
    - **属性 4：check_teammate 状态映射**
    - 对任意状态字符串，验证 `"alive"` 映射为 `alive=True`，`"dead"` 或 `AcpxError` 映射为 `alive=False`
    - **验证：需求 8.2**
    - `# Feature: acpx-backend-migration, Property 4: check_teammate 状态映射`

  - [x]* 5.2 为 server 编写单元测试
    - 测试 `force_kill_teammate` 调用 `acpx_client.close_session`
    - 测试 `process_shutdown_approved` 调用 `acpx_client.close_session`
    - 测试 `send_message` 不再调用任何 opencode 相关函数

- [x] 6. 删除 tmux_introspection.py
  - 删除 `src/claude_teams/tmux_introspection.py` 文件
  - 确认没有其他文件导入该模块
  - _需求：6.1、6.2_

- [x] 7. 最终检查点 - 确保所有测试通过
  - 确保所有测试通过，如有问题请告知。

## 备注

- 标有 `*` 的子任务为可选项，可跳过以加快 MVP 进度
- 每个任务引用了具体的需求条目以便追溯
- 检查点确保增量验证
- 属性测试验证通用正确性，单元测试验证具体示例和边界条件
