362 lines
17 KiB
Markdown
362 lines
17 KiB
Markdown
# 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`。
|