Files
cc-web/.trellis/tasks/08-11-unify-ccweb-message-reply/research/impact-map.md

362 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ccweb 消息回传统一影响面映射
## 结论
本任务的核心影响面集中在 3 条链路:
1. 公开工具契约:`lib/ccweb-mcp-server.js` 的 `TOOLS` 当前同时公开
`ccweb_send_message` 与 `ccweb_request_reply`,`server.js` 的 Codex App
fallback dynamic tools 又复制了一份定义。
2. 内部兼容分发:`server.js` 的 `callInternalMcpTool()` 已经把两个工具名分别
分发到 `sendCrossConversationMessage()` 和 `requestCrossConversationReply()`;
旧别名兼容应保留在这里。
3. 自动回传状态机:`sendCrossConversationMessage()` 通过 `expectReply` 创建
pending reply;目标完成后由 `completeCrossConversationReply()`、`deliverCrossConversationReply()`
写回来源并触发来源自动续跑。
`ccweb_request_reply` 不能简单从 `TOOLS` 删除后收工,因为当前正式 MCP 的
`tools/call` 白名单也复用 `TOOLS`。如果公开列表不再包含旧名,就必须把
“公开 tools/list” 与 “tools/call 内部兼容旧名” 分离。
## 代码索引状态
- `codebase-memory-mcp` 项目:`home-cc-web`
- 索引状态:`ready`
- 代码图节点/边:`5941` / `13063`
## 文件影响清单
| 文件 | 当前职责 | 本任务影响 |
|---|---|---|
| `lib/ccweb-mcp-server.js` | 正式 MCP stdio 服务器、正式 `TOOLS` 定义、`tools/list` 和 stdio `tools/call` 白名单 | 新增/调整共享契约导出;`ccweb_send_message` 必填 `replyMode`;公开列表移除 `ccweb_request_reply`;stdio `tools/call` 仍需兼容旧名 |
| `server.js` | 导入 `CCWEB_MCP_TOOLS`、共享 HTTP MCP、内部 dispatcher、跨对话发送/回传、Codex App fallback dynamic tools、pending 持久化 | 复用共享定义;`ccweb_send_message` 按 `replyMode` 归一到 `expectReply`;旧 `ccweb_request_reply` 兼容;目标/来源提示增加模式和原始请求;pending 状态新增 `originalRequest` |
| `README.md` | MCP 工具用户文档 | 公开工具表只推荐 `ccweb_send_message`,说明 `replyMode=one_way/return_and_continue`;旧 `ccweb_request_reply` 只标内部兼容别名 |
| `scripts/regression.js` | 端到端回归覆盖 MCP 发送、回传、busy source 队列、Codex App running target | 现有 `ccweb_request_reply` 测试改为兼容测试;新增 `replyMode` 双模式、公开列表、正式 MCP/dynamic tools 定义一致性、提示内容断言 |
| `public/app.js` | 跨对话 reply 展示、折叠状态、ready count 前端消费 | 本任务通常不改;但回归会间接受到 returned reply metadata 影响 |
## 正式 MCP 定义链路
### 当前位置
- `lib/ccweb-mcp-server.js:15` 定义 `const TOOLS = [...]`。
- `lib/ccweb-mcp-server.js:110` 定义公开 `ccweb_send_message`。
- `lib/ccweb-mcp-server.js:159` 定义公开 `ccweb_request_reply`。
- `lib/ccweb-mcp-server.js:445` 在 stdio `tools/list` 返回 `{ tools: TOOLS }`。
- `lib/ccweb-mcp-server.js:450` 在 stdio `tools/call` 用 `TOOLS.some(...)` 做白名单。
- `lib/ccweb-mcp-server.js:503` 导出 `{ TOOLS, prepareImagePayload, runStdioServer }`。
### 需要调整
- 新建或拆出无副作用共享契约:
- reply mode 常量:`one_way`、`return_and_continue`。
- 校验集合或归一函数。
- `ccweb_send_message` 的 description 与 inputSchema。
- `ccweb_send_message.inputSchema.required` 必须包含:
- `targetConversationId`
- `content`
- `replyMode`
- `replyMode` schema 必须是 enum:
- `one_way`
- `return_and_continue`
- `ccweb_request_reply` 不再出现在正式 `tools/list`。
- `tools/call` 兼容旧名时不要再依赖公开 `TOOLS` 作为唯一白名单。可选策略:
- `PUBLIC_TOOLS` 用于 `tools/list`。
- `CALLABLE_TOOL_NAMES` 或 `isCallableMcpTool(name)` 用于 `tools/call`,包含旧
`ccweb_request_reply`。
## Codex App fallback dynamic tools 链路
### 当前位置
- `server.js:15` 导入 `TOOLS: CCWEB_MCP_TOOLS`。
- `server.js:10221` 定义 `codexAppCommunicationDynamicTools()`。
- `server.js:10273` 复制定义 `ccweb_send_message`,当前无 `replyMode`。
- `server.js:10358` 复制定义 `ccweb_request_reply`。
- `server.js:10391` 定义 `handleCodexAppDynamicToolCall()`。
- `server.js:10395-10402` Codex App dynamic tool 白名单仍包含 `ccweb_request_reply`。
- `server.js:10407` 调用 `callInternalMcpTool()`。
### 需要调整
- `codexAppCommunicationDynamicTools()` 不应再复制 send 的 description/schema。
- 建议从共享正式定义派生 Codex App fallback 定义:
- 保留 `namespace: 'ccweb'`。
- 复用同一 `description` 和 `inputSchema`。
- 不包含 `ccweb_request_reply`。
- `handleCodexAppDynamicToolCall()` 要区分“公开 fallback tool”与“旧线程动态调用兼容”:
- 新公开列表不含 `ccweb_request_reply`。
- 已加载旧 dynamic tool 的线程调用 `ccweb_request_reply` 时,内部仍可接受并映射到
`return_and_continue`。
## Composer MCP 候选链路
### 当前位置
- `server.js:2536` 定义 `listComposerMcpItems()`。
- `server.js:2555` 遍历 `CCWEB_MCP_TOOLS` 生成 `/` 里的 `mcp:ccweb/<tool>` 候选。
### 需要调整
- 如果 `CCWEB_MCP_TOOLS` 改为公开 tools/list,则 Composer 会自动不再展示
`mcp:ccweb/ccweb_request_reply`。
- 必须确认 `/` 候选里 `mcp:ccweb/ccweb_send_message` 的描述与 schema 语义一致。
- 不要从内部兼容白名单反推 Composer 候选,否则会重新暴露旧工具。
## 内部 dispatcher 链路
### 当前位置
- `server.js:6084` 定义 `callInternalMcpTool(tool, args, sourceSessionId, sourceHopCount)`。
- `server.js:6094-6095`:`ccweb_send_message` 直接调用
`sendCrossConversationMessage(args, sourceSessionId, sourceHopCount)`。
- `server.js:6100-6101`:`ccweb_request_reply` 调用
`requestCrossConversationReply(args, sourceSessionId, sourceHopCount)`。
- `server.js:5915-5917`:`requestCrossConversationReply()` 只是
`sendCrossConversationMessage(..., { expectReply: true })`。
### 需要调整
- `callInternalMcpTool()` 是兼容旧工具名的最佳位置。
- 新逻辑建议:
- `ccweb_request_reply` 固定归一为 `replyMode=return_and_continue`。
- `ccweb_send_message` 读取 `args.replyMode`。
- 缺少 `replyMode` 的旧 `ccweb_send_message` 内部按 `one_way` 处理。
- 非法 `replyMode` 返回 `mcpToolError('invalid_reply_mode', ...)`。
- 保持返回字段兼容:
- `one_way` 返回现有 `messageId`、`deliveryStatus` 等。
- `return_and_continue` 继续返回 `requestId`、`status: waiting`、
`replyDelivery`、`sourceAutoRun`。
## 正式 MCP HTTP 与 stdio 入口
### 共享 HTTP MCP
- `server.js:6145` 定义 `handleMcpJsonRpcMessage()`。
- `server.js:6161` 在 `tools/list` 返回 `CCWEB_MCP_TOOLS`。
- `server.js:6165` 在 `tools/call` 用 `CCWEB_MCP_TOOLS.some(...)` 做白名单。
- `server.js:6168-6174` 调用 `callInternalMcpTool()`。
- `server.js:6188-6227` 定义 `handleSharedMcpHttpApi()`,从 URL query 读取
`sourceSessionId`、`sourceHopCount` 后调用 `handleMcpJsonRpcMessage()`。
### 旧内部 HTTP API
- `server.js:6230` 定义 `handleInternalMcpApi()`。
- `server.js:6243-6247` 从 body 读取 `tool/args/sourceSessionId/sourceHopCount`
并直接调用 `callInternalMcpTool()`。
- `scripts/regression.js` 当前大量通过该内部 API 调用工具。
### stdio MCP 桥
- `lib/ccweb-mcp-server.js:327` 定义 `callCcweb(tool, args)`。
- `lib/ccweb-mcp-server.js:352-357` 将 `tool/args/sourceSessionId/sourceHopCount`
POST 到内部 API。
- `lib/ccweb-mcp-server.js:425` 定义 stdio `handleRequest()`。
- `lib/ccweb-mcp-server.js:445` 返回 `TOOLS`。
- `lib/ccweb-mcp-server.js:450` 当前用 `TOOLS` 做 stdio call 白名单。
### 关键风险
正式 `tools/list` 删除旧工具后:
- `handleMcpJsonRpcMessage()` 的 `tools/call` 会拒绝旧 `ccweb_request_reply`。
- stdio `handleRequest()` 的 `tools/call` 也会拒绝旧 `ccweb_request_reply`。
因此兼容旧工具名必须覆盖两个 `tools/call` 白名单,而不是只改
`callInternalMcpTool()`。
## 发送与回传状态机
### 单向发送 / 创建 pending
- `server.js:5808` 定义 `sendCrossConversationMessage()`。
- `server.js:5814-5815` 由 `options.expectReply` 推导 `expectReply/sourceAutoRun`。
- `server.js:5849` `expectReply` 为真时生成 `requestId`。
- `server.js:5857-5874` 写入目标 user message 的 `crossConversation` metadata,
并通过 `setPendingCrossConversationReply()` 创建 pending。
- `server.js:5883-5887` 调用 `handleMessage()`,目标 runtimeText 来自
`buildCrossConversationRuntimeText()`。
- `server.js:5906-5911` 仅在 `requestId` 存在时返回 `status/replyDelivery/sourceAutoRun`。
### pending 持久化
- `server.js:222` pending 文件是 `config/cross-conversation-replies.json`。
- `server.js:4376-4402` `normalizeCrossConversationReplyState()` 归一状态,当前字段包括:
`requestId/messageId/sourceConversationId/sourceTitle/targetConversationId/targetTitle/status/createdAt/hopCount/sourceAutoRun/replyText/completedAt/returnedAt/replyMessageId/lastError`。
- `server.js:4419-4422` `saveCrossConversationReplies()` 写入文件。
- `server.js:4430-4439` `loadCrossConversationReplies()` 启动加载未 returned 的 pending。
- `server.js:4447-4452` `setPendingCrossConversationReply()` 创建 pending。
- `server.js:4455-4468` `updatePendingCrossConversationReply()` 更新 pending。
- `server.js:4471-4475` `deletePendingCrossConversationReply()` 删除 pending。
### 需要新增字段
- PRD 要求 pending reply 状态保留原始请求文本,建议字段名使用
`originalRequest`。
- 写入点:`sendCrossConversationMessage()` 创建 pending 的对象。
- 归一点:`normalizeCrossConversationReplyState()`,旧状态缺失时填 `''` 或明确占位。
- 输出点:
- `crossConversationReplySummary()`
- `getPendingCrossConversationReply()`
- `buildCrossConversationReplyAutoRunText()`
### 目标完成与来源写回
- `server.js:9257` 普通 Codex 运行 entry 记录 `crossConversationReplyRequestId`。
- `server.js:6796-6799` 普通 Codex 完成后调用
`completeCrossConversationReply()`,再 flush 来源 pending。
- `server.js:11158` Codex App 运行 entry 记录 `crossConversationReplyRequestId`。
- `server.js:11322-11326` Codex App 完成后调用
`completeCrossConversationReply()`,再 flush 来源 pending。
- `server.js:6060-6081` `completeCrossConversationReply()` 提取目标输出,更新
pending 为 `ready`,随后调用 `deliverCrossConversationReply()`。
- `server.js:5969-6048` `deliverCrossConversationReply()`:
- 来源不存在或目标不存在时改为 `failed`。
- 来源仍 running 时保持 `ready`,稍后 flush。
- 已处理过则标记 returned 并删除 pending。
- 追加 `ccwebDisplayOnly` assistant 消息到来源。
- metadata 写入 `replyToRequestId/processed/autoRun`。
- `sourceAutoRun` 为真时调用 `startCrossConversationReplyAutoRun()`。
- `server.js:6050-6057` `flushPendingCrossConversationReplies()` 在来源空闲后投递 ready reply。
## 目标运行提示
### 当前位置
- `server.js:5733-5737` `buildCrossConversationRuntimeText(sourceSession, content)`。
- 当前只包含来源标题、来源 ID、消息正文。
- `server.js:5886` `sendCrossConversationMessage()` 发送目标运行时使用该提示。
### 需要调整
`buildCrossConversationRuntimeText()` 需要知道模式,建议参数扩展为:
- `sourceSession`
- `content`
- `replyMode` 或 `{ replyMode, requestId }`
提示必须覆盖:
- `one_way`:明确本轮输出不会自动回传来源。
- `return_and_continue`:
- 系统会自动回传本轮最终输出。
- 目标应在真正完成或明确阻塞后给出完整交付。
- 不要为回复本请求而手工调用跨对话发送工具,避免重复。
- 继续保留来源标题、来源 ID、原消息正文。
## 来源自动续跑提示
### 当前位置
- `server.js:5744-5748` `buildCrossConversationReplyAutoRunText(targetSession, replyText)`。
- `server.js:5750-5777` `startCrossConversationReplyAutoRun()` 使用该 runtimeText 触发来源隐藏续跑。
- 当前提示只包含目标标题和返回正文。
### 需要调整
`buildCrossConversationReplyAutoRunText()` 需要额外输入 pending 或关联上下文,至少包含:
- `requestId`
- 目标对话标题/ID
- 原始请求 `originalRequest`
- 返回正文 `replyText`
- 明确要求来源先判断返回是否完整满足原始请求,不能把“已返回”直接当作“已完成”。
建议让 `deliverCrossConversationReply()` 把 `pending` 或 `{ requestId, originalRequest }`
传给 `startCrossConversationReplyAutoRun()`,再传给
`buildCrossConversationReplyAutoRunText()`。
## `ccweb_create_conversation` 间接受影响
### 当前位置
- `server.js:5646` 定义 `createMcpConversation()`。
- `server.js:5664` 兼容 `args.requestReply === true || args.waitForReply === true`。
- `server.js:5694-5697` 创建后首条消息调用
`sendCrossConversationMessage(..., { expectReply: requestReply })`。
- `server.js:5723-5728` 如果有 `requestId`,返回 `replyStatus/replyDelivery/sourceAutoRun`。
- `lib/ccweb-mcp-server.js:101` 正式 `ccweb_create_conversation` schema 公开 `requestReply`。
- `server.js:10349` Codex App fallback schema 也公开 `requestReply`。
### 需要保持
PRD 不要求修改 `ccweb_create_conversation` 的参数语义。实现 `replyMode` 时必须确保:
- `requestReply=true` 仍创建 pending reply。
- 返回字段仍兼容现有断言。
- 目标提示也应按 `return_and_continue` 模式生成,因为底层仍是跨对话发送。
## 前端展示影响
前端不参与工具契约选择,但会消费返回消息 metadata:
- `public/app.js:1481-1489` `getCrossConversationReplyCollapseKey()` 使用
`replyToRequestId/messageId` 作为折叠 key。
- `public/app.js:7135-7144` 收到 `session_message` 时,如果是
`replyToRequestId`,会减少 ready reply count。
- `public/app.js:7968-7974` `createMsgElement()` 用 `reply/replyToRequestId`
标记跨对话回复气泡。
本任务如果只新增 `originalRequest/replyMode` metadata,前端无需改;如果改变
`replyToRequestId/processed/ccwebDisplayOnly` 字段,则会影响展示和 ready count。
## 回归测试影响面
### 现有相关断言
- `scripts/regression.js:6348-6386` 覆盖 `ccweb_create_conversation` +
`requestReply=true` 自动回传。
- `scripts/regression.js:6392-6415` 覆盖 `ccweb_send_message` 单向发送与目标 runtime prompt。
- `scripts/regression.js:6417-6434` 覆盖跨对话 hop count。
- `scripts/regression.js:6440-6490` 覆盖旧 `ccweb_request_reply` 自动回传。
- `scripts/regression.js:6503-6578` 覆盖来源 busy 时 ready reply 排队、pending list/detail、
来源空闲后 flush 和 auto-run。
- `scripts/regression.js:7348-7357` 覆盖目标 Codex App running 时拒绝发送。
### 需要新增/调整断言
- `tools/list` 中:
- 存在 `ccweb_send_message`。
- `ccweb_send_message.inputSchema.required` 包含 `replyMode`。
- `replyMode.enum` 为 `['one_way', 'return_and_continue']`。
- 不存在公开 `ccweb_request_reply`。
- 正式 MCP 与 Codex App fallback:
- send definition 共用同一 schema/description。
- Codex App fallback 不公开 `ccweb_request_reply`。
- `ccweb_send_message(replyMode='one_way')`:
- 不返回 `requestId`。
- 不创建 pending。
- 目标 runtime prompt 明确不会自动回传。
- `ccweb_send_message(replyMode='return_and_continue')`:
- 返回 `requestId/status/replyDelivery/sourceAutoRun`。
- 目标消息 metadata 有 `expectsReply/replyRequestId`。
- pending 文件有 `originalRequest`。
- 目标 runtime prompt 明确会自动回传并禁止手工重复发送。
- 来源 auto-run prompt 包含 `requestId`、目标标题/ID、原始请求、返回正文、完整性判断指令。
- 兼容:
- 内部 `ccweb_request_reply` 仍成功并等价于 `return_and_continue`。
- 旧 `ccweb_send_message` 缺少 `replyMode` 仍按 `one_way` 成功。
- stdio/shared HTTP `tools/call` 对旧 `ccweb_request_reply` 仍可执行,即使 `tools/list`
不再公开它。
## 建议实现顺序
1. 抽出共享契约,定义 reply modes 和公开 `ccweb_send_message` definition。
2. 让正式 MCP `tools/list` 和 Codex App fallback 复用共享 send definition。
3. 分离公开工具列表与内部可调用工具白名单,保证旧 `ccweb_request_reply` 可 call 不可 list。
4. 在 dispatcher 或发送函数中归一 `replyMode -> expectReply`。
5. pending 增加 `originalRequest`,归一、保存、查询和旧数据兼容。
6. 改目标 runtime prompt 和来源 auto-run prompt。
7. 更新 README。
8. 更新回归断言,覆盖正式 MCP、dynamic fallback、旧兼容和 busy source 队列。
## 主要风险
- 如果只删除 `TOOLS` 中的 `ccweb_request_reply`,旧线程的正式 MCP 调用会被
`tools/call` 白名单挡住,达不到 PRD 的内部兼容要求。
- 如果只改 `lib/ccweb-mcp-server.js`,Codex App fallback dynamic tools 仍会暴露旧工具。
- 如果 `replyMode` 在 schema 必填但内部没有兼容缺省,已加载旧 schema 的
`ccweb_send_message` 调用可能被拒绝。
- 如果来源 auto-run prompt 仍只包含“已返回”,模型可能把不完整返回误判为任务完成。
- 如果 pending 删除过早,`get_pending_reply` 对 returned 历史查询会依赖来源消息里的
`replyToRequestId`,因此不能破坏 `processed/replyToRequestId/ccwebDisplayOnly`。