17 KiB
17 KiB
ccweb 消息回传统一影响面映射
结论
本任务的核心影响面集中在 3 条链路:
- 公开工具契约:
lib/ccweb-mcp-server.js的TOOLS当前同时公开ccweb_send_message与ccweb_request_reply,server.js的 Codex App fallback dynamic tools 又复制了一份定义。 - 内部兼容分发:
server.js的callInternalMcpTool()已经把两个工具名分别 分发到sendCrossConversationMessage()和requestCrossConversationReply(); 旧别名兼容应保留在这里。 - 自动回传状态机:
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在 stdiotools/list返回{ tools: TOOLS }。lib/ccweb-mcp-server.js:450在 stdiotools/call用TOOLS.some(...)做白名单。lib/ccweb-mcp-server.js:503导出{ TOOLS, prepareImagePayload, runStdioServer }。
需要调整
- 新建或拆出无副作用共享契约:
- reply mode 常量:
one_way、return_and_continue。 - 校验集合或归一函数。
ccweb_send_message的 description 与 inputSchema。
- reply mode 常量:
ccweb_send_message.inputSchema.required必须包含:targetConversationIdcontentreplyMode
replyModeschema 必须是 enum:one_wayreturn_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-10402Codex 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/sourceHopCountPOST 到内部 API。lib/ccweb-mcp-server.js:425定义 stdiohandleRequest()。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:5849expectReply为真时生成requestId。server.js:5857-5874写入目标 user message 的crossConversationmetadata, 并通过setPendingCrossConversationReply()创建 pending。server.js:5883-5887调用handleMessage(),目标 runtimeText 来自buildCrossConversationRuntimeText()。server.js:5906-5911仅在requestId存在时返回status/replyDelivery/sourceAutoRun。
pending 持久化
server.js:222pending 文件是config/cross-conversation-replies.json。server.js:4376-4402normalizeCrossConversationReplyState()归一状态,当前字段包括:requestId/messageId/sourceConversationId/sourceTitle/targetConversationId/targetTitle/status/createdAt/hopCount/sourceAutoRun/replyText/completedAt/returnedAt/replyMessageId/lastError。server.js:4419-4422saveCrossConversationReplies()写入文件。server.js:4430-4439loadCrossConversationReplies()启动加载未 returned 的 pending。server.js:4447-4452setPendingCrossConversationReply()创建 pending。server.js:4455-4468updatePendingCrossConversationReply()更新 pending。server.js:4471-4475deletePendingCrossConversationReply()删除 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:11158Codex App 运行 entry 记录crossConversationReplyRequestId。server.js:11322-11326Codex App 完成后调用completeCrossConversationReply(),再 flush 来源 pending。server.js:6060-6081completeCrossConversationReply()提取目标输出,更新 pending 为ready,随后调用deliverCrossConversationReply()。server.js:5969-6048deliverCrossConversationReply():- 来源不存在或目标不存在时改为
failed。 - 来源仍 running 时保持
ready,稍后 flush。 - 已处理过则标记 returned 并删除 pending。
- 追加
ccwebDisplayOnlyassistant 消息到来源。 - metadata 写入
replyToRequestId/processed/autoRun。 sourceAutoRun为真时调用startCrossConversationReplyAutoRun()。
- 来源不存在或目标不存在时改为
server.js:6050-6057flushPendingCrossConversationReplies()在来源空闲后投递 ready reply。
目标运行提示
当前位置
server.js:5733-5737buildCrossConversationRuntimeText(sourceSession, content)。- 当前只包含来源标题、来源 ID、消息正文。
server.js:5886sendCrossConversationMessage()发送目标运行时使用该提示。
需要调整
buildCrossConversationRuntimeText() 需要知道模式,建议参数扩展为:
sourceSessioncontentreplyMode或{ replyMode, requestId }
提示必须覆盖:
one_way:明确本轮输出不会自动回传来源。return_and_continue:- 系统会自动回传本轮最终输出。
- 目标应在真正完成或明确阻塞后给出完整交付。
- 不要为回复本请求而手工调用跨对话发送工具,避免重复。
- 继续保留来源标题、来源 ID、原消息正文。
来源自动续跑提示
当前位置
server.js:5744-5748buildCrossConversationReplyAutoRunText(targetSession, replyText)。server.js:5750-5777startCrossConversationReplyAutoRun()使用该 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_conversationschema 公开requestReply。server.js:10349Codex App fallback schema 也公开requestReply。
需要保持
PRD 不要求修改 ccweb_create_conversation 的参数语义。实现 replyMode 时必须确保:
requestReply=true仍创建 pending reply。- 返回字段仍兼容现有断言。
- 目标提示也应按
return_and_continue模式生成,因为底层仍是跨对话发送。
前端展示影响
前端不参与工具契约选择,但会消费返回消息 metadata:
public/app.js:1481-1489getCrossConversationReplyCollapseKey()使用replyToRequestId/messageId作为折叠 key。public/app.js:7135-7144收到session_message时,如果是replyToRequestId,会减少 ready reply count。public/app.js:7968-7974createMsgElement()用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不再公开它。
- 内部
建议实现顺序
- 抽出共享契约,定义 reply modes 和公开
ccweb_send_messagedefinition。 - 让正式 MCP
tools/list和 Codex App fallback 复用共享 send definition。 - 分离公开工具列表与内部可调用工具白名单,保证旧
ccweb_request_reply可 call 不可 list。 - 在 dispatcher 或发送函数中归一
replyMode -> expectReply。 - pending 增加
originalRequest,归一、保存、查询和旧数据兼容。 - 改目标 runtime prompt 和来源 auto-run prompt。
- 更新 README。
- 更新回归断言,覆盖正式 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。