# 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/` 候选。 ### 需要调整 - 如果 `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`。