iOfficeAI / iOfficeAI/AionUi

RFC: Codex 双轨收口与 Legacy 会话迁移方案(ACP 收口 + Session 接管)

Open
#1,237 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
32.9k
Forks
3.4k
Avg merge
3h 39m
Merged PRs (30d)
54

Description

## 背景 / 动机
仓库内当前同时存在两条 Codex 链路:

- **Legacy Codex**:`conversation.type = 'codex'` → `CodexAgentManager` → `CodexAgent` → `CodexConnection` → 本地 codex CLI MCP
- **ACP Codex**:`conversation.type = 'acp' && conversation.extra.backend = 'codex'` → `AcpAgentManager` → `AcpConnection.connectCodex()` → `codex-acp`

此前 issue 的目标是“直接移除 legacy,并让历史 `type='codex'` 会话不可见、不可进入、不可继续对话”。结合后续代码调研,这个目标需要调整:

1. **新建 Codex 会话必须统一收口到 ACP Codex**
2. **历史 Legacy Codex 会话当前不能直接下掉,必须继续可见、可进入、可继续对话**
3. **迁移不能只做历史消息可见性复制,而要尽量实现对底层原生 Codex session 的接管**

因此,本 issue 调整为“**双轨收口 + 低频专用迁移**”方案,而不是“立即删除 legacy runtime”。

---

## 新目标(Goals)
1. **停止继续产生新的 Legacy Codex 会话**
- 所有“新建 Codex 会话”统一创建为:
- `conversation.type = 'acp'`
- `conversation.extra.backend = 'codex'`

2. **保留历史 Legacy Codex 会话能力**
- 历史 `type='codex'` 会话继续:
- 出现在历史列表
- 可以进入会话页
- 可以继续发送消息
- 可以继续使用 legacy runtime

3. **为 Legacy Codex 会话补充“可迁移”基础能力**
- 在 Phase 1 中增加真实原生 session 标识的采集、反查、回填、状态标记能力
- 为后续无损迁移做准备

4. **在 Phase 2 提供低频专用迁移工具**
- 对“已确认可接管”的 legacy 会话,创建新的 ACP Codex 会话
- 通过 `session/load` 接管底层原生 Codex session
- 目标是让新会话继承**真实运行时上下文**,而不是只复制历史消息

---

## 非目标(Non-goals)
- **Phase 1 不删除** Legacy Codex runtime
- **Phase 1 不隐藏** 历史 `type='codex'` 会话
- **Phase 1 不阻止** 历史 `type='codex'` 会话进入与继续对话
- 不承诺所有历史 legacy 会话都能无损迁移
- 不在当前阶段修改 `codex-acp` 上游实现

---

## 当前已知情况(代码调研结论)

### 1. 新建入口当前仍会创建 Legacy `type='codex'`
当前仓库中仍有多条入口直接创建 legacy:

- `src/process/services/conversationService.ts`
- `src/process/initAgent.ts`
- `src/channels/gateway/ActionExecutor.ts`
- `src/channels/actions/SystemActions.ts`
- `src/renderer/components/AgentSetupCard.tsx`

这意味着 **Phase 1 的第一目标是“封住新建入口”**,而不是删除老链路。

### 2. 历史 Legacy Codex 会话当前仍然可见、可打开、可继续发送消息
相关链路仍然存在:

- 列表层未过滤 `type='codex'`
- 路由打开旧会话时仍会 `conversation.get -> openTab(data)`
- `WorkerManage` 仍可基于 `conversation.type === 'codex'` 构建 `CodexAgentManager`
- `conversationBridge.sendMessage` 仍保留 `task.type === 'codex'` 的发送分支
- `ChatConversation` 仍会为 `type='codex'` 渲染 `CodexChat`

这说明 **历史 legacy 会话不是“已经废弃不可用”,而是当前产品里真实可运行的一部分**。

### 3. ACP Codex 主链路已经具备 session 恢复能力
AionUi 当前 ACP Codex 已支持:

- 创建 ACP session
- 持久化 `acpSessionId`
- 通过 `session/load` 恢复 Codex ACP session

相关入口:

- `src/agent/acp/AcpConnection.ts`
- `src/agent/acp/index.ts`
- `src/common/storage.ts`

这也是后续迁移的基础能力。

### 4. Legacy Codex 当前“重启后再次打开旧会话”并没有恢复真实底层上下文
这是目前最关键的新发现。

当前 legacy 的“恢复”分成两层:

- **UI/消息历史恢复**:数据库里的 conversation/message 会被重新加载,所以用户能看到旧历史
- **底层 runtime 恢复**:如果进程还活着、内存里的 task 没丢,则后续消息会继续复用内存里的原生 session

但是在“**应用重启后再次打开旧会话**”场景下:

- `WorkerManage` 会重新 build 一个新的 `CodexAgentManager`
- 新 manager 的 `isFirstMessage` 会重新回到 `true`
- 用户下一次发送消息时,会重新走 `newSession(...)`
- 当前实现不会把旧消息历史重新注入到底层 Codex native session

也就是说,当前 behavior 是:

- **旧历史被重新显示出来了**
- **但真实底层 Codex session 并没有恢复**
- **下一条消息通常是在一个新的 native session 上继续发送**

这意味着:

- 不能把“旧历史可见”误判为“真实上下文恢复”
- 如果要做真正迁移,必须接管底层可恢复的 native session,而不是只复制数据库消息

### 5. Legacy 侧目前没有持久化可直接用于 ACP 接管的真实原生 sessionId
当前 legacy 侧虽然有一些 `sessionId` / `conversationId` 概念,但需要区分:

- `CodexSessionManager` 中的 `sessionId` 是本地 manager 状态 ID,不应直接视为 ACP 可恢复 session
- `CodexAgent` runtime 内部会维护自己的原生会话标识
- 但这些值当前没有稳定写入会话 `extra`,也没有形成 `acpSessionId` 桥接关系

这就是迁移的核心难点。

### 6. 现有 `createWithConversation` 不是我们要的迁移能力
当前已有 `conversation.createWithConversation`,但它的语义是:

- 复制数据库消息到一个新会话
- 校验成功后删除源会话

这适合“会话复制”或“工作区迁移”,**不适合**我们当前要的:

- 保留旧 legacy 会话
- 创建新的 ACP Codex 会话
- 接管真实原生 session

因此,这个接口最多只能作为参考,不能直接当最终迁移方案。

---

## 关键问题(Problem Statement)
1. **现在不能直接删除 legacy runtime**
- 因为历史 `type='codex'` 会话仍在真实使用
- 直接下掉会造成立即回归

2. **现在的 legacy 会话“恢复”只是 UI 历史恢复,不是真正的 native session 恢复**
- 尤其在应用重启后,下一条消息通常会落到新的 native session

3. **无损迁移的前提是拿到可恢复的真实原生 sessionId**
- 如果拿不到这个 ID,就不能承诺“新 ACP 会话继承真实上下文”

4. **不是所有历史 legacy 会话都保证可无损迁移**
- 一部分旧会话可能因为本地 rollout 已不存在、跨机器、工作区变化、信息不足而无法高置信度匹配

---

## 新方案(Updated Plan)

### Phase 1:新建收口 + Legacy 保留 + Session 发现/回填

#### Phase 1 目标
1. 所有新建 Codex 会话统一创建为 ACP Codex
2. 历史 legacy 会话继续正常可见、可进入、可继续对话
3. 为所有 legacy 会话补充“迁移准备”能力,而不是立即迁移

#### Phase 1 工作项

##### A. 新建入口统一收口到 ACP Codex
把所有“新建 Codex 会话”的入口统一改为:

- `type = 'acp'`
- `extra.backend = 'codex'`

同时修正渠道映射、自动首发消息 key 等相关行为。

##### B. 保留历史 Legacy 会话运行能力
**Phase 1 明确保留**:

- `CodexAgentManager`
- `CodexChat` / `CodexSendBox`
- legacy `conversation.type === 'codex'` 的 build/send/render 链路
- 历史列表展示与进入能力

##### C. 增加真实原生 session 的采集与回填能力
对 legacy 会话引入新的 session 解析层,做两件事:

1. **实时捕获**
- 对仍然被打开、继续对话的 legacy 会话,捕获 runtime 中真实可恢复的 native sessionId
- 将其持久化到 conversation extra 的新字段中

2. **离线反查**
- 对历史 legacy 会话扫描本地 Codex rollout/session 存储
- 利用以下信息做匹配:
- workspace / cwd
- 时间窗口
- 首轮消息特征
- 模型信息(若可得)
- 输出匹配结果与置信度

##### D. 为 legacy 会话增加“迁移就绪状态”
建议增加如下状态:

- `resolved`: 已找到唯一高置信度 native session
- `ambiguous`: 找到多个候选 session,需人工确认
- `missing`: 未找到可接管 session
- `ready_for_migration`: 已满足迁移条件

#### Phase 1 产出
- 新流量不再继续产生 legacy `type='codex'`
- 老 legacy 会话继续可用
- 系统可以回答“哪些 legacy 会话可以做无损迁移”

---

### Phase 2:低频专用迁移工具(Session 接管迁移)

#### Phase 2 目标
对 `ready_for_migration` 的 legacy 会话,创建新的 ACP Codex 会话,并通过 `session/load` 接管底层原生 session。

#### Phase 2 基本流程
1. 用户在 legacy 会话中点击“迁移到新 Codex 会话”
2. 系统校验该会话是否已 `ready_for_migration`
3. 创建新的 `type='acp' + backend='codex'` 会话
4. 把已解析出的 native sessionId 写入新会话的 `acpSessionId`
5. 新会话首次启动时直接走 `session/load`
6. 旧 legacy 会话保留,不删除
7. 新老会话建立关联,便于回跳与排查

#### Phase 2 失败策略
- `ambiguous`: 要求用户确认候选 session
- `missing`: 禁止宣称无损迁移,继续保留 legacy 使用
- `load` 失败:回退并保留 legacy,不自动伪造 transcript 续接

---

## 推荐的数据字段(待最终确认)
建议在 legacy conversation `extra` 中新增而不是复用 `acpSessionId`:

- `legacyCodexRuntimeSessionId`
- `legacyCodexSessionResolveStatus`
- `legacyCodexSessionResolvedAt`
- `legacyCodexSessionMatchMeta`
- `legacyCodexMigrationReady`

原因:

- Phase 1 只是“发现与准备”,不是“已经转成 ACP”
- 只有在 Phase 2 真正创建新 ACP 会话时,才应该把确认过的 sessionId 写入新会话的 `acpSessionId`

---

## 验收标准(更新版)

### Phase 1 验收
- 不再创建新的 `conversation.type='codex'`
- 历史 `type='codex'` 会话继续:
- 出现在历史列表
- 可进入
- 可继续发送消息
- 能为 legacy 会话输出 session 解析状态
- 能识别出 `ready_for_migration` 的 legacy 会话

### Phase 2 验收
- 对 `ready_for_migration` 的 legacy 会话,能创建新的 ACP Codex 会话
- 新 ACP 会话通过 `session/load` 接管真实底层上下文
- 老 legacy 会话保留
- 对于无法高置信度匹配的 legacy 会话,不执行伪装成“无损迁移”的错误行为

---

## 当前开放问题
1. 本地 Codex rollout/session 存储的扫描路径与格式是否稳定
2. 历史 legacy 会话的 native session 反查规则如何设计,才能满足高置信度
3. 是否需要对 `ambiguous` 状态提供人工确认 UI
4. 是否需要在 Phase 1 就对全部历史 legacy 会话进行后台批量反查,还是只在首次打开 / 专项任务时反查
5. 新老会话的关联关系如何在 UI 中展示

---

## 当前结论
本 issue 不再以“立刻删除 legacy Codex runtime”为目标,而改为:

- **Phase 1:停止新增 legacy + 保留旧会话能力 + 做 session 发现/回填**
- **Phase 2:对可确认 session 的 legacy 会话,提供低频专用的 Session 接管迁移工具**

只有当 Phase 2 的迁移能力稳定、且绝大多数 legacy 会话已完成迁移后,才适合讨论是否进入“最终删除 legacy runtime”的后续 issue。

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.